使用预签名URL上传GZIP压缩JSON至S3的下载异常问题
问题描述
我通过后端生成预签名URL,将gzip压缩后的JSON对象上传至S3存储桶,上传流程正常(Postman手动添加content-encoding: gzip请求头后上传成功),但遇到两个问题:
- 在AWS控制台下载该文件时无法打开,无任何错误提示;
- 通过后端生成的下载预签名URL在Postman访问时,返回错误:
Error: incorrect header check,请求头信息如下:
User-Agent: PostmanRuntime/7.29.2 Accept: */* Postman-Token: d4ecd208-8474-45aa-aecf-3d41caa863f3 Host: <host>.s3.eu-west-2.amazonaws.com Accept-Encoding: gzip, deflate, br Connection: keep-alive
若在AWS控制台移除文件的gzip编码标签,下载和查看功能恢复正常。
上传后端代码:
public String generateUploadLink(String bucketName, String bucketKey, int days) { var objectRequestBuilder = PutObjectRequest.builder().bucket(bucketName).key(bucketKey); objectRequestBuilder.contentType(MediaType.APPLICATION_JSON_VALUE); objectRequestBuilder.contentEncoding(GZIP.name().toLowerCase()); PutObjectRequest objectRequest = objectRequestBuilder.build(); PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder() .signatureDuration(Duration.ofDays(days)) .putObjectRequest(objectRequest) .build(); return this.s3Presigner.presignPutObject(presignRequest).url().toString(); }
问题原因
核心矛盾在于S3文件元数据与实际存储内容的不匹配:
- 上传时设置了
content-encoding: gzip元数据,该字段的作用是告知客户端「此文件已压缩,请自动解压后处理」; - 但实际存储的内容本身就是gzip压缩后的二进制数据,导致客户端触发双重解压逻辑:
- AWS控制台读取元数据后自动解压,因内容已是压缩状态,解压失败导致无法打开;
- Postman请求携带
Accept-Encoding: gzip,S3返回带content-encoding: gzip的响应,Postman自动解压时发现内容格式不匹配,抛出incorrect header check错误。
解决方案
方案1:上传时不设置content-encoding元数据(推荐)
如果仅需存储压缩后的JSON,不需要客户端自动解压,可移除上传代码中的contentEncoding配置,仅保留contentType:
public String generateUploadLink(String bucketName, String bucketKey, int days) { var objectRequestBuilder = PutObjectRequest.builder().bucket(bucketName).key(bucketKey); // 仅设置内容类型,移除编码声明 objectRequestBuilder.contentType(MediaType.APPLICATION_JSON_VALUE); PutObjectRequest objectRequest = objectRequestBuilder.build(); PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder() .signatureDuration(Duration.ofDays(days)) .putObjectRequest(objectRequest) .build(); return this.s3Presigner.presignPutObject(presignRequest).url().toString(); }
上传时仍需确保请求体是gzip压缩后的JSON,Postman可保留content-encoding: gzip请求头(用于告知S3上传内容的编码格式),但S3不会将该字段作为文件元数据保存。下载时客户端将拿到原始压缩数据,可手动解压或在下载预签名URL中配置响应头实现自动解压。
方案2:下载时覆盖content-encoding元数据
若必须保留文件的content-encoding元数据,可在生成下载预签名URL时,移除响应中的编码声明,避免客户端自动解压:
public String generateDownloadLink(String bucketName, String bucketKey, int days) { var getObjectRequest = GetObjectRequest.builder() .bucket(bucketName) .key(bucketKey) // 移除响应中的content-encoding字段 .responseContentEncoding(null) .build(); var presignRequest = GetObjectPresignRequest.builder() .signatureDuration(Duration.ofDays(days)) .getObjectRequest(getObjectRequest) .build(); return s3Presigner.presignGetObject(presignRequest).url().toString(); }
修改后,S3返回的响应不再携带content-encoding: gzip,客户端将直接获取原始压缩文件,可手动解压查看内容。
补充说明
content-encoding元数据的设计目的是告知客户端文件的压缩格式,用于自动解压后处理;若文件本身已是压缩状态,不应设置该元数据;- 若希望客户端下载时自动解压,建议开启S3的服务器端压缩,上传未压缩的JSON,由S3自动压缩并添加正确的
content-encoding元数据。
内容的提问来源于stack exchange,提问作者ALex
相关产品推荐
相关产品推荐

