Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Xây dựng Ứng dụng Rails với Nhiều Subdomain: Hướng dẫn Toàn diện

Trong bài viết này, chúng ta sẽ học cách xây dựng một ứng dụng Rails hỗ trợ nhiều subdomain. Giả sử chúng ta có trang web game funkygames.co và muốn hỗ trợ các subdomain như app.funkygames.co, api.funkygames.co và dev.funkygames.co trong một ứng dụng Rails duy nhất. Chúng ta cần đảm bảo xác thực đúng cho tất cả subdomain và không có route trùng lặp.

Chúng ta sẽ sử dụng các cấu trúc routing mạnh mẽ của Rails để hỗ trợ nhiều subdomain. Cũng sẽ thiết lập subdomain cục bộ và viết test cho nhiều subdomain.

Điều kiện tiên quyết

Đối với bài viết này, tôi giả định bạn đã thiết lập bản ghi DNS phù hợp cho tất cả subdomain trỏ đến ứng dụng Rails. Chúng ta chỉ tập trung vào phía Rails.

Xử lý Nhiều Subdomain

Rails sử dụng file routes.rb để xử lý request đến và ánh xạ chúng tới action controller cụ thể. Trong ứng dụng đơn giản, mọi mapping trong routes.rb ánh xạ route tới action controller như sau:

get '/games/:id', to: 'games#show'

Với cách này, tất cả endpoint định nghĩa trong routes.rb áp dụng cho mọi subdomain. Vì vậy app.funkygames.co/games/1 và api.funkygames.co/games/1 đều được xử lý bởi route này. Tuy nhiên, chúng ta chỉ muốn request từ subdomain app được xử lý bởi route này. Subdomain api chỉ dùng cho route API. Chúng ta sẽ thêm quy tắc vào routes để chúng chỉ được xử lý khi quy tắc cụ thể cho request đến được thỏa mãn.

Routing Rails cung cấp helper method constraints để chỉ định quy tắc bổ sung cho route.

get '/games/:id', to: 'games#show', constraints: { subdomain: 'app' }

Điều này đảm bảo nếu request đến từ app.funkygames.co/games/1, nó sẽ được xử lý bởi action show của GamesController. Request từ subdomain khác ngoài app sẽ không được xử lý bởi route này.

Việc định nghĩa constraints như này cho từng route sẽ trở nên rườm rà.

get '/games/:id', to: 'games#show', constraints: { subdomain: 'app' }
get '/games/list', to: 'games#list', constraints: { subdomain: 'app' }
post '/games/start', to: 'games#start', constraints: { subdomain: 'app' }

Chúng ta có thể dùng dạng block của helper constraints để định nghĩa nhiều route cho một subdomain.

constraints subdomain: 'app' do
  get '/games/:id', to: 'games#show'
  get '/games/list', to: 'games#list'
  post '/games/start', to: 'games#start'
end

Để định nghĩa route cho nhiều subdomain, chỉ cần thêm nhiều block constraints trong routes.rb.

constraints subdomain: 'app' do ... end
constraints subdomain: 'api' do ... end
constraints subdomain: 'dev' do ... end

Cơ chế bên dưới

Routing Rails cung cấp request constraints và segment constraints. Segment constraints thêm quy tắc trên đường dẫn request, còn request constraints thêm điều kiện trên request đến. Key trong request constraint phải là method của đối tượng Request trả về string, và value là giá trị mong đợi.

constraints subdomain: 'app' do ... end

Ở trên, chúng ta dùng method subdomain trên đối tượng Request và so khớp với string như app, api hoặc dev.

Xem thêm Rails routing guide để biết chi tiết.

Xử lý Subdomain Đa cấp

Giả sử dùng app.staging.funkygames.co cho môi trường staging. Với thiết lập trên, request đến subdomain app sẽ trả về 404. Debug sẽ thấy constraint subdomain thất bại.

request.subdomain #=> app.staging

Chúng ta mong đợi subdomain trả về app, nhưng nó trả về app.staging. Việc parse subdomain được quản lý bởi option config.action_dispatch.tld_length. Giá trị mặc định là 1, chỉ hỗ trợ một cấp subdomain. Với subdomain hai cấp, cần set giá trị là 2.

# config/application.rb
config.action_dispatch.tld_length = Integer(ENV['TLD_LENGTH'] || 1)

Có thể set qua biến môi trường để dùng chung code cho staging và production. Bây giờ routing sẽ hoạt động với app.staging.funkygames.co.

Quản lý Session

Sau khi định nghĩa route cho nhiều subdomain, cần lo xác thực cho tất cả. Có hai cách: cho phép cùng session user trên tất cả subdomain, hoặc session riêng cho từng subdomain.

Xác thực trong gist

Rails mặc định dùng cookie lưu session key. Sau khi user đăng nhập, thông tin session lưu trong session store và session key lưu cookie trên trình duyệt. Lần truy cập sau, cookie session gửi lên server, server kiểm tra session tồn tại hay không.

Cấu hình session mặc định:

Rails.application.config.session_store :cookie_store, key: "_funkygames_session"

Key _funkygames_session là tên cookie session, value là session id.

Cơ bản về Cookie

Mặc định cookie được set trên domain của request. Nếu truy cập từ app.funkygames.co, session cookie set cho app.funkygames.co. Mỗi subdomain set cookie riêng, do đó session user không chia sẻ giữa subdomain mặc định.

Chia sẻ Session giữa Subdomain

Để chia sẻ session, cần set session cookie trên domain funkygames.co để mọi subdomain truy cập được. Thực hiện bằng option domain trong session store.

Rails.application.config.session_store :cookie_store, key: "_funkygames_session", domain: :all

Truyền domain: :all báo cho Rails set cookie trên top-level domain (funkygames.co) thay vì request host chứa subdomain. Khi đó session chia sẻ giữa subdomain.

Có thể truyền danh sách domain cho option domains dưới dạng mảng để hỗ trợ nhiều domain.

Cần thêm option tld_length để set cookie đúng cho tất cả subdomain. Khi dùng domain: :all, option này chỉ định cách parse domain để hiểu TLD. Với app.funkygames.co, set tld_length = 2 để Rails hiểu TLD là funkygames.co. Cấu hình cuối cùng:

Rails.application.config.session_store :cookie_store, key: "_funkygames_session", domain: :all, tld_length: 2

Option tld_length của session store khác với config.action_dispatch.tld_length trước đó.

Viết Test cho Nhiều Subdomain

Vì route phụ thuộc subdomain, request spec/integration test sẽ lỗi 404 nếu test request không có subdomain đúng. Rails integration test cung cấp helper host! để set subdomain cho mọi request trong file test.

# Cấu hình subdomain trong Rails integration tests
setup do
  host! 'dev.example.com'
end

# Cấu hình subdomain trong RSpec request specs
before do
  host! 'dev.example.com'
end

Sau đó request sẽ được route đúng theo subdomain routing trong routes.rb. Lưu ý domain không quan trọng, chỉ subdomain đúng trong code test quan trọng.

Thiết lập Nhiều Subdomain Cục bộ cho Development

Có nhiều cách thiết lập subdomain local. Cách đơn giản là chỉnh file /etc/hosts.

127.0.0.1 dev.funkygames.local
127.0.0.1 app.funkygames.local
127.0.0.1 api.funkygames.local

Đảm bảo subdomain hoạt động local. Có thể dùng tool như pow để quản lý subdomain local.

Các Cạm bẫy với Constraint-based Subdomain Routing

Mặc dù constraint-based routing hoạt động hầu hết trường hợp, nhưng gây khó khăn trong một số tình huống.

Xử lý API Bên ngoài

Khi tích hợp API thứ ba, TLD local như .local hoặc .dev không được phép. Phải dùng tool như ngrok. Subdomain routing không hoạt động, cần whitelist một số route để truy cập qua ngrok.

Route Nằm Ngoài Subdomain Constraints

Một số route không thể đặt trong subdomain constraints. Ví dụ endpoint healthcheck hoặc ping. Nếu dùng load balancer trước Rails app, load balancer cần kiểm tra app lên/đồng đều. Endpoint healthcheck không thể dưới subdomain constraints vì load balancer thường không biết request host.

Thiếu Root Route

Rails có route root đặc biệt - route mặc định. Nếu không route nào khớp, root được dùng. Khi mọi route đều dưới subdomain, có thể không có root route nào. Một số gem phụ thuộc vào sự tồn tại của root route, cần thêm kiểm tra.

Kết luận

Trong bài này, chúng ta thiết lập Rails app nhiều subdomain với ít cấu hình. Cũng thấy cách setup subdomain local và các môi trường khác nhau, cùng mẹo viết test hiệu quả. Với hạ tầng Rails cung cấp, việc setup và test app nhiều subdomain trở nên dễ dàng.

P.S. Nếu bạn muốn đọc bài Ruby Magic ngay khi xuất bản, hãy đăng ký newsletter Ruby Magic để không bỏ lỡ bài nào!