How to get ActionController::Live streaming working with Thin?
Asked Answered
B

1

6

Question

Can you use thin with ActionController::Live to implement Server Side Events (SSE) and long polling? If so, how?

Context

Although the title is a repeat of How to get Rails 4 ActionController::Live streaming working with Thin and Ruby 2? And how do Thin and Puma scale with live streaming?, the OP muddied the waters by asking two questions, and this question never got answered.

A number of other posts suggest you CAN use thin for Server Side Events (sse) if you start it via exec thin start --threaded: Does Heroku support ActionController::Live? and Is puma the ONLY multi-threaded rails 4 http server?, Aaron's seminal http://tenderlovemaking.com/2012/07/30/is-it-live.html and Ryan's perennially dependable http://railscasts.com/episodes/401-actioncontroller-live?view=asciicast. But even though I'm copying the Railscast example, I haven't been able to get it to work with thin.

What I've tried

# ----------------------------------------------------------------
# file: config/routes.rb
Rails.application.routes.draw do

  resources :widgets do
    collection do
      get 'events'              # SSE test
    end
  end
end

_

# ----------------------------------------------------------------
# file: config/environments/development.rb
Rails.application.configure do
    ... snip ...
    # see http://tenderlovemaking.com/2012/07/30/is-it-live.html
    config.preload_frameworks = true
    config.allow_concurrency = true
end

_

# ----------------------------------------------------------------
# file: app/controllers/widgets_controller.rb
class WidgetsController < ApplicationController
  include ActionController::Live

  # GET /widgets/events
  # see http://railscasts.com/episodes/401-actioncontroller-live?view=asciicast
  def events
    # SSE expects the `text/event-stream` content type
    response.headers['Content-Type'] = 'text/event-stream'
    3.times do |n|
      response.stream.write "#{n}...\n\n"
      sleep 2
    end
  ensure
    response.stream.close
  end

end

_

# ----------------------------------------------------------------
# Gemfile
source 'https://rubygems.org'

gem 'rails', '4.1.8'
gem 'pg'
... snip ...
gem 'thin'

Running it

In shell window A:

$ bundle install
Chalcedony[~/Projects/heroku-sample/widget-worker]$ thin start --threaded --trace
Using rack adapter
Thin web server (v1.6.3 codename Protein Powder)
Tracing ON
Maximum connections set to 1024
Listening on 0.0.0.0:3000, CTRL+C to stop

Then in shell window B:

$ curl --no-buffer localhost:3000/widgets/events

Back in shell window A I see the request and the responses being spit out at one second intervals (there's a one second delay between the 0... and 1... and 2...). That's good:

GET /widgets/events HTTP/1.1
User-Agent: curl/7.37.1
Host: localhost:3000
Accept: */*


HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Cache-Control: no-cache
Content-Type: text/html; charset=utf-8
X-Request-Id: 95e64eb6-ee21-4e97-a33a-dbf579b3027c
X-Runtime: 0.066925
Connection: close
Server: thin


0... <delay...>


1... <delay...>


2... <delay...>

But in shell window B, the printout is delayed and appears all at once. The same thing happens when I view the page in Chrome. Have I failed to configure some settings properly?

P.S.:

$ rake about
About your application's environment
Ruby version              2.1.4-p265 (x86_64-darwin14.0)
RubyGems version          2.2.2
Rack version              1.5
Rails version             4.1.8
JavaScript Runtime        JavaScriptCore
Active Record version     4.1.8
Action Pack version       4.1.8
Action View version       4.1.8
Action Mailer version     4.1.8
Active Support version    4.1.8
Middleware                Rack::Sendfile, ActionDispatch::Static, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fb0cb4ae1a0>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
Environment               development
Database adapter          postgresql
Database schema version   20141213003938

P.P.S.

Right now you might be thinking "Why aren't you using puma like everyone else?" Good question. Right now, I'm unable to build the puma gem on my machine for reasons I haven't figured out. And I've been using thin in most of my heroku deployed apps, so I'm comfortable with it. If I can't get thin to work, I'll put more effort into building puma.

Beaver answered 14/12, 2014 at 0:8 Comment(2)
My earlier comment was incorrect (or perhaps correct, but for the wrong reasons at the very best).Protectorate
Okay, reading a little more closely, I feel like the connection header on curl shouldn't be close for streaming events. The effect you're describing is like it's not streaming at all, but instead waiting until all the data is through and then flushing. I don't have the answer, but maybe this will help. I eagerly await someone who has more experience with this!Protectorate
A
3

Sadly no, you can't use AC::Live with Thin. Here's Mark explaining why and what are the alternatives.

Aphesis answered 5/4, 2015 at 8:44 Comment(1)
Simple answer. Switch gem 'thin' for gem 'puma' et voila.Bodhisattva

© 2022 - 2024 — McMap. All rights reserved.