Module: ActionController::Live

Defined in:
lib/action_controller/metal/live.rb

Overview

Mix this module in to your controller, and all actions in that controller will be able to stream data to the client as it's written.

class MyController < ActionController::Base
  include ActionController::Live

  def stream
    response.headers['Content-Type'] = 'text/event-stream'
    100.times {
      response.stream.write "hello world\n"
      sleep 1
    }
  ensure
    response.stream.close
  end
end

There are a few caveats with this use. You cannot write headers after the response has been committed (Response#committed? will return truthy). Calling write or close on the response stream will cause the response object to be committed. Make sure all headers are set before calling write or close on your stream.

You must call close on your stream when you're finished, otherwise the socket may be left open forever.

The final caveat is that your actions are executed in a separate thread than the main thread. Make sure your actions are thread safe, and this shouldn't be a problem (don't share state across threads, etc).

Defined Under Namespace

Classes: Buffer, Response

Instance Method Summary collapse

Instance Method Details

#log_error(exception) ⇒ Object


151
152
153
154
155
156
157
158
159
# File 'lib/action_controller/metal/live.rb', line 151

def log_error(exception)
  logger = ActionController::Base.logger
  return unless logger

  message = "\n#{exception.class} (#{exception.message}):\n"
  message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
  message << "  " << exception.backtrace.join("\n  ")
  logger.fatal("#{message}\n\n")
end

#process(name) ⇒ Object


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/action_controller/metal/live.rb', line 116

def process(name)
  t1 = Thread.current
  locals = t1.keys.map { |key| [key, t1[key]] }

  # This processes the action in a child thread. It lets us return the
  # response code and headers back up the rack stack, and still process
  # the body in parallel with sending data to the client
  Thread.new {
    t2 = Thread.current
    t2.abort_on_exception = true

    # Since we're processing the view in a different thread, copy the
    # thread locals from the main thread to the child thread. :'(
    locals.each { |k,v| t2[k] = v }

    begin
      super(name)
    rescue => e
      begin
        @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
        @_response.stream.call_on_error
      rescue => exception
        log_error(exception)
      ensure
        log_error(e)
        @_response.stream.close
      end
    ensure
      @_response.commit!
    end
  }

  @_response.await_commit
end

#response_body=(body) ⇒ Object


161
162
163
164
# File 'lib/action_controller/metal/live.rb', line 161

def response_body=(body)
  super
  response.stream.close if response
end

#set_response!(request) ⇒ Object


166
167
168
169
170
171
172
173
# File 'lib/action_controller/metal/live.rb', line 166

def set_response!(request)
  if request.env["HTTP_VERSION"] == "HTTP/1.0"
    super
  else
    @_response         = Live::Response.new
    @_response.request = request
  end
end