{"id":22026176,"url":"https://github.com/mobomo/rack-stream","last_synced_at":"2025-05-07T10:15:29.297Z","repository":{"id":3387796,"uuid":"4436314","full_name":"mobomo/rack-stream","owner":"mobomo","description":"NO LONGER MAINTAINED - rack-stream","archived":true,"fork":false,"pushed_at":"2016-09-10T08:40:02.000Z","size":415,"stargazers_count":143,"open_issues_count":6,"forks_count":13,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-07T10:15:12.857Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mobomo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-05-24T18:29:24.000Z","updated_at":"2024-10-08T18:36:33.000Z","dependencies_parsed_at":"2022-09-05T14:11:49.972Z","dependency_job_id":null,"html_url":"https://github.com/mobomo/rack-stream","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobomo%2Frack-stream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobomo%2Frack-stream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobomo%2Frack-stream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobomo%2Frack-stream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mobomo","download_url":"https://codeload.github.com/mobomo/rack-stream/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252856558,"owners_count":21814858,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-30T07:25:36.401Z","updated_at":"2025-05-07T10:15:29.232Z","avatar_url":"https://github.com/mobomo.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rack-stream [![Build Status](https://secure.travis-ci.org/intridea/rack-stream.png)](http://travis-ci.org/jch/rack-stream)\n\n## Overview\n\nrack-stream is middleware for building multi-protocol streaming rack endpoints.\n\n## Installation\n\n```ruby\n# Gemfile\ngem 'rack-stream'\n```\n\n```sh\nbundle\n```\n\n## Example\n\n```ruby\n# config.ru\nrequire 'rack/stream'\n\nclass App\n  include Rack::Stream::DSL\n\n  stream do\n    after_open do\n      count = 0\n      @timer = EM.add_periodic_timer(1) do\n        if count != 3\n          chunk \"chunky #{count}\\n\"\n          count += 1\n        else\n          # Connection isn't closed until #close is called.\n          # Useful if you're building a firehose API\n          close\n        end\n      end\n    end\n\n    before_close do\n      @timer.cancel\n      chunk \"monkey!\\n\"\n    end\n\n    [200, {'Content-Type' =\u003e 'text/plain'}, []]\n  end\nend\n\napp = Rack::Builder.app do\n  use Rack::Stream\n  run App.new\nend\n\nrun app\n```\n\nTo run the example:\n\n```\n\u003e thin start -R config.ru -p 3000\n\u003e curl -i -N http://localhost:3000/\n\u003e\u003e HTTP/1.1 200 OK\n\u003e\u003e Content-Type: text/plain\n\u003e\u003e Transfer-Encoding: chunked\n\u003e\u003e\n\u003e\u003e chunky 0\n\u003e\u003e chunky 1\n\u003e\u003e chunky 2\n\u003e\u003e monkey\n```\n\nThis same endpoint can be accessed via WebSockets or EventSource, see\n'Multi-Protocol Support' below. Full examples can be found in the `examples`\ndirectory.\n\n## Connection Lifecycle\n\nWhen using rack-stream, downstream apps can access the\n`Rack::Stream::App` instance via `env['rack.stream']`. This object is\nused to control when the connection is closed, and what is streamed.\n`Rack::Stream::DSL` delegates access methods to `env['rack.stream']`\non the downstream rack app.\n\n`Rack::Stream::App` instances are in one of the follow states:\n\n* new\n* open\n* closed\n* errored\n\nEach state is described below.\n\n### new\n\nWhen a request first comes in, rack-stream processes any downstream\nrack apps and uses their status and headers for its response. Any\ndownstream response bodies are queued for streaming once the headers\nand status have been sent. Any calls to `#chunk` before a connection\nis opened is queued to be sent after a connection opens.\n\n```ruby\nuse Rack::Stream\n\n# once Rack::Stream instance is :open, 'Chunky Monkey' will be streamed out\nrun lambda {|env| [200, {'Content-Type' =\u003e 'text/plain'}, ['Chunky Monkey']]}\n```\n\n### open\n\nBefore the status and headers are sent in the response, they are\nfrozen and cannot be further modified. Attempting to modify these\nfields will put the instance into an `:errored` state.\n\nAfter the status and headers are sent, registered `:after_open`\ncallbacks will be called. If no `:after_open` callbacks are defined,\nthe instance will close the connection after flushing any queued\nchunks.\n\nIf any `:after_open` callbacks are defined, it's the callback's\nresponsibility to call `#close` when the connection should be\nclosed. This allows you to build firehose streaming APIs with full\ncontrol of when to close connections.\n\n```ruby\nuse Rack::Stream\n\nrun lambda {|env|\n  stream = env['rack.stream']\n  stream.after_open do\n    stream.chunk \"Chunky\"\n    stream.chunk \"Monkey\"\n    stream.close  # \u003c-- It's your responsibility to close the connection\n  end\n  [200, {'Content-Type' =\u003e 'text/plain'}, ['Hello', 'World']]  # \u003c-- downstream response bodies are also streamed\n}\n```\n\nThere are no `:before_open` callbacks. If you want something to be\ndone before streaming is started, simply return it as part of your\ndownstream response.\n\n### closed\n\nAn instance enters the `:closed` state after the method `#close` is\ncalled on it. By default, any remainined queued content to be streamed\nwill be flushed before the connection is closed.\n\n```ruby\nuse Rack::Stream\n\nrun lambda {|env|\n  # to save typing, access the Rack::Stream instance with #instance_eval\n  env['rack.stream'].instance_eval do\n    before_close do\n      chunk \"Goodbye!\"  # chunks can still be sent\n    end\n\n    after_close do\n      # any additional cleanup. Calling #chunk here will result in an error.\n    end\n  end\n  [200, {}, []]\n}\n```\n\n### errored\n\nAn instance enters the `:errored` state if an illegal action is\nperformed in one of the states. Legal actions for the different states\nare:\n\n* **new** - `#chunk`, `#status=`, `#headers=`\n* **open** - `#chunk`, `#close`\n\nAll other actions are considered illegal. Manipulating headers after\n`:new` is also illegal. The connection is closed immediately, and the\nerror is written to `env['rack.error']`\n\n## Manipulating Content\n\nWhen a connection is open and streaming content, you can define\n`:before_chunk` callbacks to manipulate the content before it's sent\nout.\n\n```ruby\nuse Rack::Stream\n\nrun lambda {|env|\n  env['rack.stream'].instance_eval do\n    after_open do\n      chunk \"chunky\", \"monkey\"\n    end\n\n    before_chunk do |chunks|\n      # return the manipulated chunks of data to be sent\n      # this will stream MONKEYCHUNKY\n      chunks.map(\u0026:upcase).reverse\n    end\n  end\n}\n```\n\n## Multi-Protocol Support\n\n`Rack::Stream` allows you to write an API endpoint that automatically\nresponds to different protocols based on the incoming request. This\nallows you to write a single rack endpoint that can respond to normal\nHTTP, WebSockets, or EventSource.\n\nAssuming that rack-stream endpoint is running on port 3000. You can\naccess it with the following:\n\n### HTTP\n\n```\n# -i prints headers, -N immediately displays output instead of buffering\ncurl -i -N http://localhost:3000/\n```\n\n### WebSockets\n\nWith Ruby:\n\n```ruby\nrequire 'eventmachine'\nrequire 'faye/websocket'\n\nEM.run {\n  socket = Faye::WebSocket::Client.new('ws://localhost:3000/')\n  socket.onmessage = lambda {|e| puts e.data}  # puts each streamed chunk\n  socket.onclose   = lambda {|e| EM.stop}\n}\n```\n\nWith Javascript:\n\n```js\nvar socket = new WebSocket(\"ws://localhost:3000/\");\nsocket.onmessage = function(m) {console.log(m);}\nsocket.onclose   = function()  {console.log('socket closed');}\n```\n\n### EventSource\n\nFrom Wikipedia:\n\n\u003e Server-sent events is a technology for providing push notifications\n\u003e from a server to a browser client in the form of DOM events. The\n\u003e Server-Sent Events EventSource API is now being standardized as part\n\u003e of HTML5 by the W3C.\n\nWith Ruby:\n\n```ruby\nrequire 'em-eventsource'\n\nEM.run do\n  source = EventMachine::EventSource.new(\"http://example.com/streaming\")\n  source.message do |m|\n    puts m\n  end\n  source.start\nend\n```\n\nWith Javascript:\n\n```js\nvar source = new EventSource('/');\nsource.addEventListener('message', function(e) {\n  console.log(e.data);\n});\n```\n\n## Supported Runtimes\n\n* 1.9.2\n* 1.9.3\n\nIf a runtime is not listed above, it may still work. It just means I\nhaven't tried it yet. The only app server I've tried running is Thin.\n\n## Roadmap\n\n* more protocols / custom protocols http://en.wikipedia.org/wiki/HTTP_Streaming\n* integrate into [grape](http://github.com/intridea/grape)\n* add sinatra example that serves page that uses JS to connect\n* deployment guide\n* better integration with rails\n\n* body: don't enqueue more chunks if state is succeeded?\n* performance: GC, cleanup references, profile\n\n\n## Further Reading\n\n* [API Reference](http://rubydoc.info/gems/rack-stream)\n* [Stream Updates With Server-Sent Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/)\n* [thin_async](https://github.com/macournoyer/thin_async) was where I got started\n* [thin-async-test](https://github.com/phiggins/thin-async-test) used to simulate thin in tests\n* [thin](https://github.com/macournoyer/thin)\n* [faye-websocket-ruby](https://github.com/faye/faye-websocket-ruby) used for testing and handling different protocols\n* [rack-chunked](http://rack.rubyforge.org/doc/Rack/Chunked.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobomo%2Frack-stream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmobomo%2Frack-stream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobomo%2Frack-stream/lists"}