Ruby on Rails GRIP library
Ruby
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
.gitignore
LICENSE
README.md
rails_grip.gemspec

README.md

rails-grip

Author: Konstantin Bokarius kon@fanout.io

A Ruby on Rails GRIP library.

License

rails-grip is offered under the MIT license. See the LICENSE file.

Installation

This library is compatible with both Rails 3 and 4 running against either Ruby 1.9.x or 2.x.

gem install rails_grip

Add the following lines to your Rails application Gemfile:

gem 'gripcontrol'
gem 'rails_grip'

Usage

This library comes with a Rack middleware class, which you must use, and a Railstie implementation that will automatically add the middleware to the application when rails-grip is added to the application's Gemfile. The middleware will parse the Grip-Sig header in any requests to detect if they came from a GRIP proxy, and it will apply any hold instructions when responding. Additionally, the middleware handles WebSocket-Over-HTTP processing so that WebSockets managed by the GRIP proxy can be controlled via HTTP responses from the Rails application.

The middleware should be placed as early as possible in the processing order, so that it can collect all response headers and provide them in a hold instruction if necessary.

Additionally, set grip_proxies in your application configuration:

# pushpin and/or fanout.io is used for sending realtime data to clients
config.grip_proxies = [
    # pushpin
    {
        'control_uri' => 'http://localhost:5561',
        'key' => 'changeme'
    }
    # fanout.io
    #{
    #    'control_uri' => 'https://api.fanout.io/realm/your-realm',
    #    'control_iss' => 'your-realm',
    #    'key' => Base64.decode64('your-realm-key')
    #}
]

If it's possible for clients to access the Rails app directly, without necessarily going through the GRIP proxy, then you may want to avoid sending GRIP instructions to those clients. An easy way to achieve this is with the grip_proxy_required setting. If set, then any direct requests that trigger a GRIP instruction response will be given a 501 Not Implemented error instead.

config.grip_proxy_required = true

To prepend a fixed string to all channels used for publishing and subscribing, set grip_prefix in your configuration:

grip_prefix = '<prefix>'

You can also set any other EPCP servers that aren't necessarily proxies with publish_servers:

config.publish_servers = [
    {
        'uri' => 'http://example.com/base-uri',
        'iss' => 'your-iss', 
        'key' => 'your-key'
    }
]

Note that in Rails 4 the following should be set for API endpoints in the ApplicationController to avoid CSRF authenticity exceptions:

protect_from_forgery except: :<api_endpoint>

Example controller:

class GripController < ApplicationController
  def get
    # if the request didn't come through a GRIP proxy, throw 501
    if !RailsGrip.is_grip_proxied(request)
      render :text => "Not implemented\n", :status => 501
      return
    end

    # subscribe every incoming request to a channel in stream mode
    RailsGrip.set_hold_stream(request, '<channel>')
    render :text => '[stream open]\n'
  end

  def post
    # publish data to subscribers
    data = request.body.read
    RailsGrip.publish('<channel>', HttpStreamFormat.new(data + "\n"))
    render :text => "Ok\n"
  end
end

Stateless WebSocket echo service with broadcast endpoint:

class WebSocketOverHttpGripController < ApplicationController
  def echo
    render nothing: true

    # reject non-websocket requests
    RailsGrip.verify_is_websocket(request)

    # if this is a new connection, accept it and subscribe it to a channel
    ws = RailsGrip.get_wscontext(request)
    if ws.is_opening
      ws.accept
      ws.subscribe('test_channel')
    end

    while ws.can_recv do
      message = ws.recv

      # if return value is nil, then the connection is closed
      if message.nil?
        ws.close
        break
      end

      # echo the message
      ws.send(message)
    end
  end

  def broadcast
    if request.method == 'POST'

      # publish data to all clients that are connected to the echo endpoint
      data = request.body.read
      RailsGrip.publish('<channel>', WebSocketMessageFormat.new(data))

      render :text => "Ok\n"
    else
      render :text => "Method not allowed\n", :status => 405
    end
  end
end