まとめ
res= http.get( url.path )
って書いていると、30x系のリダイレクトでパラメータ付きのURLを指示された時にはまります。url.request_uriを使いましょう。
内容
Google Calendar APIなんかを使ってWEBサービスからデータをとってくる時、net/httpを使うことはよくあるかと思います。
とってきたデータが30x系のリダイレクトであった場合まで考慮に入れたとすると、こんなコードになりますよね?
max_retry_count = 5
max_retry_count.times {|retry_count|
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true if (443==url.port)
http.ca_file = ‘/var/hogehoge/www.google.com.cer’
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
res= http.get( url.path )
case res
when Net::HTTPSuccess
break
when Net::HTTPRedirection
url = URI.parse(res[‘Location’])
next
else
break
end
}
resを使ってあれこれ。
ところが、これでGoogleカレンダーのデータをとってこようとしたらひどい目に遭います。とりあえずとってくると…
Moved Temporarily
The document has moved https://www.google.com/xxxx?gsessionid=m6Kxxxx
想定の範囲内の302 Moved Temporarilyなので、プログラムはいわれたとおりにリトライして、データをとってきます。
Moved Temporarily
The document has moved https://www.google.com/xxxx?gsessionid=1rJxxxx
え?と思いつつ、プログラムはいわれたとおりにリトライして、データをとってきます。
Moved Temporarily
The document has moved https://www.google.com/xxxx?gsessionid=Cmvxxxx
….たらい回し状態です。いっこうに目的のデータに行き着きません。
やり方が間違っているのかなぁ、それともドキュメントに読み落としがあるかなぁ、とさんざん悩んだ末、やっと原因に気がつきました。
rubyのURIライブラリは、URIを渡すと部品に分解してくれます。host/port/path/query….
そこで、http.get( url.path )を使うと? queryとして指示されている「?gsessionid=Cmvxxxx」が吹っ飛んじゃいますよね。
Googleさんから見ると、
・”xxxx”にクライアントが来たので、”xxxx?gsessionid=….”にリダイレクトを指示しました。
・なぜかクライアントはgsessionidを外して、再び”xxxx”にアクセスしてきました
・仕方がない(というか、別クライアントに見えるので)、”xxxx?gsessionid=….”にリダイレクトを指示しました。
・なぜかクライアントはgsessionidを外して”xxxx”にアクセスしてきたので….
ということになっていたわけです。
….だって みんな“http.get( url.path )” って書くじゃーん!
res= http.get( url.request_uri )
って書くのがいいみたいです。