QuarkusでMultipart Requestの文字化け対策

ちょっと嵌ったのでメモ。

# TL;DR

これで文字化けが直る

```xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-undertow</artifactId>
</dependency>
```

```java
@Provider
public class ContentTypeSetterPreProcessorInterceptor implements ContainerRequestFilter {
    private @Context HttpServletRequest request;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, StandardCharsets.UTF_8.name());
    }
}
```

# 背景

Vue.jsとQuarkusでファイルのアップロード機能を作ったのですが、同じフォームで連携しているタイトルが文字化けする現象が発生いしました。記載してたコードは以下の通り。

クライアント側。`multipart/form-data`を使うために`new FormData()`を使ってます。

```javascript
let data = new FormData();
data.append("title", this.item.title);
data.append("myfile", this.item.files[0]);

this.axios.post(uri, data).then(() => {
    this.$swal({
        icon: "success",
        text: "Upload Success!"
    });
})
```

サーバ側。JAX-RSの`@MultipartForm`を使います。タイトルもファイルも自動でBeanにマッピングされます。

```java
public class MyFileFormBean {
    @FormParam("title")
    @PartType(MediaType.TEXT_PLAIN)
    private String title = null;

    @FormParam("myfile")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    private byte[] myfile = null;
....
```

JAX-RS側。

```java
@POST
@Consumes({MediaType.MULTIPART_FORM_DATA})
@Produces(MediaType.APPLICATION_JSON)
public Response upload(@MultipartForm MyFileFormBean form) throws IOException {
    System.out.println("title: " + form.getTitle());
...
}
```

と、これで上手くいく予定だったのですが、なぜだか日本語が文字化け。。。

```bash
title: ????????????
```

# 原因

調べてみるとRESTEasyでは文字コードが含まれてない時はデフォルトではASCII(ISO-8859-1)に変換されるようです。
Formに`accept-charset`を指定すれば良いっぽいですがJSでやるのはちょっとめんどそうなので、サーバで強制変換するのがよさそうです。

ググったら[JBoss Communityにサンプル実装がありました](https://docs.jboss.org/resteasy/docs/4.5.6.Final/userguide/html_single/index.html#multipart_overwrite_default_charset)。

```java
@Provider
@ServerInterceptor
public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor {
    public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
            throws Failure, WebApplicationException {
        request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8");
        return null;
    }
}
```

なんですが、Quarkusに使われているRestEasyはJAX-RS 2.0準拠なので上記の書き方は出来ません。なので`PreProcessInterceptor`の代わりに`ContainerRequestFilter`を使う必要があります。

# 対策

サンプルを参考にしつつJAX-RS 2.0準拠で書き直します。
Quarkusはデフォルトでは[Servlet compatibility](https://quarkus.io/guides/rest-json#servlet-compatibility)に記載があるようにVert.x HTTP serverを使うのでHttpServletRequestが使えないです。なので`quarkus-undertow`を依存に追加します。

```xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-undertow</artifactId>
</dependency>
```

JAX-RS 2.0ベースでリクエストをフックして`DEFAULT_CHARSET_PROPERTY`を`UTF-8`に強制変換するのは以下のコードになります。

```java
@Provider
public class ContentTypeSetterPreProcessorInterceptor implements ContainerRequestFilter {
    private @Context HttpServletRequest request;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, StandardCharsets.UTF_8.name());
    }
}
```

`@Provider`を設定しておけば特に設定ファイルの追加等はいらないです。これで文字化けは解決しました。

# まとめ

JavaServletで文字化けするのでフィルタを書くのは昔はあるあるだったのですが、まさか現代でもやるとは思わず少し嵌りました。ファイルのアップロードは相変わらず罠がいっぱいですね。
RFCとは違うとはいえUTF-8はASCII領域は同じコードだし、UTF-8がデフォルトでも良い気はします。

GitHubのIssueでも"[UTF-8 encoding problem with MultiPart/RestEasy](https://github.com/quarkusio/quarkus/issues/10323)"として話題に上がってるので、設定ファイルで簡単に切り替えれるように将来的にはなるかもしれないです。

それではHappy Hacking!