Spring Boot で Webコンテナを Undertow に切り替えたときに Cookie が読み込めない現象

# 概要

[Spring MVC](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html) を使用した場合、デフォルトのWebコンテナは Tomcat ですが、切り替えることは可能です。 :point_right_tone2: [76. Embedded Web Servers](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html)
Tomcat から Undertow に切り替えた際に、既存で使用していた Cookie が読み込めなくなったので、その原因と解決方法をまとめます。

以下のバージョンで試したときの動作です。

- Spring Boot 2.0.6.RELEASE

# 発生した現象

ドキュメントにあるように、以下の記述で Tomcat から Undertow に切り替えられます。

```gradle:build.gradle
configurations {
	compile.exclude module: 'spring-boot-starter-tomcat'
}
dependencies {
	implementation('org.springframework.boot:spring-boot-starter-web')
	implementation('org.springframework.boot:spring-boot-starter-undertow')
}
```

再現コードとして以下の RestController を書きます。
**hoge** Cookie を取得してレスポンスするだけの単純なアプリケーションです。

```java
@RestController
class DemoController {
    @GetMapping("/")
    public String hello(@CookieValue("hoge") String cookie) {
        return cookie;
    }
}
```

`fuga&a=b` という値で **hoge** Cookie をリクエストします。
すると、 Controller が受け取った値は `fuga&a` となっていて `=b` が消えてしまいました。
Tomcat の場合や、 spring-boot-starter-jetty で Jetty を使用した場合は期待通りに `fuga&a=b` が取得できます。

```zsh
$ curl -H "Cookie: hoge=fuga&a=b" "http://localhost:8080/"
fuga&a
```

# 原因

Undertow に以下の記述があります。

https://github.com/undertow-io/undertow/blob/2.0.14.Final/core/src/main/java/io/undertow/util/Cookies.java#L176-L203
> Parses the cookies from a list of "Cookie:" header values. The cookie header values are parsed according to RFC2109

[RFC 2109](http://tools.ietf.org/search/rfc2109) に従っているというわけで、そちらを読んでみるわけですが、どうやら Cookie の値の `=` は `"` で囲まないといけないようです。
そのため、リクエストを以下のように `"` で囲むように変えると元々期待していた値を取得することができました。

```zsh
$ curl -H 'Cookie: hoge="fuga&a=b"' "http://localhost:8080/"
fuga&a=b
```

原因はわかったとして、既存の `"` で `=` を囲んでいないクライアントは問題が出てしまいます。
Undertow にはこれを解決するためのオプションが用意されています。 :point_right_tone2: [Undertow](http://undertow.io/undertow-docs/undertow-docs-1.4.0/index.html#common-listener-options)

> **ALLOW_EQUALS_IN_COOKIE_VALUE**
If this is true then Undertow will allow non-escaped equals characters in unquoted cookie values. Unquoted cookie values may not contain equals characters. If present the value ends before the equals sign. The remainder of the cookie value will be dropped. Defaults to false.

オプション名の通りですが、 Cookie の値として equal(`=`) を許可するものです。
Spring Boot でこのオプションを `true` にすれば良いので、以下のコードを追加します。

```java
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> webServlerFactoryCustomizer() {
	return undertow -> undertow.addBuilderCustomizers(builder -> builder.setServerOption(
		UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true));
}
```

こうすることで、以下のように `=` 以降の値も受け取れるようになります。

```zsh
$ curl -H "Cookie: hoge=fuga&a=b" "http://localhost:8080/"
fuga&a=b
```

### どうして Undertow だけ異なるのか

Undertow が [RFC 2109](http://tools.ietf.org/search/rfc2109) に従っているとして、他のWebコンテナ（Tomcat,Jetty）はなぜ問題にならなかったのでしょうか。

改めて Cookie についてですが、現在標準となっているのは [RFC 6265](https://tools.ietf.org/html/rfc6265) です。
[HTTP cookie](https://ja.wikipedia.org/wiki/HTTP_cookie)
[Cookieとかセッションとかセキュリティとか](https://qiita.com/uryyyyyyy/items/1c32afea4240a14d57e4)

RFC 6265 を読むと RFC 2109 と異なり `"` で囲む必要がなくなっています。（囲んでも良い）

#### Jetty

[CookieCutter.java
](https://github.com/eclipse/jetty.project/blob/jetty-9.4.9.v20180320/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java)
デフォルトはRFC6265のため `"` で囲む必要がありません。
以下のように設定でパーサーの挙動を切り替えることも可能なようです。

```ini
jetty.httpConfig.cookieCompliance=RFC2965
```

#### Tomcat

調べているうちにわかったのですが、 Tomcat はこのあたり以前から何度か変更してるみたいです。

[Apache TomcatとHTTPクッキーにまつわる騒動 - GeekFactory](https://int128.hatenablog.com/entry/20081008/1223480443)

今はどうなっているのかと言うと、以下にあるように `Rfc6265CookieProcessor` が標準となっているので `"` で囲む必要がありません。

[Apache Tomcat 9 Configuration Reference (9.0.12) - The Cookie Processor Component](https://tomcat.apache.org/tomcat-9.0-doc/config/cookie-processor.html)
> The standard implementation of CookieProcessor is org.apache.tomcat.util.http.Rfc6265CookieProcessor.

`LegacyCookieProcessor` というものに切り替えることもでき、こちらを使用した場合はドキュメントにあるようにオプションで挙動を変更できるようなっています。今の Undertow と同じような感じですね。
> allowEqualsInValue

# 所感

オプションで挙動は変えられるけど、 Undertow もデフォルトで RFC 6265 に従ったほうがいいと思いました。
後方互換の問題はあると思いますが、なんでこうなっているんでしょうね。
歴史とか経緯あまり知らないので、教えていただけたり、間違いなども指摘くださると助かります :pray:

# 参考

- [Configuring A Spring Boot Application | Baeldung](https://www.baeldung.com/spring-boot-application-configuration)
- [76. Embedded Web Servers](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html)
- [Undertow](http://undertow.io/)
- [Apache Tomcat](http://tomcat.apache.org/)
- [Jetty - Servlet Engine and Http Server - Eclipse](https://www.eclipse.org/jetty/)