Lambda函数文件上传正常,API Gateway下无法正常上传
解决FastAPI+Lambda通过API Gateway代理上传图片失败的问题
问题场景
开发了一个用于网站图片上传的FastAPI端点,部署到Lambda函数后直接访问端点时文件上传功能正常,但通过API Gateway代理访问时,图片无法正确上传。对比两者的请求日志,发现API Gateway未携带content-length请求头。
原代码如下:
from uuid import uuid4 import boto3 from fastapi import APIRouter, File, HTTPException, Request, UploadFile from loguru import logger from app.core.config import ImageRouterConfig def upload_image_to_s3(file: UploadFile, bucket_name: str, cloudfront: str) -> str: s3 = boto3.resource(service_name="s3") filename = f"{uuid4()}{file.filename}" s3_key = f"images/{filename}" try: file.file.seek(0) s3.Bucket(bucket_name).upload_fileobj( Fileobj=file.file, Key=s3_key, ExtraArgs={"ContentType": file.content_type, "ACL": "public-read", "ContentEncoding": "base64"}, ) except Exception as e: raise HTTPException(status_code=500, detail="Could not upload file.") from e return f"{cloudfront}/{s3_key}" def create_images_router(config: ImageRouterConfig) -> APIRouter: router = APIRouter() @router.post("") def post_upload_file(request: Request, file: UploadFile = File(...)) -> str: logger.info(f"Uploading file {file.filename}") logger.info(f"Request headers: {request.headers}") logger.info(f"Content type: {file.content_type}") s3_url = upload_image_to_s3(file, config.bucket_name, config.cloudfront_url) return s3_url return router
问题原因
- API Gateway代理
multipart/form-data请求时,默认不会传递content-length请求头,FastAPI的UploadFile依赖该头来完整读取文件内容,导致通过网关上传时文件读取不完整。 - 原代码中设置了
ContentEncoding: base64,但上传的图片本身并非Base64编码格式,会导致S3存储的文件损坏,无法正常显示。
解决方案
1. 配置API Gateway启用二进制媒体类型
- 登录AWS控制台,进入API Gateway管理页面,找到目标API。
- 切换到设置标签页,在二进制媒体类型中添加
multipart/form-data。 - 重新部署API到对应的阶段,确保配置生效。
2. 调整API Gateway集成请求设置
- 在API Gateway的集成请求配置中,将内容处理设置为通过(Passthrough),避免API Gateway对请求体进行Base64编码或修改。
- 若使用Lambda代理集成,确认请求体完整传递,无额外处理逻辑。
3. 修改FastAPI代码适配流式读取
由于API Gateway可能以流式方式传递请求体(无content-length),需要先将文件内容完整读取到内存再上传到S3,同时移除错误的编码设置:
修改后的代码:
from uuid import uuid4 import io import boto3 from fastapi import APIRouter, File, HTTPException, Request, UploadFile from loguru import logger from app.core.config import ImageRouterConfig def upload_image_to_s3(file: UploadFile, bucket_name: str, cloudfront: str) -> str: s3 = boto3.resource(service_name="s3") filename = f"{uuid4()}{file.filename}" s3_key = f"images/{filename}" try: # 将文件内容读取到内存,适配无content-length的流式场景 file_content = io.BytesIO(file.file.read()) file_content.seek(0) s3.Bucket(bucket_name).upload_fileobj( Fileobj=file_content, Key=s3_key, ExtraArgs={"ContentType": file.content_type, "ACL": "public-read"}, # 移除ContentEncoding: base64,避免非编码文件损坏 ) except Exception as e: raise HTTPException(status_code=500, detail="Could not upload file.") from e return f"{cloudfront}/{s3_key}" def create_images_router(config: ImageRouterConfig) -> APIRouter: router = APIRouter() @router.post("") def post_upload_file(request: Request, file: UploadFile = File(...)) -> str: logger.info(f"Uploading file {file.filename}") logger.info(f"Request headers: {request.headers}") logger.info(f"Content type: {file.content_type}") s3_url = upload_image_to_s3(file, config.bucket_name, config.cloudfront_url) return s3_url return router
4. 验证配置
- 在Lambda日志中打印完整的请求事件,确认请求体是否正确传递。
- 检查API Gateway阶段设置,确保未启用请求体压缩等可能修改请求的配置。
总结
通过启用API Gateway的二进制支持、调整集成请求设置,同时修改代码适配流式文件读取并移除错误的编码配置,即可解决通过API Gateway代理上传图片失败的问题。
内容的提问来源于stack exchange,提问作者Tom McLean
相关产品推荐
相关产品推荐

