модуль ActionController::Live
Смешайте этот модуль в свой контроллер, и все действия в этом контроллере смогут передавать данные клиенту по мере их записи.
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
Есть несколько ограничений в этом использовании. Вы не можете писать заголовки после того, как ответ был подтвержден (Response#committed? вернёт истинное значение). Вызов write или close для потока ответа приведет к подтверждению объекта ответа. Убедитесь, что все заголовки установлены перед вызовом write или close для вашего потока.
Вы обязаны вызвать close для своего потока, когда закончите, в противном случае сокет может остаться открытым навсегда.
Последнее ограничение заключается в том, что ваши действия выполняются в отдельном потоке, отличном от основного потока. Убедитесь, что ваши действия безопасны для потоков, и это не должно быть проблемой (не делитесь состоянием между потоками и т.д.).
Открытые методы экземпляра
# File actionpack/lib/action_controller/metal/live.rb, line 302
def log_error(exception)
logger = ActionController::Base.logger
return unless logger
logger.fatal do
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 ")
"#{message}\n\n"
end
end # File actionpack/lib/action_controller/metal/live.rb, line 261
def process(name)
t1 = Thread.current
locals = t1.keys.map { |key| [key, t1[key]] }
error = nil
# 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
if @_response.committed?
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
else
error = e
end
ensure
@_response.commit!
end
}
@_response.await_commit
raise error if error
end # File actionpack/lib/action_controller/metal/live.rb, line 314 def response_body=(body) super response.close if response end
# File actionpack/lib/action_controller/metal/live.rb, line 319
def set_response!(request)
if request.env["HTTP_VERSION"] == "HTTP/1.0"
super
else
@_response = Live::Response.new
@_response.request = request
end
end
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.