Java URLエンコーディング/デコーディングの手引き

1前書き

簡単に言うと、https://en.wikipedia.org/wiki/Percent-encoding[URLエンコード]は、URLの特殊文字を仕様に準拠した表現に変換し、正しく理解し解釈することができます。

この記事では、URLが仕様に準拠し、ネットワークを介して正しく送信されるように、URLまたはフォームデータをエンコード/デコードする方法に焦点を当てます。

2 URLを分析する

https://en.wikipedia.org/wiki/Uniform Resource Identifier[URI]の基本的な構文は、次のように一般化できます。

scheme:[//[user:[email protected]]host[:port]][/]path[?query][#fragment]----

URIをエンコードするための最初のステップは、その部分を調べてから関連部分だけをエンコードすることです。

URIの例を見てみましょう。

[source,java,gutter:,true]

String testUrl = "http://www.baeldung.com?key1=value+1&key2=value%40%21%242&key3=value%253";

URIを分析する1つの方法は、String表現を__java.net.URI__クラスにロードすることです。

[source,java,gutter:,true]

@Test public void givenURL whenAnalyze thenCorrect() throws Exception { URI uri = new URI(testUrl);

    assertThat(uri.getScheme(), is("http"));
    assertThat(uri.getHost(), is("www.baeldung.com"));
    assertThat(uri.getRawQuery(),
      .is("key1=value+1&key2=value%40%21%242&key3=value%253"));
}
__URI__クラスは、文字列表現のURLを解析し、その部分を単純なAPI、たとえば__getXXXを介して公開します。

===  **  3 URLをエンコードします+

**

URIをエンコードするとき、よくある落とし穴の1つは完全なURIのエンコードです。通常、URIのクエリ部分のみをエンコードする必要があります。

__URLEncoder__クラスの__encode(data、encodingScheme)__メソッドを使用してデータをエンコードしましょう。

[source,java,gutter:,true]

private String encodeValue(String value) { return URLEncoder.encode(value, StandardCharsets.UTF__8.toString()); }

@Test public void givenRequestParam whenUTF8Scheme thenEncode() throws Exception { Map<String, String> requestParams = new HashMap<>(); requestParams.put("key1", "value 1"); requestParams.put("key2", "[email protected]!$2"); requestParams.put("key3", "value%3");

String encodedURL = requestParams.keySet().stream()
  .map(key -> key + "=" + encodeValue(requestParams.get(key)))
  .collect(joining("&", "http://www.baeldung.com?", ""));
assertThat(testUrl, is(encodedURL));
__encode__メソッドは2つのパラメータを受け入れます。

.  __data__  - 翻訳する文字列

.  __encodingScheme__  - 文字エンコーディングの名前

この__encode__メソッドは文字列をhttps://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1[__** application/x-www-form-urlencoded** __]形式に変換します。

エンコード方式は、特殊文字を8ビットの2桁の16進表記に変換し、それを「__%xy__」の形式で表します。パスパラメータを処理しているとき、または動的なパラメータを追加しているときは、データをエンコードしてからサーバーに送信します。

** 注:**  World Wide Webコンソーシアムは、__UTF-8を使用することを推奨しています。そうしないと、非互換性が生じる可能性があります。

(参照:

__https://docs.oracle.com/javase/7/docs/api/java/net/URLEncoder.html__)

===  **  4 URLをデコードする

**

__URLDecoder__のdecodeメソッドを使って前のURLをデコードしましょう。

[source,java,gutter:,true]

private String decode(String value) { return URLDecoder.decode(value, StandardCharsets.UTF__8.toString()); }

@Test public void givenRequestParam whenUTF8Scheme thenDecodeRequestParams() { URI uri = new URI(testUrl);

String scheme = uri.getScheme();
String host = uri.getHost();
String query = uri.getRawQuery();
String decodedQuery = Arrays.stream(query.split("&"))
  .map(param -> param.split("=")[0]+ "=" + decode(param.split("=")[1]))
  .collect(Collectors.joining("&"));
    assertEquals(
      "http://www.baeldung.com?key1=value 1&[email protected]!$2&key3=value%3",
      scheme + "://" + host + "?" + decodedQuery);
}
ここで重要な2つのビットは次のとおりです。

** デコード前にURLを分析

** エンコードとデコードに同じエンコードスキームを使用する

分析するよりもデコードすると、URL部分が正しく解析されない可能性があります。データをデコードするために別のエンコード方式を使用した場合、ゴミデータになります。

===  5.パスセグメントをエンコードする

__URLEncoder__は、__URL__のパスセグメントのエンコードには使用できません。パスコンポーネントは、ディレクトリパスを表す階層構造を指すか、または__“/” __で区切られたリソースを見つけるのに役立ちます。

パスセグメント内の予約文字は、クエリパラメータ値とは異なります。たとえば、「+」記号はパスセグメント内の有効な文字であるため、エンコードしないでください。

パスセグメントをエンコードするには、代わりにSpring Frameworkの__UriUtils__クラスを使用します。 __UriUtils__クラスは、パスとパスセグメントをそれぞれエンコードするための__encodePath__および__encodePathSegment__メソッドを提供します。

例を見てみましょう。

[source,java,gutter:,true]

private String encodePath(String path) { try { path = UriUtils.encodePath(path, "UTF-8"); } catch (UnsupportedEncodingException e) { LOGGER.error("Error encoding parameter {}", e.getMessage(), e); } return path; }

[source,java,gutter:,true]

@Test public void givenPathSegment__thenEncodeDecode() throws UnsupportedEncodingException { String pathSegment = "/Path 1/Path+2"; String encodedPathSegment = encodePath(pathSegment); String decodedPathSegment = UriUtils.decode(encodedPathSegment, "UTF-8");

    assertEquals("/Path%201/Path+2", encodedPathSegment);
    assertEquals("/Path 1/Path+2", decodedPathSegment);
}
上記のコードスニペットでは、__encodePathSegment__メソッドを使用したときにエンコードされた値が返され、+はパスコンポーネント内の値文字であるためエンコードされていないことがわかります。

テストURLにパス変数を追加しましょう。

[source,java,gutter:,true]

String testUrl = "/path+1?key1=value+1&key2=value%40%21%242&key3=value%253";

そして、正しくエンコードされたURLを組み立ててアサートするために、セクション2のテストを変更しましょう。

[source,java,gutter:,true]

String path = "path+1"; String encodedURL = requestParams.keySet().stream() .map(k → k + "=" + encodeValue(requestParams.get(k))) .collect(joining("&", "/" + encodePath(path) + "?", "")); assertThat(testUrl, CoreMatchers.is(encodedURL));

===  **  6. 結論**

このチュートリアルでは、データを正しく転送および解釈できるようにデータをエンコードおよびデコードする方法を説明しました。この記事ではURIクエリパラメータ値のエンコード/デコードについて説明しましたが、このアプローチはHTMLフォームパラメータにも適用されます。

https://github.com/eugenp/tutorials/tree/master/core-java[over on GitHub]のソースコードを見つけることができます。