Что такое потоковое?
Потоковая передача существует в Rails начиная с версии 3.2, но она ограничена потоковой передачей шаблонов . Rails 4 поставляется с гораздо более зрелой функциональностью потоковой передачи в реальном времени. По сути, это означает, что Rails теперь может обрабатывать объекты ввода-вывода и отправлять данные клиенту в режиме реального времени.
Streaming
Live
ActionController
Streaming
Live
Основной потоковый API использует класс Fiber
Fiber
Потоки, вызванные Fiber
Потоковое шаблонов
Потоковая передача инвертирует обычный Rails-процесс рендеринга макета и шаблона. По умолчанию Rails сначала отображает шаблон, а затем макет. Первый метод, который он запускает, — yield
Затем активы и макеты отображаются.
Рассмотрим представление с интенсивным запросом, такое как общесистемная временная шкала нескольких классов, например:
class TimelineController
def index
@users = User.all
@tickets = Ticket.all
@attachments = Attachment.all
end
end
В этом случае потоковое вещание кажется подходящим. В типичном сценарии Rails эта страница загружается дольше, чем обычно, потому что сначала она должна извлечь все данные.
Давайте добавим потоковую передачу:
class TimelineController
def index
@users = User.all
@tickets = Ticket.all
@attachments = Attachment.all
render stream: true
end
end
Метод Rails render stream: true
Потоковая передача работает только с шаблонами, а не с любыми другими формами (такими как json или xml). Это добавляет умный метод, чтобы приложение расставило приоритеты шаблонов в зависимости от типа страницы и содержимого.
Передача вещи между
Потоковая передача изменяет метод рендеринга шаблона и макета. Это порождает логическую проблему: шаблоны, использующие переменные экземпляра.
Поскольку вызовы базы данных не выполнялись при визуализации шаблонов, ссылки на переменные экземпляра не будут выполнены.
Следовательно, чтобы загрузить такие атрибуты, как title
meta
content_for
yield
yield
Ранее наш метод выглядел так:
<%= yield :title %>
Теперь это будет выглядеть так:
<%= content_for :title, "My Awesome Title" %>
Начало работы с Live API
Live
Это позволяет Rails открывать и закрывать поток явным образом. Давайте создадим простое приложение и посмотрим, как его включить и получить к нему доступ извне.
Мы смотрим на концепции потокового вещания и параллелизма, и WEBrick не очень хорошо с такими вещами. В результате мы будем использовать Puma для обработки параллелизма и потоков в нашем приложении.
Добавить Puma в Gemfile и связать.
gem "puma"
:~/testapp$ bundle install
Puma хорошо интегрируется с Rails, поэтому, как только вы запустите `rails s` (требуется перезапуск сервера, если вы уже его запускаете), Puma загружается с того же номера порта, что и WEBrick.
:~/testapp$ rails s
=> Booting Puma
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Puma 2.3.0 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000
Давайте быстро сгенерируем контроллер для отправки сообщений.
:~/testapp$ rails g controller messaging
Также добавьте базовый метод для потоковой передачи сообщений на стандартный выход.
class MessagingController < ApplicationController
include ActionController::Live
def send_message
response.headers['Content-Type'] = 'text/event-stream'
10.times {
response.stream.write "This is a test Messagen"
sleep 1
}
response.stream.close
end
end
и маршрут вways.rb
get 'messaging' => 'messaging#send_message'
Мы можем получить доступ к этому методу через curl следующим образом:
~/testapp$ curl -i http://localhost:3000/messaging
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Content-Type: text/event-stream
Cache-Control: no-cache
Set-Cookie: request_method=GET; path=/
X-Request-Id: 68c6b7c7-4f5f-46cc-9923-95778033eee7
X-Runtime: 0.846080
Transfer-Encoding: chunked
This is a test message
This is a test message
This is a test message
This is a test message
Когда мы вызываем метод send_message
Стандартная конфигурация Puma допускает 16 одновременных потоков, что означает 16 клиентов. Конечно, это можно увеличить, но не без некоторых накладных расходов памяти.
Давайте создадим форму и посмотрим, сможем ли мы отправить некоторые данные в наше представление:
def index
end
def send_message
response.headers['Content-Type'] = 'text/event-stream'
10.times {
response.stream.write "#{params[:message]}n"
sleep 1
}
response.stream.close
end
Создайте форму для отправки данных в поток.
<%= form_tag messaging_path, :method => 'get' do %>
<%= text_field_tag :message, params[:message] %>
<%= submit_tag "Post Message" %>
<% end %>
И маршрут не сделать звонок.
root 'messaging#index'
get 'messaging' => 'messaging#send_message', :as => 'messaging'
Как только вы наберете сообщение и нажмете «Опубликовать сообщение», браузер получит ответ потока в виде загружаемого текстового файла, который содержит сообщение, зарегистрированное 10 раз.
Здесь, однако, поток не знает, куда отправлять данные или в каком формате. Таким образом, он пишет в текстовый файл на сервере.
Вы также можете проверить это, отправив параметры через curl .
:~/testapp$ curl -i http://localhost:3000/messaging?message="awesome"
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Content-Type: text/event-stream
Cache-Control: no-cache
Set-Cookie: request_method=GET; path=/
X-Request-Id: 382bbf75-7d32-47c4-a767-576ec59cc364
X-Runtime: 0.055470
Transfer-Encoding: chunked
awesome
awesome
Серверные события (SSE)
HTML5 представил метод, называемый серверными событиями (SSE). SSE — это метод, доступный в браузере, который распознает и запускает событие всякий раз, когда сервер отправляет данные.
Вы можете использовать SSE в сочетании с Live API для обеспечения полнодуплексной связи.
По умолчанию Rails обеспечивает односторонний процесс связи, записывая поток клиенту, когда данные доступны. Однако, если мы можем добавить SSE, мы можем включить события и ответы, таким образом делая это двусторонним.
Простой SSE выглядит следующим образом:
require 'json'
module ServerSide
class SSE
def initialize io
@io = io
end
def write object, options = {}
options.each do |k,v|
@io.write "#{k}: #{v}n"
end
@io.write "data: #{object}nn"
end
def close
@io.close
end
end
end
Этот модуль назначает объект потока ввода / вывода хешу и преобразует его в пару ключ-значение, чтобы его можно было легко прочитать, сохранить и отправить обратно в формате JSON.
Теперь вы можете обернуть ваш потоковый объект в класс SSE. Сначала включите ваш модуль SSE
Теперь открытие и закрытие соединений обрабатываются модулем SSE
Кроме того, если явно не завершено, цикл будет продолжаться бесконечно, и соединение будет открыто навсегда, поэтому мы добавляем условие ensure
require 'server_side/sse'
class MessagingController < ApplicationController
include ActionController::Live
def index
end
def stream
response.headers['Content-Type'] = 'text/event-stream'
sse = ServerSide::SSE.new(response.stream)
begin
loop do
sse.write({ :message => "#{params[:message]}" })
sleep 1
end
rescue IOError
ensure
sse.close
end
end
end
Вот как выглядит ответ:
:~/testapp$ curl -i http://localhost:3000/messaging?message="awesome"
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Content-Type: text/event-stream
Cache-Control: no-cache
Set-Cookie: request_method=GET; path=/
X-Request-Id: b922a2eb-9358-429b-b1bb-015421ab8526
X-Runtime: 0.067414
Transfer-Encoding: chunked
data: {:message=>"awesome"}
data: {:message=>"awesome"}
Gotchas
Есть несколько ошибок (всегда есть …)
- Все потоки должны быть закрыты, иначе они будут открыты навсегда.
- Вы должны будете убедиться, что ваш код является потокобезопасным, так как контроллер всегда создает новый поток при вызове метода.
- После первой фиксации ответа заголовок нельзя переписать в
write
close
Вывод
Эту функцию многие с нетерпением ждут, поскольку она значительно повысит производительность приложений Rails (потоковая передача шаблонов), а также создаст сильную конкуренцию для node.js (Live).
Есть люди, уже сравнивающие различия, но я чувствую, что это только начало и потребуется время (читай дальше), чтобы эта функция стала зрелой. Пока это хорошее начало и интересно получить эти функции в Rails.