LoginSignup
2
0

More than 3 years have passed since last update.

QuarkusでMultipart Requestの文字化け対策

Posted at

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

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_PROPERTYUTF-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!

2
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0