Streaming trong RAILS 4
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 ...
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ới write hoặc close
Tham khảo
https://www.sitepoint.com/streaming-with-rails-4/