なんかのLog

boto3で署名URLを生成してJSでS3にアップロードする

🗓️ 2022-08-25
📑 Post 

意外にはまったのでメモします

FastAPIでシンプルなAPIを作るために四苦八苦です。

ファイルアップロードにFormDataを使っていたんですがローカルでは受け取り成功していたのに本番環境だとFastAPI側でバリデーションエラーが返されました。原因を探ろうにもログもでないし再現できないのでFormDataを諦めて署名URLでアップロードすることにしました。そしてこれはこれではまった。

boto3には高機能メソッドを扱うresouceと低機能メソッドを扱うclientがあるらしいのですが、いろんなサンプルで混ざっていてそこから混乱した。

署名URLの機能はclientにしかメソッドがなさそうなのでclientを使います。雰囲気コードです

import boto3

def s3_client():
    return boto3.client('s3',
       region_name=AWS_REGION_NAME,
       aws_access_key_id=AWS_ACCESS_KEY_ID,
       aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
       config=Config(signature_version='s3v4'),
   )

def generate_presigned_url(client, key):
    return client.generate_presigned_url(
        ClientMethod='put_object',
        Params={
            'Bucket': AWS_S3_BUCKET_NAME,
            'Key': key,
        },
        ExpiresIn=60 * 5,
    )

# endpoint
@router.post("/upload")
def generate_sign_url(body = Body(...)) -> Any:
    if 'filename' not in body:
        raise HTTPException(status_code=422, detail="require filename")

    client = s3_client()
    key = f"sample/{body['filename']}"
    url = generate_presigned_url(client, key)

    return { "url": url }

JSサイド。こちらも雰囲気です

const el = document.getElementById('upload-file') // 適当なinputタグにファイルがあるとして
const file = el.files[0]

const _fetch = await fetch("https://example.com/upload")
const res = _fetch.json()

await fetch(res.url, {
    method: 'PUT',
    headers: {
      'Content-Type': file.type,
    },
    body: file,
  })

JS側、署名URLに対してどうやってアップロードすればいいのかよくわからなくてはまった。検索だとFormDataにfileやらdataやらのkey名で投げていた。リクエスト自体は成功するけどファイルがぶっ壊れる。bodyをすべてファイルにしているためだと推測してそのまま投げるようにした。これでファイルは正常になった。

APIドキュメントかなんかに書いてあると思うんだけど見つけられなかった。つらい

フロントからアップロードする場合、S3のバケットにCORS設定がいるのでついでにメモしておきます。XMLからJSONに形式変わっててこれもドキュメントどこってなりました。

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT"
        ],
        "AllowedOrigins": [
            "https://example.com",
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    }
]

ref: https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ManageCorsUsing.html

🏷️ #python