Real time Websockets với Action Cable trong Rails 5
Những năm gần đây, khái niệm web thời gian thực được nhắc tới rất nhiều. Nếu quan sát các ứng dụng mạng xã hội, bạn sẽ thấy các bài viết, thông báo, hay khi chat, bạn nhận được thông tin mới rất ảo diệu. Để thực hiện công việc đó, chúng ta có thể sử dụng phương thức HTTP, thực hiện request để lấy ...
Những năm gần đây, khái niệm web thời gian thực được nhắc tới rất nhiều. Nếu quan sát các ứng dụng mạng xã hội, bạn sẽ thấy các bài viết, thông báo, hay khi chat, bạn nhận được thông tin mới rất ảo diệu.
Để thực hiện công việc đó, chúng ta có thể sử dụng phương thức HTTP, thực hiện request để lấy data. Trong khi, real-time web, thì không hể ràng buộc việc đó, nó cho phép người dụng nhận thông tin mới từ máy chủ ngay khi nó xuất hiện - không cần thực hiện request nào.
Có rất nhiều công nghệ để thực hiện chức năng thời gian thực như vậy, nhưng WebSocket protocol nổi lên như một công nghệ nổi bật từ khi được phát triển từ năm 2009. Tuy nhiên, tới tận thời gian gần đây, việc thực thi WebSocket protocol trong Rails rất khó. Chúng ta cần sử dụng các thư viện thứ 3 như Faye hay sử dụng thư viện Javascript. Hãy cùng tìm hiểu về WebSocket và cách mà Rails 5 hỗ trợ real-time với Action Cable
WebSockets là gì?
WebSockets là phương thức được xây dựng trên TCP. Chúng duy trì kết nối tới máy chủ, nhờ đó mà máy chủ có thể gửi các thông tin tới máy khách, kể cả trong khi không có yêu cầu từ máy khách.
Với sự hỗ trợ của Action Cable trong Rails 5, chúng ta có thể thực thi WebSockets theo chuẩn thiết kể của Rails.
Giới thiệu về Action Cable
Trong docs, nó được giới thiệu là "full-stack offering": Nó cung cấp cả client-side JavaScript framework, và Ruby server-side framework. Vì nó gắn liền với Rails, nên chúng ta sẽ phải truy cập tới các models từ trong các WebSocket workers
Vậy làm sao mà Action Cable có thể tạo và duy trì kết nối WebSocket bên trong một ứng dụng Rails 5?
Action Cable có thể chạy như một server riêng rẽ, hoặc chúng ta có thể thiết lập để nó chạy trên bên trong server của ứng dụng Rails.
Action Cable thông qua Rack socket để quản lý các kết nối tới server, đa luồng, tạo nên các kênh kết nối. Với mối kênh kết nối tới sub-URI của ứng dụng để truyền dữ liệu từ những vùng nhất định của dự án tới các vùng khác.
Action Cable cung cấp code để truyền dữ liệu của những nội dung nhất định ( tin nhắn, thông báo ... ) thông qua kênh truyền, tới subscriber ( vùng đang kết nối ). Subscriber được khởi tạo phía client với một đoạn code Javascript, sử dụng Jquery để thêm nội dung vào DOM.
Cuối cùng, Action Cable sử dụng Redis để lưu trữ các dữ liệu tạm thời, đồng bộ dự liệu giữa các phần của dự án.
Tiếp theo, chúng ta sẽ xây dựng một ứng dụng chat cơ bản trong Rails 5.
Ứng dụng cho phép người dùng có thể đăng kí, đăng nhập, tạo một chat room hoặc chọn một chat room đã có sẵn và bắt đầu gửi tin nhắn. Chúng ta sẽ sử dụng Action Cable để chắc chắn rằng: Bất kì người dùng nào trong chat room đều sẽ nhận được tin nhắn trong phòng chat bất cứ khi nào có tin nhắn mới mà không cần tải lại trang web.
rails new action-cable-example -d mysql
- Đầu tiên, các bạn cần cài đặt Ruby 2.3.0 và Rails 5.0
- Khởi tạo dự án
- Add gem redis và gem puma
gem "redis", "~> 3.0" gem "puma"
- Then bundle install
Domain Model
Chúng ta sẽ có users, chat rooms và messages. Chat room sẽ có topic và nhiều messages, message sẽ có content, và thuộc về user và một chatroom nào đó. User sẽ có username, và sẽ có nhiều messages
Chúng ta sẽ lược bớt quá trình tạo model, routes, controllers cơ bản trong tutorial này.
Đối với chatroom controller, chúng ta sẽ cần một action show
# app/controllers/chatrooms_controller.rb class ChatroomsController < ApplicationController ... def show @chatroom = Chatroom.find_by slug: params[:slug] @@message = Message.new end end
Chatroom view sẽ render partial messages
# app/views/chatrooms/show.html.erb <div class="row col-md-8 col-md-offset-2"> <h1><%= @chatroom.topic %></h1> <div class="panel panel-default"> <% if @chatroom.messages.any? %> <div class="panel-body" id="messages"> <%= render partial: "messages/message", collection: @chatroom.messages%> </div> <% else %> <div class="panel-body hidden" id="messages"> </div> <% end %> </div> <%= render: "messages/message_form" %> </div>
Chúng ta cần 1 form để tạo message mới
# app/views/messages/_message_form.html.erb <%=form_for message, remote: true, authenticity_token: true do |f| %> <%= f.label :your_message %>: <%= f.text_area :content, class: "form-control", data: {textarea: "message"} %> <%= f.hidden_field :chatroom_id, value: chatroom.id %> <%= f.submit "send", class: "btn btn-primary", style: "display: none", data: {send: "message"} %> <% end %>
Chúng ta sẽ cần thêm action để tạo message
class MessagesController < ApplicationController def create message = Message.new message_params message.user = current_user if message.save # do some stuff else redirect_to chatrooms_path end end private def message_params params.require(:message).permit :content, :chatroom_id end end
Tạo Action Cable
Khi khởi tạo một ứng dụng Rails 5 mới, chúng ta sẽ có:
├── app ├── channels ├── application_cable ├── channel.rb └── connection.rb
Module ApplicationCable có class Channel và class Connection đã được định nghĩa
Connection class sẽ dùng để xác thực các kết nối — ví dụ, for example, thiết lập một kênh kết nối tới inbox của người dung, sẽ yêu cầu xác thực người dùng.
module ApplicationCable class Connection < ActionCable::Connection::Base end end
The Channel class sẽ dùng để định nghĩa logic được chia sẻ giữa các kênh mà chúng ta định nghĩa.
# app/channels/channel.rb module ApplicationCable class Channel < ActionCable::Channel::Base end end
Thiết lập kết nối WebSocket
Step 1. Server-Side
Đầu tiên, chúng ta cần trỏ Action Cable server lên một sub-URI Trong routes.rb:
Rails.application.routes.draw do # Serve websocket cable requests in-process mount ActionCable.server: "/cable" resources :chatrooms, param: :slug resources :messages ... end
Như vậy, Action Cable sẽ lắng nghe các yêu cầu WebSocket tại địa chỉ ws://localhost:3000/cable.
Tiếp theo, chúng ta cần tạo kết nối WebSocket từ client, gọi là consumer.
Step 2: Thiết lập kết nối Socket : Client-Side
app/assets/javascripts/channels we'll create a file: chatrooms.js. Here is where we will define the client-side instance of our WebSocket connection.
Định nghĩa một instance kết nối WebSocket client
// app/assets/javascripts/channels/chatrooms.js //= require cable //= require_self //= require_tree . this.App = {}; App.cable = ActionCable.createConsumer();
Note: Hãy chắc chắn bạn có require thư mục channels trong asset pipeline:
// app/assets/javascripts/application.js //= require_tree ./channels
Để chỉ định cho consumer sẽ kết nối tới đâu, chúng ta sẽ set socket URIs trong environment files, và truyền nó thông qua action_cable_meta_tag.
# config/development.rb Rails.application.configure do config.action_cable.url = "ws://localhost:3000/cable" end
# app/vippews/layouts/application.html.erb <%= action_cable_meta_tag %>
Xây dựng kênh
Chúng ta đã sử dụng Action Cable để tạo thành công một connection, đón nhận bất kì yêu cầu WebSocket nào tại địa chỉ ws://localhost:3000/cable. Nhưng như vậy chưa đủ để tạo chức năng gửi tin nhắn real-time. Chúng ta cần định nghĩa cụ thể kênh gửi nhận tin nhắn và hướng dẫn các thành phần khác của ứng dụng truyền - nhận dữ liệu từ kênh này.
Step 1: Định nghĩa Kênh truyền
Định nghĩa một kênh truyền với Action Cable rất dễ dàng. Chúng ta sẽ tạo ra một file app/channels/messages_channel.
# app/channels/messages_channel.rb class MessagesChannel < ApplicationCable::Channel end
Kênh truyền tin tức này sẽ chỉ cần một function, cho một mục đích duy nhất, là #subscribed.
# app/channels/messages_channel.rb class MessagesChannel < ApplicationCable::Channel def subscribed stream_from 'messages' end end
Step 2: Truyền thông tin tới kênh
Ngay khi có một tin nhắn được tạo, chúng ta cần truyền nó tới kênh customer.
# app/controllers/messages_controller.rb class MessagesController < ApplicationController def create message = Message.new message_params message.user = current_user if message.save ActionCable.server.broadcast "messages", message: message.content, user: message.user.username head :ok end end ... end
Chúng ta truyền một số tham biến sau vào method #broadcast của Action Cable server:
- "messages", Tên của channel
- Thông tin sẽ được truyền tới kênh dưới dạng JSON:
-
- message, nội dung của tin nhắn vừa được tạo.
-
- user, thiết lập giá trị bằng username của user vừa tạo.
Step 3: Áp dụng Redis cho Action Cable
Action Cable sử dụng Redis để gửi và nhận tin nhắn thông qua kênh đã tạo. Vì vậy, khi chúng ta 'nói' với Action Cable server #broadcast tới "messages" channel, chúng ta thực ra đang "gửi những tin nhắn mới tới kênh "messages" được duy trì bởi Redis."
Cùng lúc đó, #subscribed trong Messages Channel đang streaming các tin nhắn được gửi thông qua kênh "messages" được duy trì bởi Redis.
Vậy, Redis đóng vai trò như một vùng lưu trữ dữ liệu và đảm bảo rằng các tin nhắn sẽ được bảo vệ và cập nhật qua 'vùng' của dự án
Action Cable sẽ tìm kiếm cấu hình Redis trong Rails.root.join("config/cable.yml").
production: adapter: redis url: redis://localhost:6379/1 development: adapter: async test: adapter: async
Định nghĩa Subscriber của kênh Trước đó, chúng ta đã tạo ra consumer
// app/assets/javascripts/channels/chatrooms.js this.App = {}; App.cable = ActionCable.createConsumer();
consumer là đầu phía client của kết nối WebSocket. Bây giờ chúng ta sẽ tạo ra cơ chế đón thông tin cho consumer.
// app/assets/javascripts/channels/messages.js App.messages = App.cable.subscriptions.create('MessagesChannel', { received: function(data) { $("#messages").removeClass('hidden') return $('#messages').append(this.renderMessage(data)); }, renderMessage: function(data) { return "<p> <b>" + data.user + ": </b>" + data.message + "</p>"; } });
Sơ đồ hoạt động
Kết luận
Trên đây là bài dịch từ nguồn tham khảo: https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable#building-the-channel Hy vọng kiến thức này có thể giúp ích cho bạn.