민프
[AWS] API-Gateway + Lambda를 이용하여 S3에 이미지 업로드를 해보자 본문
이번 글에서는 API Gateway + Lambda + S3를 활용하여 사용자가 이미지를 업로드하면 Lambda가 이를 S3 버킷에 저장하는 아키텍쳐를 구축한 과정을 알려드리겠습니다
실습 과정
- 클라이언트가 multipart/form-data 형식으로 이미지를 POST로 전송
- API Gateway가 요청을 받아 Lambda를 호출
- Lambda는 S3 버킷에 이미지를 저장
- 저장 후 결과를 반환 (성공/실패 여부)
구현 과정
1. S3 버킷 생성
2. Lambda 함수 구성 (Python 3.11)
python에서 multipart/form-data를 받기 위하여 toolbelt-layer를 계층으로 삽입하였습니다.
// Python디렉토리 생성 및 패키지 설치
mkdir -p python
pip install requests-toolbelt -t python
// 압축
zip -r9 toolbelt-layer.zip python
from requests_toolbelt.multipart import decoder
def lambda_handler(event, context):
import boto3
import base64
import uuid
from requests_toolbelt.multipart import decoder
bucket = '버킷 이름을 넣으시면 됩니다.'
file_name = f"menu/{uuid.uuid4().hex}.jpg" // 폴더 이름 및 파일이름 지정
body = base64.b64decode(event["body"])
content_type = event["headers"].get("Content-Type") or event["headers"].get("content-type")
multipart_data = decoder.MultipartDecoder(body, content_type)
for part in multipart_data.parts:
if b'image' in part.headers.get(b'Content-Type', b''):
s3 = boto3.client('s3')
s3.put_object(
Bucket=bucket,
Key=file_name,
Body=part.content,
ContentType='image/jpeg'
)
return {
"statusCode": 200,
"body": f"Uploaded as {file_name}"
}
return {
"statusCode": 400,
"body": "No image found in multipart data"
}
3. API-gateway 생성 및 설정
-1. 리소스 생성
-2. 메서드 생성
위에서 만든 Lambda를 연결해주세요.
-3. 이진 미디어 유형 추가
API를 선택한 후 -> API 설정 -> 미디어 유형 관리에서 'multipart/form-data'추가
-4. 리소스 설정
리소스 설정을 하기에 앞서 위 사진의 각 단계가 어떤 것들을 의미하는지부터 알아보겠습니다.
1. 메서드 요청 (Method Request)
- 클라이언트가 API Gateway에 보내는 요청을 다룹니다.
- 여기에서 설정 가능한 것들
- HTTP 메서드 (GET, POST 등)
- URL 경로 파라미터, 쿼리 스트링, 헤더
- IAM 인증, API Key, CORS 설정 등
- 예를 들어서)
/menu-ai?type=jpeg의 요청에서 type을 파라미터로 받을지 여부 등을 설정
2. 통합 요청 (Integration Request)
- API Gateway가 백엔드 서비스(Lambda 등)로 요청을 보내기 전 처리하는 단계입니다.
- 실제로는 여기서 Lambda에 전달할 payload 가공(매핑), 포맷 설정, 이진 처리를 합니다.
예를 들어서) form-data로 받은 이미지를 base64로 인코딩하거나, S3에서 쓸 key 이름으로 변환하는 작업
저는 여기서 매핑 템플릿을 넣었습니다.
아래 템플릿은 multipart/form-data로 업로드된 이진 데이터를 API Gateway가 Lambda가 이해할 수 있는 JSON 형태로 변환해주는 역할을 합니다.
#set($inputRoot = $input.path('$'))
{
"body": "$input.body", -> 클라이언트로부터 받은 실제 body 내용(이진)
"isBase64Encoded": true, -> Lambda에서 base64로 디코딩해서 파일로 처리할 수 있게 명시
"headers": { -> 요청 헤더들을 모두 Lambda에 넘겨줌 (예: Content-Type, boundary 등)
#foreach($header in $input.params().header.keySet()) -> 전체 헤더를 동적으로 key:value 쌍으로 만들어 줌
"$header": "$util.escapeJavaScript($input.params().header.get($header))"
#if($foreach.hasNext),#end
#end
}
}
이렇게 매핑 템플릿을 만들게 되면 Lambda함수에서는 아래와 같이 받게 됨으로써 base64.b64decode(event['body'])로 파일을 복원
할 수 있게 됩니다.
{
"body": "base64로 인코딩된 이미지 본문",
"isBase64Encoded": true,
"headers": {
"Content-Type": "multipart/form-data; boundary=..."
}
}
3. Lambda 통합 (Integration Target)
- 실제 Lambda 함수 또는 HTTP, VPC 등 연결된 백엔드 서비스가 실행되는 지점입니다.
- 위에서 준비한 payload를 기반으로 Lambda가 호출됩니다.
4. 통합 응답 (Integration Response)
- Lambda의 실행 결과를 받아 API Gateway가 해석하는 단계입니다.
- 주로 여기서 상태코드 매핑 (200, 500 등), 응답 body 가공, 에러 포맷 등을 처리합니다.
예를 들어서) Lambda가 { statusCode: 500 }을 반환했을 때 이를 클라이언트에게 어떻게 보여줄지 설정 가능
5. 메서드 응답 (Method Response)
- API Gateway가 클라이언트에게 최종적으로 전달할 응답을 구성하는 단계입니다.
- 클라이언트에 보낼 HTTP 상태코드, 헤더, JSON 스키마 구조 등을 지정할 수 있습니다.
예를 들어서) 응답 header에 Content-Type: application/json을 넣거나, CORS 허용 헤더를 설정하는 곳
저는 아래와 같이 하였습니다.
-5. API 배포 및 스테이지 CloudWatch 설정
API 배포를 하시면 스테이지가 생성되는데 이 부분에서 로그들을 추적하고 싶으시다면
'스테이지 -> 로그 및 추적 -> 편집' 에 차례대로 접근하시면 되는데
관련 권한 에러가 나올 수 있으니 나오는 권한을 IAM에서 추가해주시면 됩니다.
예시 정리
자 그럼 위 설정에 대한 예시를 다시 한번 정리해보겠습니다.
POST /menu-ai로 이미지 업로드 요청 시
- 메서드 요청: 사용자 요청을 받아들임 (form-data, header 등)
- 통합 요청: 요청 body를 Lambda가 이해할 수 있는 JSON 형식으로 변환
- Lambda 통합: Lambda 함수 실행, S3에 이미지 저장
- 통합 응답: Lambda 응답의 statusCode, body를 분해 및 가공
- 메서드 응답: 최종 응답 구조로 만들어 사용자에게 반환
결과
postman으로 요청했고, s3에 잘 저장된 것을 확인하실 수 있습니다.