RestTemplateで意図せずURLエンコードさせないための注意点

こんにちは。 エキサイト株式会社の三浦です。

URLには、特殊な意味を持っていたり使えなかったりする文字があり、それらを使いたい場合はURLエンコードを掛ける必要があります。 そのため、とある条件下ではJavaのRestTemplateは自動的にURLエンコードを掛けてくれます。

ですが、場合によってはエンコードされてしまうと問題がある場合もあります。

今回は、RestTemplateで意図せずURLエンコードさせないための注意点を紹介します。

URLエンコードをすると問題があるパターン

URLには、特殊な意味があったり使えない文字があります。 例えば日本語は、使えない文字の1つです。 日本語を使いたい場合は、以下のように変換する必要があります。

// これを
https://サンプル

// こうする
https://%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

これでURLとして使えるようになるのですが、実はこの % はURLで特殊な意味を持つ文字となっています。 そのため、うっかりもう一回URLエンコードを掛けてしまうと、以下のように更に変換されてしまいます。

// これが
https://%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

// こうなる
https://%25E3%2582%25B5%25E3%2583%25B3%25E3%2583%2597%25E3%2583%25AB

この状態だと受け取り側も2回デコードを掛ける必要が出てきてしまうため、避ける必要があります。

RestTemplateとエンコード

ところで、JavaでURLリクエストをする際は、RestTemplateを使うという方が多いと思います。 例えば、以下のように使うことができます。

// 1. URLエンコードを掛けた上でURL文字列を作成
String uriString = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUriString();

// 2. 作ったURL文字列を使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uriString,
    HttpMethod.GET,
    null,
    String.class
);

ただし、これだと実は問題があります。 まず、 1. URLエンコードを掛けた上でURL文字列を作成 をした結果、以下のような文字列が発行されます。

// 1. URLエンコードを掛けた上でURL文字列を作成
String uriString = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUriString();

// uriString = https://sample?sample_query=%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

これは正しくURLエンコードされた文字列になります。 しかし、実際に 2. 作ったURL文字列を使ってGETリクエストを送信 にて送信されるURLは以下になります。

// 2. 作ったURL文字列を使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uriString,
    HttpMethod.GET,
    null,
    String.class
);

// 送信されたURL : https://sample?sample_query=%25E3%2582%25B5%25E3%2583%25B3%25E3%2583%2597%25E3%2583%25AB

見ての通り、二重エンコードされています。 実は文字列を渡して restTemplate.exchange を実行すると、自動的にURLエンコードが掛けられます。 もちろん有用な場合もありますが、知らずに使っていると上記のように意図しないエンコード結果になってしまうでしょう。

RestTemplateで自動的にエンコードさせない方法

では、どうすれば自動エンコードさせないようにできるのでしょうか。 実は、文字列ではなくURI型で渡せばエンコードされずにリクエストが実行されます。

// 1. URLエンコードを掛けた上でURIを作成
URI uri = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUri();

// 2. 作ったURIを使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uri,
    HttpMethod.GET,
    null,
    String.class
);

このようにすれば、作成したURIそのままでリクエストが実行されます。

最後に

意図しないURLエンコードは、当然ですが意図しないリクエストにつながる恐れがあります。 基本的にはRestTemplateにはURI型を渡すようにし、エンコードをかける際は UriComponentsBuilder で明示的に実行するのが良いでしょう。