티스토리 뷰
FastAPI에서 AWS S3에 이미지 업로드하는 방법을 공유한다
# S3 버킷 생성
버킷 생성 부분은 따로 다루지 않겠다. 구글에 검색하면 자료가 많이 나오니 참조하면 될 것 같다.
퍼블릭 엑세스를 모두 허용했는데도 access denied가 뜬다면, 권한 정책을 잘못 설정한 것일 가능성이 있으므로 다음을 체크해본다.
# boto3
python에서 aws s3에 접속하기 위해서는 boto3라는 aws sdk를 사용한다.
pip install boto3
먼저 boto3를 인스톨해준다.
s3 = boto3.client(
"s3", aws_access_key_id=AWS_S3_ACCESS_KEY, aws_secret_access_key=AWS_S3_PRIVATE_KEY
)
그런다음 s3 클라이언트를 생성해준다.
이상하게 boto3는 자동완성 기능을 지원하지 않는데, 일부러 동적으로 코드가 생성되게끔 만들어놔서 내부 구현을 못 보게(?) 해놓은 것 같다...
자동완성이 되지 않아도 정상적으로 코드를 작성중인 것이니 안심하고 따라오면 된다.
# 파일 업로드
boto3에서는 파일 업로드에 관하여 다음 두 메서드를 제공한다.
upload_file(파일이름, 버킷이름, s3키값)
upload_fileobj(파일자체, 버킷이름, s3키값)
upload_file은 로컬에 있는 파일을 경로로 업로드하는 함수고, upload_fileobj는 바이트스트림으로 읽어서 바로 업로드하는 메서드이다.
보다 구체적인 차이점은 공식문서나 스택오버플로우를 참조할 것
마찬가지로 자동완성이 안 되어서 답답하지만 참고 진행한다.
여기서는 upload_fileobj를 사용하여 읽은 파일을 바로 업로드하는 코드를 작성해볼것이다.
# 완성 코드
@router.post("/upload")
async def upload(file: UploadFile, directory: str):
if directory not in directories:
raise HTTPException(status_code=400, detail="유효하지 않는 디렉토리에요")
filename = f"{str(uuid.uuid4())}.jpg"
s3_key = f"{directory}/{filename}"
try:
s3.upload_fileobj(file.file, BUCKET_NAME, s3_key)
except (BotoCoreError, ClientError) as e:
raise HTTPException(status_code=500, detail=f"S3 upload fails: {str(e)}")
url = "https://s3-ap-northeast-2.amazonaws.com/%s/%s" % (
BUCKET_NAME,
urllib.parse.quote(s3_key, safe="~()*!.'"),
)
return JSONResponse(content={"url": url})
query param으로 디렉토리 (S3 폴더명)을 받고 해당 디렉토리 + 서버파일명을 생성하여 파일을 저장한다.
파일이름은 uuid로 생성하였고, s3_key는 s3에 저장되는 경로+파일명을 말한다.
ex. images/123asd342-asdfasdf2312-asdfasd.jpg
s3.upload_fileobj(file.file, BUCKET_NAME, s3_key)
FastAPI UploadFile객체의 file 변수는 바이너리타입이라 이를 바로 전달해주면 된다.
url = "https://s3-ap-northeast-2.amazonaws.com/%s/%s" % (
BUCKET_NAME,
urllib.parse.quote(s3_key, safe="~()*!.'"),
)
리턴 값으로는 s3 파일에 접근할 수 있는 url을 리턴하는데, boto3에는 url을 가져올 수 있는 방법이 따로 없다(...)
어차피 s3 url은 정적이므로, 이를 직접 만들어서 리턴하도록 구현하였다.
https://stackoverflow.com/questions/48608570/python-3-boto-3-aws-s3-get-object-url