動機
- Sinatraアプリケーションを動かしたい。
- リバースプロキシを経由したい(URL,SSL,パフォーマンス, etc.)
- URLはルートではなく,サブディレクトリに配置したい
今回のサンプルでは,リバースプロキシとしてApacheを使っているが,nginxなど,他のWebサーバでも同様のはず。
準備
サンプルアプリケーション
require "sinatra"
get "/" do
"Hello!"
end
get "/redirect" do
redirect to("dest")
end
get "/dest" do
"redirected"
end
これを
$ ruby application.rb
とすることで, http://localhost:4567/ でサンプルアプリケーションが起動する。
リバースプロキシの設定 (Apache 2.4)
上で用意したサンプルアプリケーションを,
http://example.com/example-app/
として動作させたい。
※SinatraアプリケーションとApacheは同じホスト(example.com)上で動作しているとする。
apacheの設定ファイルで,
ProxyPass /example-app/ http://localhost:4567/
ProxyPassReverse /example-app/ http://localhost:4567/
これで,Webブラウザから http://example.com/example-app/ にアクセスすると,画面に「Hello!」という文字列が表示されるはず。
何が問題?
http://example.com/example-app/redirect にアクセスしてみよう。
本当は, http://example.com/example-app/dest にリダイレクトされて,画面に「redirected」と表示されて欲しい。
が,試すと http://example.com/dest にリダイレクトされ,画面には「404 Not Found」エラーが…。
原因は?
まず,前提条件として,HTTPによるリダイレクトでは,リダイレクト先はアクセス可能なURIで返さねばならず,相対パスで指定することはできない。
上記のサンプルアプリケーションで
get "/redirect" do
redirect to("dest")
end
でリダイレクト処理が書かれている。ここではリダイレクト先が相対指定されているが,Sinatraのtoヘルパメソッド(実際にはuriヘルパメソッドのエイリアス)によって,正しいURIに書き換えられてクライアントに返される。
この時,toヘルパメソッドの中では,スキーム(http / https),ホスト名(example.com),ポート番号,パスを結合してURIが組み立てられている。ただ, リバースプロキシを介してアクセスしている場合,実際にブラウザがアクセスしているURIはSinatraアプリケーションは知らないので,通常であればリダイレクト先として,
http://localhost:4567/dest
のようなURIを返すはず。このように返してくれれば,上でApacheに設定した
ProxyPassReverse /example-app/ http://localhost:4567/
の設定により,Apacheがブラウザに返すレスポンスでは,リダイレクト先が
http://example.com/example-app/dest
と書き換えられるはずだ。(そのためのProxyPassReverse設定)
ただ,Sinatraでは,Sinatraが親切にもリバースプロキシの有無をチェックし,リバースプロキシ経由でアクセスされている場合は,toヘルパメソッドでURIを組み立てる際に,自動的にホスト名を書き換えてくれてしまう。その結果,リダイレクト先として
http://example.com/dest
というURIが作られ,これは上記のProxyPassReverseの設定では変換されず,そのままブラウザに返されてしまう。
対処方法
Sinatraのtoヘルパメソッド(実際にはuriヘルパメソッド)のソースを見ると,
uri << request.script_name.to_s if add_script_name
とあり, request.script_name の内容をアプリケーションのディレクトリ名(今回の例では/example-app)として使用している。であれば,この request.script_name を書き換えてあげればよさそうだ。
そこで,application.rbにbeforeフィルタを追加してやる
before do
request.script_name = "/example-app"
end
こうすることで,toヘルパメソッドで正しいURIが組み立てられ,想定通り
http://example.com/example-app/dest
にリダイレクトされるようになる。
上のコードではハードコードしてしまっているが,実際にはこの部分を設定ファイルなどでアプリケーションの外に出してやれば,アプリケーションの展開環境に応じたディレクトリ名を指定することができる。
別解 (Thinを使用している場合)
上の例では,アプリケーション側で対処しているが,実のところ,SinatraのアプリケーションサーバとしてThinを使っているのであれば,Thinの設定で対処することも可能。
Thinの設定ファイル(yaml)内で,
prefix: /example-app
と指定することでSinatraの request.script_name に設定される。
ただ,今回の環境では,アプリケーションサーバとしてPumaを使っているため,上記の方法が使えず,アプリケーション側で対応することにした。