この記事は以下のブログ記事の日本語訳です。
URI.escape is obsolete. Percent-encoding your query string
【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには
みなさんはRuby 2.7.0を使っているプロジェクトで以下の警告に遭遇しましたか?
warning: URI.escape is obsolete
warning: URI.encode is obsolete
この警告の直し方を見ていきましょう!
歴史について少しだけ
Ruby 2.7.0ではURI.escape
またはエイリアスメソッドのURI.encode
を呼びだしたときに警告が出ます。これはあたかも新しく追加された警告のように見えますが、実際はなんと・・・10年以上も非推奨とされ続けていたのです!どうしても今までこの警告を目にしなかったんだろう?と不思議に思っている方へ。答えはこうです。これまではverboseモードでスクリプトを実行したときだけ表示されていました。そして、この仕様が最近変わりました。これがその理由です。
じゃあなんでURI.escape
は非推奨メソッドなの?
「URIをエスケープする」という概念は実はやっかいです。なぜならURIは多数の要素(path
やquery
など)から成り立っており、その要素をすべて同じ方法でエスケープするわけではないからです。たとえば、#
という文字について考えてみましょう。この文字はURIの最後に出てくるときは問題ありません(これは世間ではアンカーと呼ばれます。URI用語で言うところのfragment
要素です)。しかし、ユーザー入力の一部に#
が使われたとき(たとえば検索キーワードなど)は、入力値を正しく解釈するためにそれをエンコードしたいと思うはずです。同様に、クエリ文字列に=
や&
といった予約文字が含まれていた場合、それらが間違って区切り文字として解釈されないようにエスケープしたくなるはずです(あたかも予約文字であるかのように)。
URI.escape
は単純にgsub
メソッドを使って文字列全体を置換しているだけです。各要素が何であるかは区別しません。なので、上で述べたような複雑な問題は一切考慮されないのです。
どう修正するの?
URI文字列をまるごとを受け取って、各要素の違いをうまく解釈しつつ最適なエスケープ処理を加える、というような既存の解決策はまだ見つかっていません。ですので、私が考えるに、こういう場合は各要素を別々にエンコードするのが良いと思います。最も一般的な(そして最も問題が起きやすい)ユースケースは、おそらくクエリ文字列(query
要素)のエンコーディングでしょう。そこで、今回はこのユースケースに焦点を当てます。RubyのURI
モジュールには2種類の便利なメソッドが用意されているので、これを使えばうまくいきます!
クエリ文字列をパーセントエンコードする
URI.encode_www_form_component(string, enc=nil)
このメソッドは*, -, ., 0-9, A-Z, _, a-z
だけをそのまま残し、それ以外のすべての予約文字をパーセントエンコードします。さらに、スペースも+
に置換してくれます。以下の例を見てください。
query = "Tom & Jerry"
query = URI.encode_www_form_component(query)
query # => "Tom+%26+Jerry"
post = "So scared, but let's do this! #yolo"
post = URI.encode_www_form_component(post)
post # => "So+scared%2C+but+let%27s+do+this%21+%23yolo"
ご覧のとおり、適切なエスケープ処理を加えて安全なクエリ文字列を作ることができました。しかし、これだとちょっと手間がかかりすぎると感じた場合は、クエリ全体を適切に処理してくれる便利な方法があります。それがこちらです。
URI.encode_www_form(enum, enc=nil)
このメソッドは先ほどのメソッドとはインターフェースが若干異なり、Enumerable
(通常はネストした配列かハッシュ)を受け取ります。それから、そのデータを使ってクエリ文字列を構築します。このメソッドは内部で.encode_www_form_component
を使うため、エンコーディング規則はどちらも同じです。異なるのは使い方だけです。以下の例を見てください。
URI.encode_www_form([["search", "Tom & Jerry"], ["page", "3"]])
# => "search=Tom+%26+Jerry&page=3"
URI.encode_www_form(["search", "2 + 2 = 5"])
# => "search=2+%2B+2+%3D+5"
URI.encode_www_form(search: "why is URI.escape obsolete", category: "meta")
#=> "search=why+is+URI.escape+obsolete&category=meta"
# Shameless plug
URI.encode_www_form(q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en")
#=> "q=how+to+speed+up+your+CI%3F&tag=%23devops&tag=%23productivity&lang=en"
とても簡単ですね。
Railsの場合は?
Hash#to_query
(Hash.to_param
というエイリアスメソッドもあります)
RailsはHash
クラスを拡張して、この便利なメソッドを追加しています。このメソッドは正しい形式でクエリ文字列を返します。値は適切にエスケープされます。
query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query
#=> "lang=en&q=how+to+speed+up+your+CI%3F&tag%5B%5D=%23devops&tag%5B%5D=%23productivity"
(キーがアルファベット順にソートされる点にも注意してください)
このメソッドにはオプション引数としてネームスペースを渡すこともできます。返ってくる文字列はネームスペースが各キーを内包する形になります。たとえば、search[q]="how to speed up your CI?"
というような形です(もちろんパーセントエンコードされますが)。
query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query("search")
#=> "search%5Blang%5D=en&search%5Bq%5D=how+to+speed+up+your+CI%3F&search%5Btag%5D%5B%5D=%23devops&search%5Btag%5D%5B%5D=%23productivity"
まとめ
短い記事ですが、みなさんが「この記事を読んだらURI文字列のエンコードに関する疑問が解消された!」と思ってくれたら嬉しいです。もし、みなさんのRuby/Railsプロジェクトでは何か別の方法を使っているなら、コメント欄にてその方法も教えてください。
そしてもし、みなさんのプロジェクトでCIのビルドが遅いことにお困りでしたら、ぜひKnapsack Proをチェックしてみてください。その問題を解消します。
(翻訳はここまで)
訳者による補足
日本語や"%"が含まれる場合のエンコード結果について
クエリ文字列に日本語が含まれる場合や、文字列に%
が含まれる場合のエンコード結果も調べてみました。
結果としてはURI.encode
を使った場合も、URI.encode_www_form_component
やURI.encode_www_form
を使った場合も同じでした。
# 日本語をエンコードする場合
str = "あいう"
URI.encode(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"
URI.encode_www_form_component(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"
URI.encode_www_form([['query', str]])
#=> "query=%E3%81%82%E3%81%84%E3%81%86"
# %を含む文字列をエンコードする場合
str = "10%"
URI.encode(str)
#=> "10%25"
URI.encode_www_form_component(str)
#=> "10%25"
URI.encode_www_form([['query', str]])
#=> "query=10%25"
公式リファレンスのリンク等
各メソッドの公式リファレンスはこちらです。
また、公式リファレンスではURI.encode
の移行先として、encode_www_form_component
以外にも以下のようなメソッドが紹介されています。