HTTPのリダイレクト
2021/03/01更新
目次
概要
HTTPリダイレクトに関して、忘れがちな情報をここにまとめておく。
HTTPリダイレクト
HTTPリダイレクトの一覧
HTTPリダイレクトには以下のような種類がある。
HTTPステータスコード | 恒久的/一時的 | メソッドの変更 | 経緯 |
---|---|---|---|
301 Moved Permanently | 恒久的 | GETは変更しない。GET以外は変更される可能性がある。 | 当初からある恒久的リダイレクトのコード。 |
302 Found | 一時的 | GETは変更しない。GET以外は変更される可能性がある。 | 当初からある一時的リダイレクトのコード。 |
303 See Other | 一時的 | GETは変更しない。GET以外はGETに変更する。 | 302の再定義。POSTリクエストの応答として別のページへGETでアクセスしてほしいケースなどにために作られた。 |
307 Temporary Redirect | 一時的 | メソッドも本文も変更しない。GET・HEAD以外はユーザーの確認が必要。 | 302の再定義。「メソッド変更不可」であることを改めて厳格に定義したもの。 |
308 Permanent Redirect | 恒久的 | メソッドも本文も変更しない。GET・HEAD以外はユーザーの確認が必要。 | 301の再定義。「メソッド変更不可」であることを改めて厳格に定義したもの。 |
HTTP/1.0(RFC 1945)から定義されていたのは301と302だけであった(302は当初「Moved Temporarily」だった)が、302の規格外の使い方(現在の303に相当する使い方)が増えたため、HTTP/1.1(RFC 2068)で303が登場した。さらに、本来メソッドを変えてはいけなかったはずの302を再定義するため、RFC 2616で307が登場し、同様に301の再定義となる308がRFC 7538で登場した。なお、308以外は現在ではRFC 7231での定義が最新となっている。
GET以外のメソッドが特に問題とならない場合は、現在でも多くの場合で301と302が使われる。POSTやPUTリクエストの結果として、別ページへ移動する必要がある場合(リダイレクト先のURLではなく、最初のURLでPOSTやPUTに対応する処理が行なわれる場合)は303を使う。また、GET以外のメソッドが使われる場合で、それらも完全なリダイレクトが必要となるときは厳格な307と308を使用する。
リダイレクト時のレスポンス
リダイレクト時のレスポンスの例を以下に示す。
ステータスコードが300番台になっている。
リダイレクト先を表す
Location
ヘッダが必ず付いている。リダイレクト先が書かれた簡単なHTMLがレスポンスボディに入れられることがある。これは多くの場合、サーバーが自動的に生成する形式的なもので、ブラウザで実際に表示されることはない。
HTTP/1.1 302 Found Date: Mon, 01 Mar 2021 12:34:56 GMT Content-Type: text/html; charset=iso-8859-1 Content-Length: 248 Location: https://example.com/hoge/fuga/index.html <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>302 Found</title> </head><body> <h1>Found</h1> <p>The document has moved <a href="https://example.com/hoge/fuga/index.html">here</a>.</p> </body></html>
Apacheでのリダイレクトの方法
Redirectディレクティブ
Apacheで通常の単純なリダイレクトを行なうには、mod_aliasのRedirect
ディレクティブを使う。
# Redirect ステータスコード 対象パス リダイレクト先 Redirect permanent /aaa/index.html /bbb/index.html
対象パスは必ず「/」から始まる絶対パスを指定する。リダイレクト先は「https://」などから始まるURLか、「/」から始まる絶対パスを指定する。
ステータスコードの部分は、ステータスコードを直接指定する他に、以下のような指定ができる。
キーワード | 意味 |
---|---|
| 301リダイレクト |
| 302リダイレクト |
| 303リダイレクト |
| 410レスポンス(Gone) |
300番台以外のステータスコードとなる場合は、最後のリダイレクト先は指定できない。
Redirect
ディレクティブは、実は先頭からパス区切り単位でのマッチングを行なっている。つまり、対象パスとリダイレクト先に、ファイルの代わりにディレクトリを指定すれば、それ以下のファイルが全て1対1にリダイレクトされる。
Redirect permanent /aaa /bbb # /aaa/ は /bbb/ へリダイレクトされる。 # /aaa/hoge/index.html は /bbb/hoge/index.html へリダイレクトされる。 # /aaa.html や /aaa123/ はリダイレクトされない(パス区切りでマッチしないため)。
トリッキーな例として、リダイレクト元に/index.html
のようにファイルを指定したつもりでも、仮に/index.html/
というディレクトリがあれば、それ以下のファイルもリダイレクト対象となってしまう。また、リダイレクト先を/index.html
のようにしても、マッチしたパス全てがそのファイルにリダイレクトされるわけではなく、マッチしたパスに応じて/index.html
の後ろにパスが付加されることになる。
Redirect permanent /index.html /bbb/index.html # もし /index.html/hoge/ というパスがあれば、 /bbb/index.html/hoge/ にリダイレクトされる。 Redirect permanent /aaa/ /bbb/index.html # /aaa/hoge/ は /bbb/index.htmlhoge/ にリダイレクトされてしまう。
クエリ文字列は、基本的にはそのまま引き継がれるが、リダイレクト先にあらかじめクエリ文字列を指定した場合は、元のクエリ文字列は無視される。なお、リダイレクト元としてクエリ文字列を指定することはできない。
Redirect permanent /aaa/index.html /bbb/index.html # /aaa/index.html?hoge=fuga は /bbb/index.html?hoge=fuga にリダイレクトされる。 Redirect permanent /ccc/index.html /ddd/index.html?piyo=paco # /ccc/index.html?hoge=fuga は /ddd/index.html?piyo=paco にリダイレクトされる。
2つ以上にマッチする場合、最初に記述してあるものが有効となる。サブディレクトリのリダイレクトを記述する場合などは注意が必要となる。
Redirect permanent /aaa/ /bbb/ Redirect permanent /aaa/hoge/ /ccc/ # /aaa/hoge/index.html は /ccc/index.html ではなく /bbb/hoge/index.html にリダイレクトされてしまう。 Redirect permanent /aaa/hoge/ /ccc/ Redirect permanent /aaa/ /bbb/ # /aaa/hoge/index.html が /ccc/index.html に正しくリダイレクトされる。
.htaccessに記述する場合、当然そのディレクトリ以下のパスについてしか設定できない。ただし、その場合でも対象パスは「/」から絶対パスで記述する必要がある。
RedirectMatchディレクティブ
RedirectMatch
ディレクティブは、Redirect
と似ているが、マッチングに正規表現が使用でき、Redirect
ではできなかった柔軟なリダイレクトが可能となる。
RedirectMatch permanent ^/aaa/.* /bbb/ # /aaa/ 以下全てが /bbb/ にリダイレクトされる。 RedirectMatch permanent ^/ccc/(.+\.jpg)$ /ddd/$1 # /ccc/ 以下の.jpgファイルのみリダイレクトさせる。 RedirectMatch permanent ^/eee/(.+)\.jpg$ /eee/$1.png # .jpgファイルを.pngファイルにリダイレクトさせる。
Redirect
ディレクティブと異なり、単純な前方一致ではなく、正規表現でのマッチングとなるため、^
(先頭)や$
(末尾)の指定を忘れると想定外の結果となる場合があるので注意。
mod_rewrite
さらに細かなリダイレクトを設定するには、mod_rewriteを使う。もう間違えないmod_rewriteを参照。
リダイレクト先URLの取得
JavaScriptで取得
ブラウザ上のJavaScriptだけで、任意のURLのリダイレクト先URLを取得するのは難しい。XMLHttpRequest
のresponseURL
を参照すれば、最終的にたどり着いたURLだけは取得できるが、多段階にリダイレクトしている場合、途中のURLは分からない。また、Ajaxである以上、他のドメイン(オリジン)のURLを調べることは原則としてできない。
const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://example.com/'); xhr.onload = function() { console.log(xhr.responseURL); }; xhr.send();
curlコマンドで取得
HTTPのレスポンスを細かく調べるには、どうしてもブラウザ上の制限から抜け出す必要がある。最も簡単なのは、Linuxなどで使用できるcurlコマンドを使う方法である。
# 最初のURLのみ curl -s -I https://example.com/ | grep -i "Location:" # 最終的なリダイレクト先まで全て curl -s -I -L https://example.com/ | grep -i "Location:"
curlコマンドの-I
オプションでレスポンスヘッダのみを出力し、その中からリダイレクト先が書かれているLocation
ヘッダをgrepで取り出している。-L
オプションをつけると、リダイレクト先に自動的にリクエストを再送信してくれるため、多段階にリダイレクトされている場合でも全てのURLを取得できる。-s
は詳細情報を出力しないオプション、grepの-i
は大文字小文字を無視するオプションである。
wgetコマンドで取得
wgetコマンドでもレスポンスヘッダを取得することができる。
# 最初のURLのみ wget -q -S --max-redirect=0 https://example.com/ 2>&1 | grep -i "Location:" # 最終的なリダイレクト先まで全て wget -q -S https://example.com/ 2>&1 | grep -i "Location:"
wgetコマンドでは-S
オプションでレスポンスヘッダを標準エラーに出力できる。-q
は詳細情報を出力しないオプションである。wgetの場合、自動でリダイレクト先に再リクエストしてくれるため、最初のURLのみ取得する場合は、--max-redirect=0
でリダイレクト回数を制限すればよい。
Node.jsで取得
ブラウザ上のJavaScriptでは難しかったが、Node.jsであればrequest
モジュールでHTTPリクエストを投げてそのレスポンスを調べることができる。
const request = require('request'); request({ uri: 'https://example.com/', followRedirect: false }, function(err, res, body) { console.log(res.headers.location); });
ただ、request
モジュールは2021年現在deprecatedとなってしまっているため、今後は別のモジュールを使っていく必要がある。例えば、axios
モジュールを使う場合、以下のようになる。
const axios = require('axios'); axios.get('https://example.com/', { maxRedirects: 0 }).catch(function(res) { console.log(res.response.headers.location); });
Perlで取得
PerlではLWP::UserAgent
を使う。ただ、場合によっては、curlコマンドやwgetコマンドを直接呼んでしまう方が簡単である。
use LWP::UserAgent; my $ua = LWP::UserAgent->new(max_redirect => 0); my $res = $ua->get('https://example.com/'); print $res->header('location') . "\n";