daisuzz.log

UriComponentsBuilderとUriComponents、どちらのencodeメソッドを使えばいいのか?

UriComponentsBuilderが提供するencodeメソッドとUriComponentsが提供するencodeメソッドの違いがわからず調べたのでまとめてみます。

今回、使用したSpring Framework のバージョンは5.3.3です。

そもそもUriComponentsBuilderとは?

Spring Frameworkが提供するURIComponentsクラスのBuilderです。 UriComponentsBuilderを使った例が以下です。

        String url1 = UriComponentsBuilder
                .fromUriString("http://localhost:8080/{path}")
                .query("query1=q1")
                .query("query2={query}")
                .encode()
                .buildAndExpand("p", "q2")
                .toUriString();

        System.out.println(url1);

例では、fromUriString()を使ってURIのテンプレートを文字列で渡して、query()でクエリパラメータをセットし、buildAndExpand()でBuilderからUriComponentクラスを生成した後に、toUriString()URI文字列に変換しています。 {}を使うことで、URIのテンプレートに変数を埋め込むことができ、buildAndExpand()に引数として渡している値が、その順番でセットされます。

{path}pqueryq2がセットされるので、最終的にhttp://localhost:8080/p?query1=q1&query2=q2というURI文字列が生成されます。

クエリパラメータをセットするメソッドはクエリパラメータ名と値を分けて渡せるqueryParam()やクエリパラメータの値が存在するときだけセットするqueryParamIfPresent()など用途に応じて様々なものが用意されています。

UriComponentsBuilderとUriComponentsのencode()の違い

最初のコード例では、buidAndExpand()の前にUriComponentsBuilder#encode()を呼び出してURIテンプレートに含まれるnon-ASCIIや不正な文字をエンコードしていますが、以下のようにbuildAndExpand()の後にUriComponents#encode()を使ってURIエンコードすることもできます。

        String url1 = UriComponentsBuilder
                .fromUriString("http://localhost:8080/{path}")
                .query("query1=q1")
                .query("query2={query}")
                .buildAndExpand("p", "q2")
                .encode()
                .toUriString();

        System.out.println(url1);

一見順序が違うだけのように見えますが、UriComponentsBuilder#encode()UriComponents#encode()では↓こちらのコード例のように挙動が微妙に異なります。

        // UriComponentsBuilder#encode()を利用した場合
        // URI変数として渡したreserved character → encodeされる
        // URIテンプレート内のreserved character → encodeされない
        String url1 = UriComponentsBuilder.fromUriString("http://localhost:8080/日;/{path}/")
                .query("query=日;")
                .query("query2={query}")
                .encode()
                .buildAndExpand("日;", "日;")
                .toUriString();

        // UriComponents#encode()を利用した場合
        // URI変数として渡したreserved character → encodeされない
        // URIテンプレート内のreserved character → encodeされない
        String url2 = UriComponentsBuilder.fromUriString("http://localhost:8080/日;/{path}/")
                .query("query=日;")
                .query("query2={query}")
                .buildAndExpand("日;", "日;")
                .encode()
                .toUriString();

        System.out.println(url1);  
        System.out.println(url2);
 
        /* 表示されるURI文字列
           http://localhost:8080/%E6%97%A5;/%E6%97%A5%3B/?query=%E6%97%A5;&query2=%E6%97%A5%3B
           http://localhost:8080/%E6%97%A5;/%E6%97%A5;/?query=%E6%97%A5;&query2=%E6%97%A5;
         */

UriComponentsBuilder#encode()を利用した場合、URI変数の値として渡したreserved character(例ではセミコロンを利用している)はencodeされますが、URIテンプレート内に書かれたreserved characterはencodeされません。 一方、UriComponents#encode()を利用した場合は、reserved characterはどこで利用していてもencodeされません。

reserved characterについては,RFC3986に定義されているのでそちらを参照してください。

RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax

どちらを使うべきか?

どちらのencode()を使うべきかですが、UriComponents#encode()だとクエリパラメータに含まれるreserved characterがエンコードされず想定とは異なる挙動になりやすい点と、UriComponentsBuilder#encode()JavaDocにも以下のような記載があるため、大半のケースではUriComponentsBuilder#encode()を使っておくのがよさそうです。

For most cases, this method is more likely to give the expected result because in treats URI variables as opaque data to be fully encoded, while UriComponents.encode() is useful only if intentionally expanding URI variables that contain reserved characters.

UriComponentsBuilder (Spring Framework 6.0.10 API)

参考

Redirecting...