модуль ActionController::Live
Модуль Action Controller 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 на своём потоке, когда закончите, в противном случае сокет может остаться открытым навсегда.
Окончательное ограничение заключается в том, что ваши действия выполняются в отдельном потоке от основного потока. Убедитесь, что ваши действия потокобезопасны, и это не должно быть проблемой (не делитесь состоянием между потоками и т. д.).
Обратите внимание, что Rails по умолчанию включает Rack::ETag, что будет буферировать ваш ответ. В результате, потоковые ответы могут работать неправильно с Rack 2.2.x, и вам может потребоваться реализовать обходные пути в вашем приложении. Вы можете установить заголовки ответа ETag или Last-Modified или удалить Rack::ETag из стека миддлверов, чтобы решить эту проблему.
Вот пример того, как можно установить заголовок Last-Modified если ваша версия Rack 2.2.x:
def stream response.headers["Content-Type"] = "text/event-stream" response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x ... end
Публичные методы экземпляра
# File actionpack/lib/action_controller/metal/live.rb, line 273
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.
new_controller_thread {
ActiveSupport::Dependencies.interlock.running do
t2 = Thread.current
# 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 }
ActiveSupport::IsolatedExecutionState.share_with(t1)
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
end
}
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@_response.await_commit
end
raise error if error
end # File actionpack/lib/action_controller/metal/live.rb, line 319 def response_body=(body) super response.close if response end
# File actionpack/lib/action_controller/metal/live.rb, line 345
def send_stream(filename:, disposition: "attachment", type: nil)
response.headers["Content-Type"] =
(type.is_a?(Symbol) ? Mime[type].to_s : type) ||
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
"application/octet-stream"
response.headers["Content-Disposition"] =
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
yield response.stream
ensure
response.stream.close
end Отправляет поток в браузер, что полезно при генерации экспортов или других данных, где вы не хотите буферизовать весь файл в памяти. Аналогично send_data, но данные генерируются в реальном времени.
Параметры:
-
:filename- предлагает имя файла для использования браузером. -
:type- указывает тип содержимого HTTP. Вы можете указать строку или символ для зарегистрированного типа с помощьюMime::Type.register, например :json. Если опущено, тип будет определён из расширения файла, указанного в:filename. Если для расширения не зарегистрирован тип содержимого, будет использован по умолчанию ‘application/octet-stream’. -
:disposition- указывает, будет ли файл отображаться встроены или загружен. Допустимые значения 'inline' и 'attachment' (по умолчанию).
Пример генерации csv экспорта:
send_stream(filename: "subscribers.csv") do |stream|
stream.write "email_address,updated_at\n"
@subscribers.find_each do |subscriber|
stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
end
end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.