機械学習, Rails, Androidが好きです - プログラマdogwood008のライフハック

最近のマイブームは機械学習, Ruby on Rails, Android、中でも機械学習を使った金融商品の自動取引に興味があります。

Ruby 2.1 と 2.2 における、URI#parseの挙動の違い

Ruby 2.1 と 2.2 における、URI#parseの挙動の違い

症状

Ruby 2.1では、URIに使用できない文字(アンダースコア、アンダーバー)を含んだ文字列( http://abc_def.com/foobar/ ※1)をURI#parseに与えた際にURI::InvalidURIErrorの例外が発生する。

2015/08/15追記:
※1…この文字列はURIとしては RFC違反 です。ホスト名にアンダースコア、アンダーバーを含むことはできません。@key-amb様、ご指摘頂きありがとうございます。

なお、この記事では
「RFC違反の文字列に対して、同じURI#parseを使っているが、Rubyのマイナーバージョンに依って挙動が違う」
という点にのみ焦点を当てて、実際にどのように異なっているのか、Ruby2.2で2.1の挙動が欲しい場合にどうすれば良いのかについて論じます。

[1] pry(main)> require 'uri'
=>
true
[2] pry(main)> url="http://abc_def.com/foobar/"
=>
"http://abc_def.com/foobar/"
[3] pry(main)> URI.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)

しかし、Ruby 2.2では、そのような文字列も問題なくパースできてしまう。

[1] pry(main)> require 'uri'
=> true
[2] pry(main)> url="http://abc_def.com/foobar/"
=> "http://abc_def.com/foobar/"
[3] pry(main)> URI.parse url
=> #http://abc_def.com/foobar/>

原因

標準ライブラリのURIが参照するRFCが変更されたため。
古い RFC2396ではなく、新しい RFC3986 を参照し、Ruby 2.2からはこれに準拠するようになったため。

RFC2396に準拠したパースを行うには

URI::RFC2396_Parserを使用する。またはURI::Parserを使う。

URI::RFC2396_Parserを使う

[1] pry(main)> require 'uri'
=>
true
[2] pry(main)> url="http://abc_def.com/foobar/"
=>
"http://abc_def.com/foobar/"
[3] pry(main)> p=URI::RFC2396_Parser.new
=>
#
[4] pry(main)> p.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)

URI::Parserを使う

[1] pry(main)> require 'uri'
=>
true
[2] pry(main)> url="http://abc_def.com/foobar/"
=>
"http://abc_def.com/foobar/"
[3] pry(main)> p=URI::Parser.new
=>
#
[4] pry(main)> p.parse url
URI::InvalidURIError: the scheme http does not accept registry part: abc_def.com (or bad hostname?)

参考

補足

Ruby 2.2, 2.1 標準のURIライブラリでは、日本語などのマルチバイト文字を含むURIをparseすることができません。

[1] pry(main)> require 'uri'
=>
true
[2] pry(main)> URI.parse 'http://example.com/日本語'
URI::InvalidURIError: URI must be ascii only "http://example.com/\u{65e5}\u{672c}\u{8a9e}"
[3] pry(main)>
[4] pry(main)> p = URI::Parser.new
=>
#
[5] pry(main)> p.parse 'http://example.com/日本語'
URI::InvalidURIError: bad URI(is not URI?): http://example.com/日本語


[6] pry(main)> URI.parse 'http://example.com/Ω'
URI::InvalidURIError: URI must be ascii only "http://example.com/\u{3a9}"
[7] pry(main)> p.parse 'http://example.com/Ω'
URI::InvalidURIError: bad URI(is not URI?): http://example.com/Ω

addressableというgemを使用すると、マルチバイト文字もparseできます。RFC 3986, RFC 3987, RFC 6570に準拠しているようです。

[1] pry(main)> require 'addressable/uri'
=> true
[2] pry(main)> url = 'http://example.com/日本語'
=> "http://example.com/日本語"
[3] pry(main)> Addressable::URI.parse url
=> #http://example.com/日本語>

謝辞

修正前の記事は、「RFC3986によって、ホスト名にアンダーバーを含むことができるようになった」との誤解を与えかねない記事でした。また、更新によって有用なgemの紹介もすることができました。以下の皆様のご指摘で記事を修正、更新することができました。感謝の意を表明致します。

@key-amb
id:tmatsuu
id:tohokuaiki
id:toyama0919

Original

Back of Flier: Ruby 2.1 と 2.2 における、URI#parseの挙動の違い