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?)
参考
- https://github.com/ruby/ruby/blob/trunk/lib/uri/rfc2396_parser.rb
- https://bugs.ruby-lang.org/issues/10669
- https://bugs.ruby-lang.org/issues/2542
- https://github.com/rack/rack/blob/ab172af1b63f0d8e91ce579dd2907c43b96cf82a/lib/rack/mock.rb#L82-L85
- URIに使ってよい文字の話 - RFC2396 と RFC3986
補足
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様