Android、kotlon、RetrofitでHTTP multipart/form-dataでファイルを送信してみる

# HTTP POST multipart/form-dataでファイルを送信してみる
前回、RetrofitでJSON over HTTPで送受信をしてみましたが、今回はHTTP POST multipart/form-dataでファイルを送信してみます。

https://qiita.com/cozyk100/items/400d1b2bdaa48364cd6a

## Serviceのインタフェース
ミソは@Post、@Multipartアノテーションをメソッドに付けます。
引数の送信するファイルは@Partアノテーションを付けます。引数の型はokhttp3.MultipartBody.Partになります。
```kotlin:SampleService.kt
interface MediCodeSendService {
・・・

    @POST
    @Multipart
    suspend fun doUpload(
        @Url url: String,
        @Part part: MultipartBody.Part
    ): Response<Unit>
}
```
戻り値の型ですが、ファイルをアップロードする場合でHTTPのレスポンスBodyがない場合、Responseの型引数はUnitとなります。ファイルをアップロードは成功したか、失敗したかなのでHTTP Statusで足りるのでレスポンスBodyが何も返らない場合があります。

Retrofitの公式ではあまりこの辺は詳しくかかれていませんが（そもそもドキュメントが貧弱）、[Retrofitの公式ページの FORM ENCODED AND MULTIPART](https://square.github.io/retrofit/)に書かれています。

## Repositoryクラス
Repositoryクラスの初期化の部分は前回と同じです。ファイルアップロードのメソッドだけ追加します
```kotlin:SampleRepository.kt

class SampleRepository(baseUrl: String, timeout: Long) {
    /** サービス */
    private var service: SampleService

    init {
        // ロギング
        val logging = HttpLoggingInterceptor()
        logging.setLevel(HttpLoggingInterceptor.Level.BASIC)
        val client = OkHttpClient.Builder()
            .readTimeout(timeout, TimeUnit.SECONDS)
            .connectTimeout(timeout, TimeUnit.SECONDS)
            .addInterceptor(logging)
            .build()
        val moshi = Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()
        service = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(client)
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .build()
            .create(SampleService::class.java)
    }
・・・

    suspend fun doUplaod(uploadUrl: String, datFile: File): Pair<String, String> {
        val part = MultipartBody.Part.createFormData(
            "data", "testData.csv", datFile.asRequestBody("text/csv".toMediaType())
        )
        val response = service.doUpload(uploadUrl, part)
    }

}
```
Repositoryクラスの中でFileからMultipart/form-dataに相当する、MultipartBody.Partのインスタンスを作ってやらないといけません。
```kotlin
val part = MultipartBody.Part.createFormData(
            "data", "testData.csv", datFile.asRequestBody("text/csv".toMediaType())
```
の部分がそれに相当しますが、ここはOkHttp3の機能なのでRetrofitの公式ページには触れられていません。
MultipartBody.Part.createFormDataの
* 第一引数が、Content-Dispositionのname属性
* 第二引数が、Content-Dispositionのfilename属性
* 第三引数が、ファイルの実体、asRequestBody(・・・)が、Content-Type: text/csvに相当

となります。
```
Content-Disposition: form-data; name="data"; filename="testData.csv"
Content-Type: text/csv
```

## HTTP Heaerを付けてみる
HTTP request headerを付けて送る方法は[Retrofitの公式ページの HEADER MANIPULATION](https://square.github.io/retrofit/)に書かれています。

HTTP headerを1個だけ、固定で送信する場合
```kotlin
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
```
HTTP headerを複数、固定で送信する場合
```kotlin
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
```
HTTP headerを1個だけ、引数に含めて可変で送信する場合
```kotlin
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
```
HTTP headerを複数、引数に含めて可変で送信する場合
```kotlin
@GET("user")
Call<User> getUser(@HeaderMap Map<String, String> headers)
```

response headerは戻り値のResponseから取得します。
```kotlin
val response = service.doUpload(uploadUrl, part)
response.headers().forEach {header ->  
    println("${header.first} = ${header.second}") // 全部
}
val hogeHeader = response.headers()["X-HOGE-HEADER"] ?: "" // 1個だけ狙い撃ち
```