開発しているサービスの中で、ユーザにサブドメインが切られたページを提供したいなと思うことがあっていろいろ調べたけれど、あまり情報がなくて困ったので書いた。
よくある dev.example.com
や admin.example.com
のような事前に決めうちされたサブドメインにそれぞれ別の環境を用意するものではなく、例えば
user1.example.com
user2.example.com
user3.example.com ...
のように、ユーザの数だけサブドメインが発行されるものを想定している。
example.com
の部分はご自身のドメインに置き換えてください。
前提
- Ruby 2.2.0
- Rails 4.2.0
- devise 3.4.1
- Unicorn 4.8.3
- Nginx 1.6.2
- EC2 Amazon Linux AMI 2015.09.1 (HVM)
やること
-
- サーバ側のレコード設定でサブドメインのAレコードを設定する
-
- ローカル環境でサブドメインのページを表示させる
-
- Rails側でサブドメインを受け取りuser#showにルーティングする
-
- 補足:サブドメイン<->メインドメイン間でcookieを共有する
-
- 補足:サブドメイン<->メインドメイン間をリンクで遷移する
1. サーバ側のレコード設定でサブドメインのAレコードを設定する
まずやらなければいけないのは user1.example.com
で example.com
で同じhostの同じページを表示できるようにすること。user1の部分はこの時点ではどんな文字列でも問題ない。
これを実現するためにサーバのAレコードを変更して、サブドメインでリクエストされた場合にメインドメインと同じhostを返してあげる。AWSで運用しているのでRoute53を使った。さくらVPSなどでももちろん大丈夫。
現在稼働しているHosted zonesを選択し、Record Setの追加を行う。Create Record Set より、以下のように設定する。
- Name: *.example.com.
- Type: A - IPv4 Address
- Alias: Yes or No
- TTL(Seconds): 300
- Value: xx.xx.xx.xx
Nameにワイルドカード使用してすべてのサブドメインに対してRecordをセットしておく。こうしておくことで user1.example.com
でアクセスされた場合も user2.example.com
でアクセスされた場合も同じRecord Setを適応できる。AliasはYesにしてELBをはさんだ場合もNoにして特定のインスタンスを指定した場合も、メインドメインのAレコードと合わせるようにする。TTLは3600とかに設定してしまうと反映に1時間かかったりするのでひとまず300とかにしておく。
5分ほど時間をおいた後、digコマンドでレコードが正常に反映されているか確認する。メインドメインとサブドメインのAレコードが同じになっていればok。さらに、ブラウザからも確認しておく。
$ dig example.com
;; ANSWER SECTION:
example.com. 300 IN A xx.xx.xx.xx
$ dig user1.example.com
;; ANSWER SECTION:
user1.example.com. 300 IN A xx.xx.xx.xx
2. ローカル環境でサブドメインのページを表示させる
ローカル環境にてサブドメインでアクセスされた場合にトップページ(root_path)を表示するようにする。とりあえず確認するだけであれば /etc/hosts
にずらずらと書き連ねても良いが増えてくるととてもつらい。
127.0.0.1 localhost example.com user1.example.com user2.example.com user3.example.com
メインドメインでアクセスしてもサブドメインでアクセスしても 127.0.0.1
に返してくれるドメイン(サービス)があるのでそれをつかう。ループバックドメインと呼ばれるもので、localtest.me / devd.io / loopback.jp などがあるが、比較的よく知られている、lvh.meをつかった。ちなみに、サブドメインでのアクセスを 127.0.0.1
に返してあげるだけのシンプル設計なので自作してもよい。
lvh.me:3000 / user1.lvh.me:3000 でroot_pathが表示されることを確認する。
3. Rails側でサブドメインを受け取りuser#showにルーティングする
ユーザページ(user#show)を example.com/user1
-> user1.example.com
で表示できるように変更する。Railsではサブドメインの値(request.subdomain)など、require
でリクエストを送ってきたユーザのヘッダー情報や環境変数を受け取ることができる。
参考:request - リファレンス - - Railsドキュメント
www.example.com
がサブドメインとして認識されてしまうため、libにwwwをはじくためのclassを用意しておく。
class Subdomain
def self.matches?(request)
# wwwがサブドメインとして認識されてしまうのを防ぐ
request.subdomain.present? && request.subdomain != 'www'
end
end
routesに以下の記述を追加し、user1.example.com
でアクセスされた際にusers#showで処理するようにしておく。
require Rails.root.join('lib', 'subdomain.rb')
constraints Subdomain do
get 'user', to: 'users#show', path: '/'
end
後は、controller側で request.subdomain の有無で分岐してあげればよい。
class UsersController < ApplicationController
def show
if request.subdomain.blank?
@user = User.find_by!(username: params[:username])
else
@user = User.find_by!(username: request.subdomain)
end
end
end
lvh.me:3000 / user1.lvh.me:3000 でそれぞれ、user1のユーザページが呼ばれるようになっていればok。ここまでできれいれば、後はdeployして正しくルーティングされるか確認するだけ。
4. 補足:サブドメイン<->メインドメイン間でcookieを共有する
Rails側では別hostとして認識されているため、「サブドメインでuser1としてサインインしていても、メインドメインではサインインできていない」という場合がある。ユーザの認証管理にdeviseをつかっている場合は、解決しないとだめだった。具体的には、session_store.rb に以下の記述を追加しておく。key:
には各自のアプリケーション名に応じて変える。
Rails.application.config.session_store :cookie_store, key: app_key, domain: :all
cookieがキャッシュに残ったままになっていて反映されない場合は、ブラウザの履歴からcookieを削除してあげると解消するはず。
5. 補足:サブドメイン<->メインドメイン間をリンクで遷移する
サブドメインのページからメインドメインのページにリンクで遷移する際、baseタグに特に記述がなければ、サブドメインのページを起点とした相対パスになる。この状態でlink_toヘルパーなどをつかってroot_pathを指定したとしても、サブドメインのroot_pathに遷移されてしまうため少々不便。リンクに引数を持たせるなどしてサブドメインとメインドメインのパスを切り替えるheplerをつくってあげるか、手っ取り早くbaseタグに直接指定してあげると良さそう。