{"id":13880441,"url":"https://github.com/firehoseio/firehose","last_synced_at":"2025-07-16T16:31:55.582Z","repository":{"id":1876521,"uuid":"2801924","full_name":"firehoseio/firehose","owner":"firehoseio","description":"Build realtime Ruby web applications. Created by the fine folks at Poll Everywhere.","archived":false,"fork":false,"pushed_at":"2023-04-11T22:40:34.000Z","size":1773,"stargazers_count":728,"open_issues_count":20,"forks_count":71,"subscribers_count":38,"default_branch":"master","last_synced_at":"2025-06-23T07:51:04.837Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://firehose.io/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/firehoseio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2011-11-18T10:31:18.000Z","updated_at":"2025-05-01T11:52:13.000Z","dependencies_parsed_at":"2023-07-05T21:02:06.707Z","dependency_job_id":null,"html_url":"https://github.com/firehoseio/firehose","commit_stats":{"total_commits":635,"total_committers":23,"mean_commits":"27.608695652173914","dds":0.7606299212598425,"last_synced_commit":"baf440d8efb60b2ac51083e8f95b8f8fd3a348b9"},"previous_names":[],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/firehoseio/firehose","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firehoseio%2Ffirehose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firehoseio%2Ffirehose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firehoseio%2Ffirehose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firehoseio%2Ffirehose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/firehoseio","download_url":"https://codeload.github.com/firehoseio/firehose/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/firehoseio%2Ffirehose/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265524647,"owners_count":23782016,"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-08-06T08:03:02.306Z","updated_at":"2025-07-16T16:31:55.275Z","avatar_url":"https://github.com/firehoseio.png","language":"Ruby","readme":"      __ _          _\n     / _(_)        | |\n    | |_ _ _ __ ___| |__   ___  ___  ___\n    |  _| | '__/ _ \\ '_ \\ / _ \\/ __|/ _ \\\n    | | | | | |  __/ | | | (_) \\__ \\  __/\n    |_| |_|_|  \\___|_| |_|\\___/|___/\\___|\n\n    Build realtime web applications in Ruby and JS\n\n[![Build Status](https://travis-ci.org/firehoseio/firehose.svg?branch=master)](https://travis-ci.org/firehoseio/firehose) [![Code Climate](https://codeclimate.com/github/firehoseio/firehose/badges/gpa.svg)](https://codeclimate.com/github/firehoseio/firehose) [![Test Coverage](https://codeclimate.com/github/firehoseio/firehose/badges/coverage.svg)](https://codeclimate.com/github/firehoseio/firehose/coverage)\n\n# What is Firehose?\n\nFirehose is both a Rack application and JavaScript library that makes building real-time web applications possible.\n\n# Getting Started\n\nFirst, you'll need to [install and run Redis 2.6](http://redis.io/download).\nVersion 2.6 is required because Firehose uses [Lua/EVAL](http://redis.io/commands/eval) for its transactions, which is not available in earlier versions of Redis.\n\nThen install the gem.\n\n```sh\n$ gem install firehose\n```\n\n## The Server\n\nNow fire up the server.\n\n```\n$ firehose server\n\u003e\u003e Thin web server (v1.3.1 codename Triple Espresso)\n\u003e\u003e Maximum connections set to 1024\n\u003e\u003e Listening on 127.0.0.1:7474, CTRL+C to stop\n```\n\nor\n\n```\ndocker-compose build\ndocker-compose up\n```\n\nIn case you're wondering, the Firehose application server runs the Rack app `Firehose::Rack::App.new` inside of Thin or Rainbows! `Firehose::Rack::App` consists of a bunch of smaller apps and a middleware, which is useful for hacking.\n\n## Publish a message to a bunch of subscribers\n\nLets test it out! Open two terminal windows. In one window, curl:\n\n```sh\n$ curl \"http://localhost:7474/hello\"\n```\n\nThen run the following in the other terminal:\n\n```sh\n$ curl -X PUT -d \"Greetings fellow human being...\" \"http://localhost:7474/hello\"\n```\n\nand you should see the message in the other terminal.\n\n```sh\nGreetings fellow human being...\n```\n\n## Run the tests\n\n```sh\ndocker-compose run firehose bundle exec rspec spec\n```\n\n## Yeah, so?\n\nYou have a dirt simple HTTP pub-sub feed. You could setup an `after_commit` hook on ActiveRecord to push JSON to an end-point. On the other side, you could have a Backbone.js application that picks up the changes and updates the client-side UI.\n\nHoly mackerel! Its a nice, clean, RESTful way to build real-time web applications.\n\n# The JavaScript Consumer\n\nFirehose doesn't just stop at curl; it has a full-featured JavaScript client that lets you subscribe to channels for live updates.\n\nStill have the server running? Copy and paste the code below into Firebug or the WebKit console.\n\n```javascript\n\nnew Firehose.Consumer({\n  message: function(msg){\n    console.log(msg);\n  },\n  connected: function(){\n    console.log(\"Great Scotts!! We're connected!\");\n  },\n  disconnected: function(){\n    console.log(\"Well shucks, we're not connected anymore\");\n  },\n  error: function(){\n    console.log(\"Well then, something went horribly wrong.\");\n  },\n  // Note that we do NOT specify a protocol here because we don't\n  // know that yet.\n  uri: '//localhost:7474/hello'\n}).connect();\n```\n\nThere's also a Consumer that uses channel multiplexing.\nThe multiplexed consumer is useful for scenarios where you want to subscribe\nto messages from many channels at once, without having to use one connection\nper channel. You can specify a list of channels to subscribe to, including a\nhandler function per channel that gets called with all messages coming from that\nchannel.\n\nExample:\n\n```javascript\nnew Firehose.MultiplexedConsumer({\n  connected: function(){\n    console.log(\"Great Scotts!! We're connected!\");\n  },\n  disconnected: function(){\n    console.log(\"Well shucks, we're not connected anymore\");\n  },\n  error: function(){\n    console.log(\"Well then, something went horribly wrong.\");\n  },\n  // Note that we don't specify a general message handler function\n  // but instead define one per channel below\n\n  // Note that we do NOT specify a protocol here because we don't\n  // know that yet. We also don't specify a specific channel name as part of\n  // the URI but instead pass in a list of subscriptions below\n  uri: '//localhost:7474/',\n\n  // List of channel subscriptions:\n  channels: {\n    \"/my/channel/1\": {\n      last_sequence: 10,        // defaults to 0 and can be ommitted\n      message: function(msg) {\n        console.log(\"got message on channel 1:\");\n        console.log(msg);\n      }\n    },\n    \"/my/channel/2\": {\n      message: function(msg) {\n        console.log(\"got message on channel 2:\");\n        console.log(msg);\n      }\n    }\n  }\n}).connect();\n```\n\nThen publish another message.\n\n```sh\n$ curl -X PUT -d \"\\\"This is almost magical\\\"\" \"http://localhost:7474/hello\"\n```\n\n# How is it different from socket.io?\n\nsocket.io attempts to store connection state per node instance. Firehose makes no attempt to store connection state.\n\nAlso, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of existing HTTP transports, like POST and PUT, for client-to-server communications.\n\n# The Ruby Publisher\n\nWhile you can certainly make your own PUT requests when publishing messages, Firehose includes a Ruby client for easy publishing.\n\n```ruby\nrequire 'firehose'\nrequire 'json'\njson = {'hello'=\u003e 'world'}.to_json\nfirehose = Firehose::Client::Producer::Http.new('//127.0.0.1:7474')\nfirehose.publish(json).to(\"/my/messages/path\")\n```\n\n## Publishing Options\nYou can pass additional options to the publisher that set specific custom\nconfiguration http headers. The options available are:\n- TTL (how long should the message be buffered for)\n- Buffer size (how many messages for the channel should be kept in the buffer)\n- Deprecated (if marked as deprecated, any publications or subscriptions to the channel will be logged with a deprecation warning)\n- Persist (persisting causes the channel \u0026 message to not be expired after a given or the default TTL)\n\nThe corresponding HTTP headers and allowed values are:\n- `Cache-Control: int`\n- `X-Firehose-Buffer-Size: int`\n- `X-Firehose-Deprecated: true | false`\n- `X-Firehose-Persist: true | false`\n\n```ruby\nfirehose = Firehose::Client::Producer::Http.new('//127.0.0.1:7474')\n# mark channel as deprecated\nfirehose.publish(json).to(\"/my/messages/path\", deprecated: true)\n# expire after 120 seconds\nfirehose.publish(json).to(\"/my/messages/path\", ttl: 120)\n# only keep last item\nfirehose.publish(json).to(\"/my/messages/path\", buffer_size: 1)\n# persist channel \u0026 message forever (or until a new message for this channel declares a new TTL and persist != true)\nfirehose.publish(json).to(\"/my/messages/path\", persist: true)\n```\n\nThese options can be of course be combined within a single request.\n\n# Configuration\n\nFirehose can be configured via environmental variables. Take a look at the [`.env.sample`](./.env.sample) file for more info.\n\n## Server Configuration\n\nThe Firehose server may be configured via the `Firehose::Server.configuration` object as follows:\n\n```ruby\nrequire \"firehose\"\n\n# Implement a custom message handler.\nclass MyFilter \u003c Firehose::Server::MessageFilter\n  def process(message)\n    # SHOUT AT ALL THE SUBSCRIBERS!\n    name = params[\"name\"]\n    message.payload = \"HEY #{name}!, #{message.payload.upcase}!\"\n  end\nend\n\nFirehose::Server.configuration do |config|\n  # Custom message filter. This is useful if you want to implement\n  # authorization per-message for Firehose.\n  config.message_filter = MyFilter\n\n  # Configure redis connection.\n  config.redis.url = ENV.fetch \"FIREHOSE_REDIS_URL\", \"redis://redis:6379/10\"\nend\n```\n\n## Custom MessageFilters\n\nAs mentioned above you can define custom MessageFilters which allow you to\nadd custom logic for things like authentication \u0026 filtering of content.\nBy default, the `Firehose::Server::MessageFilter` base class is used, which does\nnothing to the messages being published.\nYou can override the following methods in your own implementations:\n\n```ruby\nclass MyFilter \u003c Firehose::Server::MessageFilter\n  # Optional override if you need to do any other setup operation.\n  # Make sure to call super(channel).\n  # - channel: name of the channel (String)\n  def initialize(channel)\n    super(channel)\n    MyLogger.info \"Subscribing to channel: #{channel}\"\n  end\n\n  # Optional, called once before process().\n  # - params: Hash of params of the subscription message the client sent\n  def on_subscribe(params)\n    @my_param = params[\"my-param\"].to_i\n\n    # You can also optionally raise an instance of\n    # Firehose::Server::ChannelSubscription::Failed\n    # this will cause the client to receive an error message of the form:\n    # { error: \"Subscription failed\", reason: error_reason }\n    # and the client will call its `subscriptionFailed` callback (if configured)\n  end\n\n  # Custom logic for a message to be published to client.\n  # - message: Firehose::Server::Message instance\n  def process(message)\n    if @my_param \u003e 10\n      message.payload += \"My-Param: #{@my_param}\"\n    end\n  end\n\n  # optional cleanup logic\n  def on_unsubscribe\n  end\nend\n```\n\n## Deprecation logging for channels\n\n## Client publishing option\nYou can mark a message as deprecated (to be logged by Firehose) by passing\n`deprecated: true` as an option to `Firehose::Client::Producer::HTTP#put`.\n\n```ruby\nfirehose = Firehose::Client::Producer::Http.new('//127.0.0.1:7474')\nfirehose.publish(\"{'hello': 'world'}\").to(\"/my/messages/path\", deprecated: true)\n```\n\n## Server side config\nYou can specify a list of channels that are marked as deprecated and will cause subscription and publish events on any of those channels to be logged with a special deprecation message.\n\nExample config:\n\n```ruby\nFirehose::Server.configuration do |config|\n  # set a static list of deprecated channels:\n  config.deprecated_channels = [\"/foo/bar.json\", \"/foo/bar/baz.json\"]\n  # provide a block to determine if a channel is deprecated via custom logic:\n  config.deprecated_channel do |channel|\n    channel =~ /^\\/foo\\/*\\.json$/\n  end\nend\n```\n\n## Rack Configuration\n\nThere are two rack applications that are included with Firehose: `Firehose::Rack::Producer` which a client can `PUT` HTTP request with message payloads to publish information on Firehose and the `Firehose::Rack::Consumer` application which a client connects to via HTTP long polling or WebSockets to consume a message.\n\n### Consumer Configuration\n\n```ruby\n# Kitchen-sink rack configuration file example\nrequire 'firehose'\n\nconsumer = Firehose::Rack::Consumer.new do |app|\n  # Configure how long the server should wait before send the client a 204\n  # with a request to reconnect. Typically browsers time-out the client connection\n  # after 30 seconds, so we set the `Firehose.Consumer` JS client to 25, and the\n  # server to 20 to make sure latency or timing doesn't cause any problems.\n  app.http_long_poll.timeout = 20\nend\n\nrun consumer\n```\n\n### Publisher Configuration\n\n```ruby\n# Kitchen-sink rack configuration file example\nrequire 'firehose'\n\n# There's nothing to configure with the Publisher, but its possible that\n# you might include rack middleware authorization mechanisms here to control\n# who can publish to Firehose.\n\nrun Firehose::Rack::Publisher.new\n```\n\n## Sprockets\n\nUsing Sprockets is the recommended method of including the included client-side assets in a web page.\n\n1. Add the firehose gem in your app's Gemfile.\n\n2. Append the firehose gem's assets to the sprockets path. In a Rails app, this is usually done in an initializer.\n\n```ruby\n# Add firehose to a custom sprockets configuration.\nmy_sprockets_env = Sprockets::Environment.new\nFirehose::Assets::Sprockets.configure my_sprockets_env\n```\n\n3. Require your config file and the firehose gem. This would look something like this:\n\n```ruby\n#= require some/other/js/file\n#= require lib/firehose_config\n#= require firehose\n#= require some/more/js/files\n```\n\nIt is important that your firehose config file comes first.\n\n### Not using sprockets?\n\nIf you don't intend to use the Firehose JavaScript client in a Ruby stack where Sprockets is available, you can grab the unminified source by running:\n\n```sh\n$ firehose javascript \u003e firehose.js\n```\n\nCopy the firehose.js where needed in your project.\n\n# Web Server\n\nFirehose currently supports Thin and Rainbows! (which is the default). Neither is listed as a dependency in the gemspec so that you don't need to install whichever one you aren't using. You can set which server to use via the `.env` file (recommended) or with the `-s` option to `bin/firehose`.\n\n# Exception Notification\n\nIf you'd like to be notified of exceptions, add something like this in your custom config.ru file.\n\n```ruby\n# Use exceptional to handle anything missed by Rack::Exceptional\nif exceptional_key = ENV['EXCEPTIONAL_KEY']\n  require 'exceptional'\n  EM.error_handler do |e|\n    Firehose.logger.error \"Unhandled exception: #{e.class} #{e.message}\\n#{e.backtrace.join \"\\n\"}\"\n    ::Exceptional.handle(e)\n  end\nend\n```\n\n# Deployment\n\nThe recommended method of deploying Firehose is to deploy it separately from your main app.\n\n1. Create a new project with a Gemfile such as\n\n```\ngem \"firehose\"\ngem \"airbrake\"\ngem \"rainbows\",   :require =\u003e false\ngem \"rack\", \"~\u003e 1.4.0\" # if you're using Rainbows. See https://github.com/firehoseio/firehose/commit/dfe55fff\ngem \"foreman\",    :require =\u003e false\ngem \"capistrano\", :require =\u003e false\n```\n\nOf course, you could use `exceptional` instead of `airbrake` and `thin` instead of `rainbows`.\n\n2. Set up `config/deploy.rb` to your liking. You can follow most directions for using Capistrano and Foreman to deploy Rack apps, such as https://gist.github.com/1027117\n\n3. Set up `config/rainbows.rb` (if you are using Rainbows!). The gem includes example configurations scripts to get you started. There's also an example at https://gist.github.com/bradgessler/f2416efdbb1771e983b3.\n\n# New releases \u0026 version bump\n\nFor a new release of Firehose, bump the version number in `lib/firehose/version.rb` as well as `package.json`.\nMake sure, they have the same version number.\n\n# Support\n\n## Ruby version\n\nFirehose will support the latest minor 2.x revisions of Ruby that are officially supported by the Ruby community. More details at https://www.ruby-lang.org/.\n","funding_links":[],"categories":["Ruby","WebSocket"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehoseio%2Ffirehose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffirehoseio%2Ffirehose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehoseio%2Ffirehose/lists"}