Help us understand the problem. What is going on with this article?

【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

この記事は以下のブログ記事の日本語訳です。

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は多数の要素(pathqueryなど)から成り立っており、その要素をすべて同じ方法でエスケープするわけではないからです。たとえば、#という文字について考えてみましょう。この文字は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_componentURI.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以外にも以下のようなメソッドが紹介されています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした