Streaming trong RAILS 4
Bài đăng này đã không được cập nhật trong 7 năm
Streaming là gì?
Streaming đã được sử dụng trong Rails từ phiên bản 3.2 tuy nhiên nó bị giới hạn chỉ sử dụng streaming template
. Rail 4 mạnh mẽ hơn và khả năng streaming thời gian thực. Có nghĩa là Rail từ giờ có khả năng xử lý các đối tượng I/O nguyên bản cùng khả năng gửi dữ liệu tới client theo thời gian thực.
Streaming
và Live
là 2 modun riêng biệt được viết trong ActionController
. Streaming
là mặc định trong khi Live
cần được định nghĩa trong controller.
Streaming API sử dụng class Fiber
của Ruby
(có từ 1.9.2). Fiber
cung cấp các khối thread được xây dựng sẵn trong Ruby
. Fiber
gọi các thread và điều khiển chúng dừng hoặc tiếp tục.
Template Streaming
Streaming sẽ đảo ngược quá trình render layout và template của Rails thông thường. Mặc định, Rails sẽ render template đầu tiên và sau đó mới đến layout. yeild
sẽ được chạy đầu tiên, sau đó load lên template, sau đó assets và layout được render.
Hãy xem xét ví dụ sau, hiển thị timeline của nhiều class:
class TimelineController
def index
@users = User.all
@tickets = Ticket.all
@attachments = Attachment.all
end
end
Ở đây, sử dụng streaming
khả phù hợp vì nếu tải bình thường, trang này sẽ mất rất nhiều thời gian để lấy tất cả dữ liệu trước.
Cách áp dụng streaming
:
class TimelineController
def index
@users = User.all
@tickets = Ticket.all
@attachments = Attachment.all
render stream: true
end
end
Khi sử dụng render stream: true
, Rails sẽ load query từ từ và cho phép chúng chạy sau khi assets và layout được render xong. Nhưng streaming chỉ làm việc với template và không sử dụng được với các dạng khác như json
hoặc xml
.
Passing the Stuff in Between
Streaming
thay đổi cách thức render template và layout, điều này gây ra một issue. Việc gọi database sẽ không được thực hiện khi template được render xong, các logic và view sử dụng biến instance sẽ bị lỗi.
Vì thế, để tải các thuộc tính title
hoặc meta
, chúng ta cần sử dụng content_for
thay cho yeild
. Tất nhiên với body
thì ta vẫn có thể sử dụng yeild
.
Going Live với Live API
Live
là một modun đặc biệt trong ActionController
. Nó cho phép Rails mở và đóng stream. Hãy cùng tạo một ứng dụng đơn giản sử dụng Live
.
Bình thường trên local, Rails sẽ sử dụng WEBrick
nhưng WEBrick
sẽ không làm việc với live streaming vì thế chúng ta sẽ sử dụng Puma.
Gemfile
gem "puma"
:~/testapp$ bundle install
Puma
tích hợp vào Rails vì thế ta có thể sử dụng rails s
để khởi động server, nó có cùng port với 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
Giờ ta sẽ tạo controller để send messages
:~/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
#routes.rb
get 'messaging' => 'messaging#send_message'
Ta sẽ access vào bằng curl như sau:
~/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
Khi chúng ta gọi send_method
, Puma
khởi tạo 1 thread mới và xử lý data streaming cho 1 cliend trong thread này. Mặc định Puma
cho phép 16 thread đồng thời, tức là 16 clients đồng thời (config này có thể thay đổi).
Thay đổi 1 chút, chúng ta sẽ build 1 form và send data ra view:
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
#views/messages/index.html.erb
<%= form_tag messaging_path, :method => 'get' do %>
<%= text_field_tag :message, params[:message] %>
<%= submit_tag "Post Message" %>
<% end %>
#routes
root 'messaging#index'
get 'messaging' => 'messaging#send_message', :as => 'messaging'
Ngay khi bạn nhập message và ấn Post Message
, browser sẽ nhận response stream như một text file để download chứa message được ghi 10 lần.
Ở đây, tất nhiên stream không biết nơi gửi data hoặc format là gì, do đó nó viết vào text file trên server.
Bạn có thể kiểm tra bằng cách gửi param:
:~/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
Server Side Events (SSEs)
HTML5
có một method gọi là Server Side Events (SSEs). Nó là một method có sẵn trên browver, sẽ xác nhận và thực hiện event bất cứ khi nào server gửi data.
Bạn có thể sử dụng SSE
kết hợp với LIVE API để thực hiện giao tiếp full-duplex
.
Mặc định, Rails cung cấp một tiến trình giao tiếp một chiều bằng cách viết các stream tới client khi data sẵn sàng. Tuy nhiên, nếu thêm SSEs
, chúng ta có thể kích hoạt các event và response hoạt động 2 chiều.
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
Modun này gán object stream I/O thành một hash và chuyển nó thành cặp key-value
vì thế sẽ rất dễ dàng để đọc, lưu và gửi nó thành định dạng JSON.
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
Chúng ta có response như sau:
:~/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"}
Những điểm cần lưu ý
- Tất cả stream phải được closed nếu không nó sẽ mở mãi mãi.
- Bạn phải chắc chắn code của mình là
threadsage
, như controller luôn phải tạo ra một thread mới khi method được gọi. - Sau commit đầu tiên của response,
header
không thể được ghi lại vớiwrite
hoặcclose
Tham khảo
All rights reserved