Java
JavaEE

JAX-RS の WebTarget.queryParam() の落とし穴

JAX-RS Client の WebTarget.queryParam() の仕様にハマったのでメモ。

問題点

WebTarget.queryParam() の仕様はこちらです。
WebTarget に対してクエリパラメータを設定するというメソッドです。

このメソッド、URIエンコードについて注意点があります。

  • パラメータ名・値ともに URIテンプレートが使用できる。
  • 仕様には明記されていないが、たいていの実装ではパラメータ名・値ともに URI エンコードしてくれる。

URIテンプレートは {} で囲んだ部分にパスパラメータを突っ込んでくれるという機能ですが、よく考えるとちょっと問題があります。
それはクエリパラメータ値に "{" や "}" 自体を含めることができない、という問題です。"{", "}" ともにパスパラメータとして解釈されるためですね。

これを避けるためには、"{" や "}" を URIエンコードしなければなりません。それぞれ %7b, %7d にすれば良いということです。

が、JerseyやRESTEasyなどの実装では本メソッドは上記の通り URI エンコードをする動作なので、そのままもう一度URIエンコードされると %257b, %257d に変換されてしまうはずです。つまり { や } が使えないのでは、、、?

回避方法

Jersey 実装ではこれを回避するために URI エンコードを二重に実施しないようになっています。具体的には % の後ろに16進文字(0-9a-fA-F)が2個連続している場合は % をエンコードしないというルールです。なので、{, } をそのままエンコードしても大丈夫です。

また逆に考えると、たまたま % の後ろに16進文字が2個連続していたら % は URL エンコードしてくれないということでもあります。

従って、常に自分で URIエンコードしてから queryParam() を呼ぶようにしたほうがよいということです。

他の実装がどうなっているかは知りません。RESTEasy のコードをちょっと見た感じでは % は一切エンコードしないようになっていたみたいです。

当然ですが、上記動作は JAX-RS の仕様からは読み取れないです(実装依存)。なので一応動作確認してから使いましょう。