内容简介:In today’s post, we’ll learn how to build a Rails app that can support multiple subdomains. Let’s assume that we have a gaming websiteWe’ll use Rails’ powerful routing constructs to support multiple subdomains in our application. We’ll also set up subdomai
In today’s post, we’ll learn how to build a Rails app that can support multiple
subdomains. Let’s assume that we have a gaming website funkygames.co
and we want to support multiple subdomains such as app.funkygames.co
, api.funkygames.co
, and dev.funkygames.co
with a single Rails application. We want to ensure that proper authentication is performed for all subdomains and that there are no duplicate routes.
We’ll use Rails’ powerful routing constructs to support multiple subdomains in our application. We’ll also set up subdomains locally and write tests for multiple subdomains.
Prerequisites
For the purpose of this post, I’m assuming that you have set up appropriate DNS records for all the subdomains to point to the Rails app. We’ll only deal with the Rails side of things in this post.
Handling Multiple Subdomains
Rails uses routes.rb
file to handle incoming requests and map them to
specific controller actions. In a trivial app, every mapping in routes.rb
maps a route to a controller action as follows:
get '/games/:id', to: 'games#show'
With this approach, all the endpoints defined in our routes.rb
file
are applicable to all the subdomains. So app.funkygames.co/games/1
as well as api.funkygames.co/games/1
will be handled by this route. However, we only want the request coming from app
subdomain to be handled by this route. The api
subdomain is only to be used for API routes. We’ll add some rules to the routes so that they are handled only if a specific rule is met for the incoming request.
Rails routing provides a constraints
helper method which can specify additional rules for the given route.
get '/games/:id', to: 'games#show', constraints: { subdomain: 'app' }
This will ensure that if the request is coming from app.funkygames.co/games/1
, it will be handled by GamesController's
show action. Any request from other subdomains apart from app
will not be handled by this route.
It will become very cumbersome to define constraints
like this for each and every route.
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' }
We can use the block form of the constraints
helper to define multiple
routes for a single subdomain.
constraints subdomain: 'app' do get '/games/:id', to: 'games#show' get '/games/list', to: 'games#list' post '/games/start', to: 'games#start' end
To define routes for multiple subdomains, we just have to add multiple constraints
blocks in our routes.rb
file.
constraints subdomain: 'app' do ... end constraints subdomain: 'api' do ... end constraints subdomain: 'dev' do ... end
Under the Hood
Rails routing provides request constraints and segment constraints. Segment constraints add rules on the request path whereas request constraints add conditions on the incoming request. The hash key in a request constraint needs to be a method on the Request
object that returns a string and the value needs to be the expected value.
constraints subdomain: 'app' do ... end
In the above case, we are using the subdomain
method on the Request
object and matching it with a string like app
, api
or dev
.
For more details, consult the Rails routing guide .
Handling Multi-level Subdomains
Let’s say we are using app.staging.funkygames.co
for our staging environment. If we have the setup above, we will quickly notice that all the requests that are supposed to hit the app
subdomain are returning a 404. If we debug things further, we will notice that our constraint for the subdomain is failing.
request.subdomain #=> app.staging
We expected the subdomain to return app
, but instead, it returns app.staging
. Of course, we want to solve this without adding environment-specific code! The parsing of request’s subdomain is managed by config.action_dispatch.tld_length
option. The default value of this configuration is 1, which basically supports one level of subdomains. As we have two level subdomains, we need to set the value for config.action_dispatch.tld_length
to 2.
# config/application.rb config.action_dispatch.tld_length = Integer(ENV['TLD_LENGTH'] || 1)
We can set it using an environment variable so that we can use the same code in the staging as well as in the production environment. Now, our routing setup will work for app.staging.funkygames.co
as well.
Session Management
Now that routes are defined to handle requests coming from multiple subdomains, we need to take care of authentication for all the subdomains. We can do this in two ways—we can either allow the same user session to be used across all subdomains, or we can have separate sessions for separate subdomains.
Authentication in a Nutshell
Rails uses cookies to store user session key by default. Once the user logs in, the user’s session information is stored in the session store of our choice and the session key is stored as a cookie in the browser. So the next time the user visits our website, the same session cookie is sent from the browser to the server and the server decides whether the user is logged in or not based on whether the session exists for the incoming session cookie.
The default configuration for the session looks like this in the Rails app:
Rails.application.config.session_store :cookie_store, key: "_funkygames_session"
The key _funkygames_session
will be used as the name of the session cookie and its value will be the session id.
Cookies Primer
By default, cookies are set by the browser on the request’s domain. So if we are hitting our application from app.funkygames.co
then the session cookie will be set against app.funkygames.co
. Each subdomain will set its own session cookies, therefore the user session will not be shared across subdomains by default.
Sharing Session between Different Subdomains
If we want to share the user session across subdomains, we’ll need to set the session cookie on the funkygames.co
domain itself so that all subdomains can access it. This can be achieved by passing the domain
option to the session store settings.
Rails.application.config.session_store :cookie_store, key: "_funkygames_session", domain: :all
By passing domain
as :all
, we are basically telling Rails to set the session cookie on the top-level domain of the application such as funkygames.co
instead of on the request host which may include the individual subdomains. Once we do this, the session can be shared between different subdomains.
We can also pass a list of domains to the domains
option in an array format to support multiple domains.
There is one more option that needs to be configured to properly set the cookies for all subdomains. It is the tld_length
option. When using domain: :all
, this option can specify how to parse the domain to interpret the TLD of the domain. In our case, for app.funkygames.co
, we should set tld_length
to 2 for Rails to interpret the TLD as funkygames.co
when setting up the cookies. So the final session store configuration for multiple subdomains looks like this:
Rails.application.config.session_store :cookie_store, key: "_funkygames_session", domain: :all, tld_length: 2
The tld_length
option from the session store is different from the config.action_dispatch.tld_length
discussed earlier.
Writing Tests for Multiple Subdomains
As the routes are subdomain specific, the request specs or integration tests result in 404 errors if the test request does not have a proper subdomain. Rails integration tests provide a host!
helper which can set the proper subdomain for all requests made within a test file.
# Configuring subdomain in Rails integration tests setup do host! 'dev.example.com' end # # Configuring subdomain in RSpec request specs before do host! 'dev.example.com' end
After this, the requests will be correctly routed to the controller actions as per subdomain routing in routes.rb
file.
Note that the domain does not matter here, only the proper subdomain based in the code we are testing matters.
Setting up Multiple Subdomains Locally for Development
There are multiple ways to set up subdomains locally. The simplest is editing the /etc/hosts
file.
127.0.0.1 dev.funkygames.local 127.0.0.1 app.funkygames.local 127.0.0.1 api.funkygames.local
This ensures that the subdomains setup will work in a local environment. We can also use tools such as pow for managing subdomains locally.
Gotchas with Constraints Based Subdomain Routing
Though the constraints based subdomain routing works in most cases, it can be a pain in certain situations.
Dealing with External APIs
When we are working with third-party APIs and building integrations, the local development TLDs such as .local
or .dev
are not allowed. We have to use tools such as ngrok
. The subdomain based routing does not work in such cases and we have to whitelist certain routes so that they are accessible via ngrok as well.
Routes Outside of Subdomains Constraints
Certain routes can’t be placed inside the subdomain constraints. A typical example is healthcheck
or ping
endpoints. If we are using a load balancer in front of our Rails app, the load balancer needs to periodically check if the app is up or not. The healthcheck
endpoint used in such cases can’t be under subdomain constraints as the load balancer most probably won’t have knowledge of the request host.
Absence of root Route
Rails has a special root
route which is basically the default route of the application. If none of the other routes are matched with the request, then the root
route is used. When we have all of our routes under any one of the subdomains, then there can be situations where we don’t have any root
route defined at all.
Certain gems might depend on the presence of a root
route and we need to add checks and balances accordingly.
Conclusion
In this post, we set up a Rails app with multiple subdomains with very few lines of configuration. We also saw how to set up the subdomains locally as well as with different environments, with tips on writing effective tests for multiple subdomains. With the plumbing provided by Rails, it becomes easy to set up and test a Rails app with multiple subdomains.
P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post !
Guest author Prathamesh Sonpatki is a developer working in Ruby and Ruby on Rails. He also co-organizes RubyConfIndia and DeccanRubyConf
以上所述就是小编给大家介绍的《Building a Rails App With Multiple Subdomains》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SEM修炼手册:百度竞价、信息流广告、数据分析与专题页策划实战详解
陈丰洲 / 电子工业出版社 / 2018-10 / 59.80元
SEM人员在职场打拼的过程中,会遇到一个又一个坑,《SEM修炼手册:百度竞价、信息流广告、数据分析与专题页策划实战详解》尝试站在一定的高度,将从业者从专员走向管理岗位过程中可能碰到的问题进行整理,不仅谈竞价推广,也谈基于SEM的营销体系。 《SEM修炼手册:百度竞价、信息流广告、数据分析与专题页策划实战详解》包括11章内容,由浅入深地分享SEM的进阶过程。第1章是SEM概述,让读者对SEM有......一起来看看 《SEM修炼手册:百度竞价、信息流广告、数据分析与专题页策划实战详解》 这本书的介绍吧!