TIL 20210614 presigned url로 client side에서 직접 파일 업로드

jiffydev·2021년 6월 16일
1

현재 사용자가 파일을 업로드하면 s3에 저장되도록 하고 있는데, 파일이 5mb가 넘어가면 timeout이 발생한다는 것을 발견했다.

이는 현재 사용하고 있는 lambda 서버의 payload 용량이 6mb여서 업로드가 안되는 것으로 보였다.

이 포스팅에서 presigned url을 사용해 클라이언트에서 직접 s3에 업로드가 가능한 방식을 발견해, 기능을 추가하기로 했다.

이 방법을 사용하면 백엔드 api를 거치지 않고 클라이언트쪽에서 직접 s3에 업로드 하므로 payload 크기에 구애받지 않을 수 있다. 또한 s3 버킷에 직접 접근하는 것이 아니라 presigned url을 통해 접근하므로 보안 측면에서도 문제가 없을 것이라 판단했다.

현재는 사용자가 파일을 업로드하게 되면 클라이언트에서 파일 자체를 POST로 백엔드 API에 넘기고, 백엔드에서 s3 API를 호출해 업로드하는 방식을 사용하고 있는데, presigned url을 사용하면 다음과 같은 흐름으로 업로드가 된다.

s3 버킷의 세팅은 이미 되어 있는 것으로 가정하겠다. 다만 버킷의 CORS 세팅은 나중에 언급할 것이다.

client side code

uploadImage = (file: any) => {

  const params = {
    file_type: file.type,
    file_name: file.name
  }

  const options = {
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
  }

  return axios.post(
    'https://[domain]/api/v1/xxxxxxx', params, options)
    .then(res => {
      const img_options = {
        headers: {
          'Content-Type': file.type
        }
      };
      return axios.put(res.data.file_upload_url, file, img_options);
    })
}

출처: https://ja.wayama.io/article/library/react/s3upload/

위 코드에서는 서버에 POST로 보낸 요청에 대한 응답을 가지고 s3에 PUT으로 파일을 전송했는데, 작업할 때는 PUT 메소드 부분만 참고했다.

한 가지 주의할 점은 s3에 업로드 시 PUT 메소드를 사용한다는 것이다.

POST 메소드로 업로드 할 경우에는 밑에서 언급될 함수와는 다른 함수를 사용해서 url을 가져와야 한다.

server side code

presigned url을 생성하는 코드는 다음과 같다.

import boto3
from botocore.exceptions import ClientError
from botocore.config import Config
import requests

def create_presigned_upload(expiration=3600):
    # Generate a presigned S3 PUT URL
    s3_client = boto3.client("s3", config=Config(signature_version='s3v4'),
                             region_name="us-west-2",
                             aws_access_key_id="ACCESS_KEY",
                             aws_secret_access_key="SECRET_KEY",
                             aws_session_token="SESSION_TOKEN")
    try:
        response = s3_client.generate_presigned_url('put_object',
                   Params= {'Bucket': "BUCKET_NAME",
                            "Key":"OBJECT_KEY"},
                            ExpiresIn=3600)
    except ClientError as e:
        return None

    # The response contains the presigned URL and required fields
    return response

이렇게 해서 나온 url은 다음과 같은 형태이다.

https://experiment.s3.amazonaws.com/3eb426c2-8b03-4df0-ac8b-2818d7/0185-43b6-4e82-90-f80ddeb/Dockerfile.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAYBAO6D35IL7EVWWQ%2F20201203%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20201203T160918Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Security-Token=FwoGZXIvYXdzEEkaDBMMvCnwF6gDfBdJJyKNAkWcPF2UGsEKr%2BhTII0%2Fea0BnYVEfD%2B715iFASMgmQajg3D%2FcOYV5y975wzaUDCTESEt8VKQjUWb3sayTlDp6Hs2LuPoII92n%2FmOFZPxvBqU43FBYndpIVmOVg1vKno%2Bj7pYaoYzSdpAEIVv4yb5Bg%2BBiMT2x3E7GG771%2Fq4mi1jWF8lqf6QkyTT9qpLEiFQDxSGH47nT%2BzcoNmpHLLdHSTMDJsmDpiHiuvczQdSroBR6I9%2BksN7Lm3k1cKM1XAw3JFN%2BKmyAX%2BDuR5cGpNUWQehl1OxXHZx7%2F9BLsWgF6Nf7vj5vLc5e%2BvcuUwsmSliKsQvnnXM2zvzT2LK5wBm3qeKcxiyX1994U9jt%2BemKJOQpP4FMiv8IKTD07lWgWZN8uYrU2II4O5YUE8hhiRGCIch8v4b3mfPoVp%2B7OYKVkVn&X-Amz-Signature=812952fe5fadf0174ea95a2eadaa88c7526613b29c76e865d4548952fa55dc64

이 url로 클라이언트에서 PUT 메소드로 파일을 넣어 요청하면 업로드가 가능하다.

출처: https://python.plainenglish.io/access-files-from-aws-s3-using-pre-signed-urls-in-python-f1b2f66bdd57

기타

1. CORS 에러

코드를 작성하고 업로드를 시도하였을 때 CORS에러가 발생했다.
버킷 정책에서 CORS관련 정책을 확인하여 다음처럼 PUT 메소드가 허가되어 있는지 확인하면 된다.

   <?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET, PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

2. Content-Type 관련

만약 위 CORS 정책 중 AllowedHeader가 포함되어 있지 않다면, Content-Type, Authorization 등의 헤더를 넣을 시 CORS 에러가 뜰 것이다. 따라서 필요한 헤더를 추가해 주면 된다.

profile
잘 & 열심히 살고싶은 개발자

0개의 댓글