Railsで自分のサーバ名を知ろうとしてrequest.host_with_portを使おうとして、多段reverse proxy下だったのでめんどうなことになった。
Rails(Rack)だと
def host_with_port if forwarded = @env["HTTP_X_FORWARDED_HOST"] forwarded.split(/,\s?/).last else @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}" end end
となっていてX-Forwarded-Hostヘッダがあればこれの最後の値を利用しようとしている。X-Forwarded-Hostはproxyが受け取ったHostヘッダを本当の宛先に送るヘッダ(非標準)。以下のような多段proxyの場合
client -> proxy1 -> proxy2 -> ... -> proxyN -> server
serverが受け取るX-Forwarded-Hostは
proxy1, proxy2, ..., proxyN
という形式になる。
そのため、host_with_portはproxyNを返すのだがこちらとしてはproxy1が外から見えるホスト名なのでproxy1を返してほしい。
アプリ側はいじれなかったのでApacheの設定で無理矢理proxyNがサーバに
Host: proxy1
と送るようにしたけど。
なんで最後を返すのかがわからずぐぐってみたがやっぱりわからない
を見てredirectがうまくいくようにということみたいだけども、redirectの場合もproxy1を返してほしい気がする。
で他のフレームワークはどうだろうと思ってちょっとしらべてみる
Django
def get_host(self): """Returns the HTTP host using the environment or request headers.""" # We try three options, in order of decreasing preference. if settings.USE_X_FORWARDED_HOST and ( 'HTTP_X_FORWARDED_HOST' in self.META): host = self.META['HTTP_X_FORWARDED_HOST'] elif 'HTTP_HOST' in self.META: host = self.META['HTTP_HOST'] else: # Reconstruct the host using the algorithm from PEP 333. host = self.META['SERVER_NAME'] server_port = str(self.META['SERVER_PORT']) if server_port != ('443' if self.is_secure() else '80'): host = '%s:%s' % (host, server_port)
設定でUSE_X_FORWARDED_HOSTを有効にしている場合のみX-Forwarded-Hostを利用している。これについては以下で議論されている
確かにX-Forwarded-Hostをどこまで信用すべきなのかという話もあるし、forward proxyの情報が削除されていない場合はどうするのとかもあるし、あんまりフレームワークががんばってもしょうがない気がする。
あと、複数値ある場合には対応していないっぽい
Plack
sub _uri_base { my $self = shift; my $env = $self->env; my $uri = ($env->{'psgi.url_scheme'} || "http") . "://" . ($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) . ($env->{SCRIPT_NAME} || '/'); return $uri; }
X-Forwarded-Hostは利用していないみたい。
まとめ
まあ、X-Forwarded-Hostはフレームワークによっては対応していないし、あんまり信用すべきでもない。