@kyanny's blog

My life. Opinions are my own.

LWP::UserAgent でリダイレクトする URL にアクセスした際、リダイレクト先の URL を得るには

先日↑なタイトルで $res->base を使うといける、とか書きましたがデマでした。すいません。消しちゃったので permalink なくなりました。もしブクマしてくれた人がいたら重ねてすいません。

で、 HTTP::Response のマニュアルをちゃんと読んで、今度は大丈夫かなと思ったのでメモ。

検証コード

リダイレクト先の URL を得るには

my $response = $ua->get($url);
my $uri = $response->request->uri;
print $uri;

これでいい。 $uri は URI モジュールのインスタンス。ふつうに get とかして帰ってくる $response が最終的に 30x じゃないステータスコードが返されたレスポンスのオブジェクトなので、そのレスポンスを発生させたリクエスト (->request) をたどってその uri をみる。

リダイレクトされてる途中の URL を得るには

my $response = $ua->get($url);
my @responses = $response->redirects();

@responses が HTTP::Response のインスタンスの配列になってる。順序は、 $responses[0] が一番古い(最初にリダイレクトされた)レスポンス。あとは $responses[0]->request->uri とかすればいい。

なぜ $response->base ではダメか

$response->base について POD にはこう書いてある(テキトー訳)
http://search.cpan.org/~gaas/libwww-perl-5.832/lib/HTTP/Response.pm

$r->base

    このレスポンスの base URI を返す。返り値は URI のオブジェクト。

    base URI は以下の方法で決定される(優先順位の高い順に)

       1. <BASE HREF="..."> タグで HTML 文書中に埋め込まれていたとき
       2. レスポンスに "Content-Base:" ないし "Content-Location:" ヘッダが含まれていたとき

          古い HTTP 実装との後方互換性のために "Base:" ヘッダも参照する

       3. このレスポンスがリダイレクトだったとき。リダイレクトレスポンスを受け取った場合、 base URI はもともとの $ua->request() メソッドに渡された URI とは違う可能性がある。

    以上のどれも絶対 URI を返さなかったときは undef を返す。

    (以下よくわかんないけど) LWP モジュールから HTTP::Response オブジェクトを扱うとき、 HTML 文書に base URI が埋め込まれていると "Content-Base:" ヘッダもつくので、このメソッドは実質 step 2, 3 だけだよ(訳せません)

今回おかしくなったのは、まさに <base href="..."> が書いてあったページ(具体的には http://live.nicovideo.jp/watch/lv3905014 です)をリクエストして $response->base をとってしまったので、 http://live.nicovideo.jp/ がとれてしまい、トップページの URI なんかとっても意味ないだろ!ブラウザでみてもリダイレクトなんてしてないのになんで?というわけで調べてみたら自分が間違っていた、という話でした。

たくさんリダイレクトされた途中のレスポンスとかリクエストに用があることは少ないと思うので(デバッグ目的なら lwp-request -dS ... とかで十分)、素直に $response = $ua->get($url); $response->request->uri; を使えば良さそう。