Rails Request/Response Cycle
Minh / 22 Apr 2019Request: Browser -> Nginx -> Puma/Unicorn -> Rack (Middleware) -> Routing -> Controller (-> Model) -> Views -> Controller (response được tạo ra) Response: Controller -> Routing -> Rack (Middleware) -> Puma/Unicorn -> nginx -> Browser
Tại sao cần cả Nginx và Puma/Unicorn
- Nginx gọi là web server. Puma/Unicorn gọi là Application server
- Tầng nginx thường sử lý để redirect
- Nginx viết bằng C nên rất nhanh
- Những request động cần vào server thì redirect request vào Puma/Unicorn
- Những request tĩnh, ví dụ 404.html, 500.html, images tĩnh v.v, /public (phân biệt với /public ở Rails app) mà không cần khởi động application server (giả sử App server chết thì nginx vẫn trả về trang 500.html cho người dùng được)
- http redirects to https
- Xử lý multi threads, bundle nhiều request vào file gửi cho Puma/Unicorn hiệu quả về performance hơn
- nginx xử lý DDos attack
- Tầng Puma/Unicorn
- App server là thứ thật sự “chạy” app của bạn, chạy ở đây tức là load code và lưu nó ở trong memory.
- Trong khi nginx không cần thiết ở môi trường development, application server bắt buộc ở cả môi trường dev/prod
Rack là gì
Tại sao cần Rack
Nói 1 cách đơn giản, Rack là bridge giữa App server và Rails app. Vì mỗi app server có thể nói 1 ngôn ngữ khác nhau. Nên cần 1 thằng ở giữa để dịch.
Ví dụ:
Puma gửi request object (Array) -> Rack -> Rails -> Rack nhận response object, parse thành Array -> Puma
Unicorn gửi request object (Hash) -> Rack -> Rails -> Rack nhận response object, parse thành Hash -> Unicorn
Viết 1 Rack app đơn giản
class ComplimentApp
def call(request)
[200, {'Content-Type' => 'text/plain'}, ["I like your shoes"]]
end
end
- Rack app bắt buộc phải có method
call
- ở ví dụ này return 1
Array
có 3 phần từ (phù hợp với format của Puma, mỗi App server có những quy định riêng về kiểu trả về, format của kiểu trả về).- Puma quy định phần tử thứ nhất là HTTP status code
- Phần tử thứ 2 là 1 hash, giá trị của HTTP headers
- Phần tử thứ 3 là 1 object
response_to
methodeach
, chính làbody
của response
Rack endpoint
Endpoint (đích đến) của Rack, tức là Rails Application được định nghĩa ở file config.ru
require_relative 'config/environment'
run Rails.application
Rails.application
return instance của Rails App class, Rails App class lại được định nghĩa ở application.rb
module PLServer
class Application < Rails::Application
end
end
trường hợp này là return instance của PLServer::Application
class. PLServer::Application
kế thừa từ Rails::Application
, một rack App. Rails::Application
implement phương thức #call
(nói ở trên)
def call(env)
req = build_request env
app.call req.env
end
env ở đây không phải biến môi trường ENV, nó chính là request object
Rack Middleware
Là một thành phần trong Rack. Có 2 nhiệm vụ:
- Sửa env object trước khi được pass vào endpoint tiếp theo
- Sửa response object trước được pass vào endpoint tiếp theo
Sau khi request đi qua khoảng 200 middleware objects, nó sẽ vào Router
Routing
router dispatch request vào controller và action tương ứng ở bước này thì đã có 1 empty response object được tạo ra rồi
controller_class.dispatch(action, request, response)
Controllers
- chạy before_action
- logic trong action
- view rendering, stored in
response.body
, - after_action
Rời controllers
- chạy after_action
response.to_a
convert response object thành một array mà Rack hiểu được
Rời routing
Routing dơn giản là sẽ pass response của controller vào Middleware, trừ khi response.headers
include X-Cascade: pass
. Khi đó Routing sẽ dispatch response object này sang một App khác chẳng hạn,
Vì dụ NodeJS app
Rời Middleware
Middleware có thể sửa status code, headers, body ở phase này. Ví dụ:
- xóa toàn bộ response body nếu có
HTTP caching headers
- set/edit cookie in response header
Rails as backend, JS framework as frontend
Gỉa sử ta có 2 server, 1 server chạy Rails, serve APIs, một server chạy JS Framwork như là React, VueJS, v.v,
Rails app: api.example.com, Front-end app: front.example.com
Tại frontend server, phải fetch dữ liệu từ backend server thông qua API. Khi đó ta sẽ gặp phải vấn đề Cross-Origin Resource Sharing (CORS)
Để giải quyết vấn đề này, đơn giản nhất là dùng gem:
gem "rack-cors", require: "rack/cors"
Bản chất của gem này là insert một middleware vào đầu tiên trong danh sách middleware stack để modify header của các request từ JS front server.
module MyApp
class Application < Rails::Application
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins 'front.example.com'
resource '/api/v1',
:headers => :any,
:expose => ['X-User-Authentication-Token', 'X-User-Id'],
:methods => [:get, :post, :options, :patch, :delete]
end
end
end
end