AWS Lambda Java实现S3文件下载返回API网关响应损坏问题求助
错误原因
- 响应头配置错误:错误设置
Content-Type为application/json,二进制下载响应应该设置为和文件匹配的MIME类型,不确定的情况下统一用application/octet-stream即可;同时你在响应头里加了请求头Accept,完全无效属于冗余配置,会干扰客户端解析。 - S3流未正确关闭:读取完S3对象的字节流后没有显式关闭
S3ObjectInputStream和S3Object,容易出现流截断、字节读取不全的问题,直接导致文件损坏。 - 代码语法错误:注解
@Overrider拼写错误,应为@Override;同时方法内大括号位置错误,头部逻辑结束后提前闭合了方法块,实际运行会有编译问题。 - 冗余的JSONObject逻辑:构造的
response对象完全没有被使用,属于无效代码。 - 硬编码密钥风险:Lambda运行环境自带权限凭证链,不需要手动写AK/SK,给Lambda执行角色绑定S3只读权限即可,硬编码密钥存在严重泄露风险。
- API Gateway配置缺失:即使代码设置了
isBase64Encoded=true,也需要在API Gateway控制台的「二进制媒体类型」配置中添加需要支持的类型,比如*/*或者application/octet-stream,否则API Gateway会将Base64编码的内容当做普通字符串处理,导致内容转义损坏。 - Content-Disposition文件名未编码:如果文件名包含中文、空格等特殊字符,会导致文件名乱码,建议用URLEncoder编码后再填入响应头。
修正后的代码
import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectInputStream; import java.io.IOException; import java.net.URLEncoder; import java.util.Base64; import java.util.HashMap; import java.util.Map; public class DownloadFileHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { private static final String BUCKET_NAME = "你的bucket名称"; @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) { String fileName = event.getQueryStringParameters().get("fileName"); byte[] fileBytes = new byte[0]; // 使用Lambda默认凭证链,不需要手动配置AK/SK AmazonS3 s3Client = AmazonS3ClientBuilder.standard() .withRegion(Regions.US_EAST_2) .build(); S3Object objectFile = null; S3ObjectInputStream objectInputStream = null; try { GetObjectRequest request = new GetObjectRequest(BUCKET_NAME, fileName); objectFile = s3Client.getObject(request); objectInputStream = objectFile.getObjectContent(); fileBytes = objectInputStream.readAllBytes(); } catch (IOException e) { e.printStackTrace(); return new APIGatewayProxyResponseEvent() .withStatusCode(500) .withBody("文件读取失败"); } finally { // 显式关闭流,释放资源 if (objectInputStream != null) { try { objectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (objectFile != null) { try { objectFile.close(); } catch (IOException e) { e.printStackTrace(); } } } Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/octet-stream"); try { String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); headers.put("Content-Disposition", String.format("attachment; filename=%s", encodedFileName)); } catch (Exception e) { headers.put("Content-Disposition", String.format("attachment; filename=%s", fileName)); } headers.put("Content-Length", String.valueOf(fileBytes.length)); return new APIGatewayProxyResponseEvent() .withHeaders(headers) .withStatusCode(200) .withIsBase64Encoded(true) .withBody(Base64.getEncoder().encodeToString(fileBytes)); } }
额外注意事项
- 部署后记得在API Gateway控制台配置二进制媒体类型,配置完成后需要重新部署API才能生效
- 确保Lambda执行角色的IAM权限包含
s3:GetObject权限,对应的资源范围包含你的S3桶路径 - 如果文件大小超过6MB,不建议用Lambda直接返回,应该生成S3预签名链接返回给客户端,让客户端直接从S3下载,避免触发Lambda和API Gateway的 payload 大小限制
内容的提问来源于stack exchange,提问作者noname
相关产品推荐
相关产品推荐

