Spec-Zone.ru › Ruby on Rails 8.0

модуль ActionController::Live

Контроллер Действий 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

Публичные методы класса

live_thread_pool_executor() Показать исходный код
# File actionpack/lib/action_controller/metal/live.rb, line 386
def self.live_thread_pool_executor
  @live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live")
end

Публичные методы экземпляра

process(name) Показать исходный код
# File actionpack/lib/action_controller/metal/live.rb, line 276
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
        # Ensure we clean up any thread locals we copied so that the thread can reused.
        ActiveSupport::IsolatedExecutionState.clear
        locals.each { |k, _| t2[k] = nil }

        @_response.commit!
      end
    end
  }

  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
    @_response.await_commit
  end

  raise error if error
end
Вызывает метод суперкласса
response_body=(body) Показать исходный код
# File actionpack/lib/action_controller/metal/live.rb, line 326
def response_body=(body)
  super
  response.close if response
end
Вызывает метод суперкласса
send_stream(filename:, disposition: "attachment", type: nil) { |stream| ... } Показать исходный код
# File actionpack/lib/action_controller/metal/live.rb, line 356
def send_stream(filename:, disposition: "attachment", type: nil)
  payload = { filename: filename, disposition: disposition, type: type }
  ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
    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
  end
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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API