🙅‍♂️

AndroidからPresigned URLを使ってS3に画像をアップロードする

2024/05/02に公開

業務にてPresigned URLを用いたアップロードを行ったのですが一部詰まったところがあったため記事に起こします。

Presigned URLとは

PresignedURLとはAWSのS3に関する機能の一つです。
特別な権限なしにS3へのアップロード・ダウンロードを行うことができる署名付きURLを発行してくれます。

Presigned URLを使うことで以下のような恩恵を受けることができます。

  • クライアントサイドから直接画像のアップロード・ダウンロードを行うことができる
  • URLに有効期限があるため、セキュリティリスクも比較的低く抑えることができる

Presigned URLを用いるフロー

以下にPresigned URLを用いてS3にオブジェクトを上げるまでのシークエンス図を示します。

Presigned URLを用いてオブジェクトを上げるまでの流れ

画像がうまく送れない(詰まったところ)

今回詰まったのはステップ5の「URLに対してPUTメソッドを用いてオブジェクトを送る」部分です。
動作しないコードを以下に示します。
なお、通信用ライブラリとしてOkHttpを利用しています。

動作しない.kt
fun uploadFile(presignedUrl: String, file: File) {
        val requestBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart(
                name = "file",
                filename = file.name,
                body = file.asRequestBody("image/png".toMediaTypeOrNull()),
            )
            .build();
        val request = Request.Builder()
            .url(presignedUrl)
            .put(requestBody)
            .build()
        okHttpClient.newCall(request).execute()
}

このコードを使ってpng画像をアップロードするとS3に上げることができます・・・が、画像データが壊れてしまいます。

試行錯誤した結果、Presigned URLではヘッダー情報を最小限にする必要があることがわかりました。
ヘッダー情報を載せた場合、画像データに合わせてヘッダー情報も載ってしまった結果壊れていたようです。

正しくは以下のようなコードになります。

正常に動作する.kt
fun uploadFile(presignedUrl: String, file: File) {
+         val requestBody = file.asRequestBody("image/png".toMediaTypeOrNull())
-        val requestBody = MultipartBody.Builder()
-            .setType(MultipartBody.FORM)
-            .addFormDataPart(
-                name = "file",
-                filename = file.name,
-                body = file.asRequestBody("image/png".toMediaTypeOrNull()),
-            )
-            .build();
        val request = Request.Builder()
            .url(presignedUrl)
            .put(requestBody)
            .build()
        okHttpClient.newCall(request).execute()
}

Discussion