Tomcat 8からURIEncodingのデフォルトがUTF-8になっていた

先日、JBoss(ServletコンテナとしてTomcatを使っている)のクエリ文字列の文字コードの指定方法を調べていた時に、Tomcat 8のURIEncodingがISO-8859-1からUTF-8に変わっていたことを知りました。
たしかに・・・Tomcat 8にしてからURIEncodingを明示的に指定しなくても文字化けしなくなったような気はしていました。

せっかくなので、TomcatのURIエンコーディングについて簡単にまとめてみます。

# Tomcat 7までのURIEncoding
[Tomcat 7のドキュメント](https://tomcat.apache.org/tomcat-7.0-doc/config/http.html#Common_Attributes)を見ると、ISO-8859-1と明記されています。
そのため、URI内のリクエストパラメータをUTF-8で解釈したい場合は、`$TOMCAT_HOME/conf/server.xml`に以下のような定義を追加する必要がありました。

```xml:$TOMCAT_HOME/conf/server.xml
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="UTF-8" /> <!-- ← 追加 -->
```

# Tomcat 8からのURIEncoding

[Tomcat 8のドキュメント](https://tomcat.apache.org/tomcat-8.0-doc/config/http.html#Common_Attributes)を見ると、UTF-8と明記されています。
つまり、UTF-8にするために特になにもする必要はありません。（すばらしいですね）
いちおう、Tomcat 7(Servlet仕様?)と同じISO-8859-1にするオプションが用意されていて、システムプロパティ(-Dオプション)に「`org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true`」と指定すればよいみたいです。（たぶんこのオプションを使うことはないでしょう・・・）

# Spring Boot組み込みTomcatのURIEncoding

Spring Boot上で組み込みTomcatを使用する場合は、Tomcatのバージョンに関係なくURIEncodingのデフォルトはUTF-8になります。
これは、Spring Bootが組み込みTomcatのコンフィギュレーションを行う際にUTF-8をデフォルト値として指定しているためです。
UTF-8以外の文字コードを使用したい場合は、`application.yml` or `application.properties`に文字コードを指定してください。

```yaml:src/main/resources/application.yml
server:
    tomcat:
        uri-encoding: Windows-31J
```

# useBodyEncodingForURI属性

TomcatにはURI内のクエリパラメータの文字コードを指定する方法として、`URIEncoding`属性とは別に`useBodyEncodingForURI`属性を用意しています。
この属性を`true`にすると、`javax.servlet.ServletRequest#setCharacterEncoding(String)`で指定した文字コードを使用してクエリパラメータをデコードしてくれます。同一インスタンス内で複数の文字コードを扱う必要がある場合は、`URIEncoding`属性ではなく`useBodyEncodingForURI`属性を使うことになると思います。（とはいえ、同一インスタンス内で複数の文字コードを扱わないように設計するのがよいとは思います）

```xml:$TOMCAT_HOME/conf/server.xml
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           useBodyEncodingForURI="true" /> <!-- ← 追加 -->
```

では、Spring Bootだとどうやって設定するのでしょうか？
Spring Boot 1.3.3.RELEASEの時点では、残念ながらyaml又はpropertiesファイルで指定する方法はありません。
このような場合は、Spring Bootが提供しているServlet Containerをカスタマイズする仕組み(`EmbeddedServletContainerCustomizer` + `ConnectorCustomizer`)を使います。

```java:JavaConfigクラス
@Bean
EmbeddedServletContainerCustomizer containerCustomizer() {
    return (containerFactory) -> {
        if (containerFactory instanceof TomcatEmbeddedServletContainerFactory) {
            TomcatEmbeddedServletContainerFactory tomcatContainerFactory = (TomcatEmbeddedServletContainerFactory) containerFactory;
            tomcatContainerFactory.addConnectorCustomizers((connector) -> {
                connector.setUseBodyEncodingForURI(true);
            });
        }
    };
}
```

# SpringでServletRequest#setCharacterEncoding(String)する方法

ついでなので、Springアプリで文字コードを指定する方法も記載しておきます。

## Spring MVCアプリで文字コード指定

Spring MVCを使う場合は、Spring Webから提供されている`org.springframework.web.filter.CharacterEncodingFilter`を使用して文字コードを指定します。

* XML based configuration

```xml:src/main/webapp/WEB-INF/web.xml
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
```

* Java based configuration

```java:SpringMvcWebApplicationInitializer.java
public class SpringMvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new CharacterEncodingFilter("UTF-8")};
    }

}

```
## Spring Bootアプリで文字コード指定

Spring Boot 1.2からデフォルトで`CharacterEncodingFilter`が適用されるようになっており、UTF-8が設定されます。（UTF-8でOKなら何もする必要はありません）
UTF-8以外の文字コードを使用したい場合は、`application.yml` or `application.properties`に文字コードを指定してください。

```yaml:src/main/resources/application.yml
spring:
  http:
    encoding:
      charset: Windows-31J
```

# まとめ

最新のSpring Boot(1.3.3.RELEASE)を使えば、~~組み込みTomcatも8系だし~~文字コードがUTF-8の`CharacterEncodingFilter`が自動で適用されるので、扱う文字コードがUTF-8なら何もしなくても文字化けしません！！
Spring Bootを使ってアプリケーションを作りましょう！！