LoginSignup
8
3

More than 1 year has passed since last update.

Retrofit+OkHttpでmultipart/form-dataなAPIの呼び出してハマった

Posted at

この記事はラクスアドベントカレンダーの4日目の記事です。

空いているので昨日に引き続き書いていきたいと思います。

はじめに

multipart/form-dataなAPIを呼び出す実装が一番面倒だと思っているのですが、自前でゴリゴリと実装している人が意外と多い印象です。
Retrofit+OkHttpを使ったらどれぐらい簡単なのか試してみたのですがハマり所もあったので記事していきます。

ハマりどころ

@retrofit2.http.Multipart @retrofit2.http.Part @retrofit2.http.Body okhttp3.MultipartBody okhttp3.MultipartBody.Part okhttp3.RequestBody あたりを組み合わせて実装するのですが、まずい組み合わせでハマったり、必要なリクエストヘッダーが指定できそうで出来なかったりと使い方が分かるまで結構大変でした。

StackOverFlowでも反応が多いのでハマっている人の多さが伺い知れます。

まずやっておいた方が良いこと

どんなリクエストデータが作成されたのか見れないとデバッグもままならないのでリクエストボディをログに出すようにInterceptorを仕込んでおくことをお勧めします。

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .build();

これによって、生のリクエスト内容がログに出るので何が起きているか把握できるようになります。
ちなみにmultipart/form-dataのバウンダリーや改行がどう入るのかを見れるので勉強にもなるかと思います。

2021-12-04 22:43:08.014  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : --> POST http://localhost:8080/upload3
2021-12-04 22:43:08.014  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : Content-Type: multipart/form-data; boundary=1f2d4b84-5503-43b1-aa79-2e46bd64074f
2021-12-04 22:43:08.014  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : Content-Length: 343
2021-12-04 22:43:08.015  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : 
2021-12-04 22:43:08.015  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : --1f2d4b84-5503-43b1-aa79-2e46bd64074f
Content-Disposition: form-data; name="json"
Content-Length: 14

{"param": "1"}
--1f2d4b84-5503-43b1-aa79-2e46bd64074f
Content-Disposition: form-data; name="file"; filename="test.csv"
Content-Type: text/csv
Content-Length: 24

no,name
1,taro
2,hanako

--1f2d4b84-5503-43b1-aa79-2e46bd64074f--

2021-12-04 22:43:08.015  INFO 43159 --- [nio-8080-exec-1] okhttp3.OkHttpClient                     : --> END POST (343-byte body)

実装方法その1

MutliPartBodyオブジェクトを自分で組み立てて@Bodyで渡すやり方。
後で出てくる@Multipart@Bodyは組み合わせられないのですが、最初はそれをやろうとしてハマりました。

interface UploadService {
    @POST("upload")
    Call<Map<String, String>> upload(@Body MultipartBody multipartBody);
}

次に呼び出し方。
MutliPartBody.Builder.addFormDataPartを使ってマルチパートのボディ部分を作るのが一番良さそうでした。
それに気づくまではaddPartを使ったり試行錯誤したのですが、思った通りのリクエスト内容にならなくて結構ハマりました。

MultipartBody multipartBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM) //デフォルトはmultipart/mixedなのでmultipart/form-dataに設定し直す
    // Content-Typeを指定したくて試行錯誤した結果がこちら
    // filenameにnullを渡さないといけないのが気持ち悪いけど他に丁度いいインターフェイスがない
    .addFormDataPart("json", null,
        RequestBody.create(MediaType.get("application/json"), "{\"param\": \"1\"}"))
    .addFormDataPart("file", "test.csv",
        RequestBody.create(MediaType.get("text/csv"), new File("/tmp/test.csv")))
    .build();
service.upload(multipartBody).execute();

実装方法その2

今度は@Multipartを使うパターンです。
前述した通り@Bodyとは組み合わせられないので@Partを使います。

interface UploadService2 {
    @Multipart
    @POST("upload3")
    Call<Map<String, String>> upload(@Part MultipartBody.Part json, @Part MultipartBody.Part file);
}

MultipartBody.Partを作ってAPIに渡します。作り方はその1と同じような感じでOKです。

MultipartBody.Part json = MultipartBody.Part.createFormData("json", null,
    RequestBody.create(MediaType.get("application/json"), "{\"param\": \"1\"}"));
MultipartBody.Part file = MultipartBody.Part.createFormData("file", "test.csv",
    RequestBody.create(MediaType.get("text/csv"), new File("/tmp/test.csv")));
UploadService2 service2 = retrofit.create(UploadService2.class);
service2.upload(json, file).execute();

おわり

以上、やりたいことが出来ました。
結局、かなり試行錯誤しないと実装出来ませんでしたが、やり方が分かってしまえばスッキリ短く書けました!

8
3
0

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
8
3