1. 概要

簡単に言うと、 URLエンコーディングは、特殊文字をURLから、仕様に準拠し、正しく理解および解釈できる表現に変換します。

このチュートリアルでは、 URLまたはフォームデータをエンコード/デコードして、仕様に準拠し、ネットワークを介して正しく送信する方法に焦点を当てます。

2. URLを分析します

まず、基本的なURI構文を見てみましょう。

scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]

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

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

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

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

@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を解析し、getXXXなどの単純なAPIを介してその部分を公開します。

3. URLをエンコードする

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

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

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", "value@!$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つのパラメーターを受け入れます。

  1. data –変換される文字列
  2. encodingScheme –文字エンコードの名前

このencodeメソッドは、文字列を application /x-www-form-urlencoded形式に変換します。

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

注: World Wide Webコンソーシアムの推奨事項では、UTF-8を使用する必要があると規定されています。 そうしないと、非互換性が生じる可能性があります。 (参照: https://docs.oracle.com/javase/7/docs/api/java/net/URLEncoder.html

4. URLをデコードします

URLDecoder のデコード方法を使用して、前のURLをデコードしてみましょう。

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&key2=value@!$2&key3=value%3",
      scheme + "://" + host + "?" + decodedQuery);
}

ここで覚えておくべき2つの重要なポイントがあります。

  • デコードする前にURLを分析する
  • エンコードとデコードに同じエンコードスキームを使用する

デコードしてから分析すると、URL部分が正しく解析されない可能性があります。 別のエンコード方式を使用してデータをデコードすると、ガベージデータが発生します。

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

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

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

パスセグメントをエンコードするには、代わりにSpringFrameworkのUriUtilsクラスを使用します。

UriUtils クラスは、パスとパスセグメントをそれぞれエンコードするためのencodePathメソッドとencodePathSegmentメソッドを提供します。

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;
}
@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にパス変数を追加しましょう。

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

そして、適切にエンコードされたURLをアセンブルしてアサートするために、テストをセクション2から変更します。

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フォームパラメータにも適用されます。

いつものように、ソースコードはGitHubから入手できます。