ちょっと嵌ったのでメモ。
TL;DR
これで文字化けが直る
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow</artifactId>
</dependency>
@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()
を使ってます。
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にマッピングされます。
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側。
@POST
@Consumes({MediaType.MULTIPART_FORM_DATA})
@Produces(MediaType.APPLICATION_JSON)
public Response upload(@MultipartForm MyFileFormBean form) throws IOException {
System.out.println("title: " + form.getTitle());
...
}
と、これで上手くいく予定だったのですが、なぜだか日本語が文字化け。。。
title: ????????????
原因
調べてみるとRESTEasyでは文字コードが含まれてない時はデフォルトではASCII(ISO-8859-1)に変換されるようです。
Formにaccept-charset
を指定すれば良いっぽいですがJSでやるのはちょっとめんどそうなので、サーバで強制変換するのがよさそうです。
ググったらJBoss Communityにサンプル実装がありました。
@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に記載があるようにVert.x HTTP serverを使うのでHttpServletRequestが使えないです。なのでquarkus-undertow
を依存に追加します。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow</artifactId>
</dependency>
JAX-RS 2.0ベースでリクエストをフックしてDEFAULT_CHARSET_PROPERTY
をUTF-8
に強制変換するのは以下のコードになります。
@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"として話題に上がってるので、設定ファイルで簡単に切り替えれるように将来的にはなるかもしれないです。
それではHappy Hacking!