在测试将文件从ec2上传到s3的应用程序时,我遇到了一个前所未有的异常。内容是:
Exception in thread "Thread-1" com.amazonaws.services.s3.model.AmazonS3Exception: The Content-MD5 you specified did not match what we received. (Service: Amazon S3; Status Code: 400; Error Code: BadDigest; Request ID: 972CB8E04388AB20), S3 Extended Request ID: T7bmFnQ2RlGWlJD+aGYfTy97XZw88pbQrwNB8YCezSjyq6O2joxHRP/6ko+Q2zZeGewkw4x/90k=
at com.amazonaws.http.AmazonHttpClient.handleErrorResponse(AmazonHttpClient.java:1383)
at com.amazonaws.http.AmazonHttpClient.executeOneRequest(AmazonHttpClient.java:902)
at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:607)
at com.amazonaws.http.AmazonHttpClient.doExecute(AmazonHttpClient.java:376)
at com.amazonaws.http.AmazonHttpClient.executeWithTimer(AmazonHttpClient.java:338)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:287)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:3676)
at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1439)
at com.amazonaws.services.s3.transfer.internal.UploadCallable.uploadInOneChunk(UploadCallable.java:131)
at com.amazonaws.services.s3.transfer.internal.UploadCallable.call(UploadCallable.java:123)
at com.amazonaws.services.s3.transfer.internal.UploadMonitor.call(UploadMonitor.java:139)
at com.amazonaws.services.s3.transfer.internal.UploadMonitor.call(UploadMonitor.java:47)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
我能做些什么来修复这个错误?我在应用程序中使用了与以前相同的代码。
我想我已经解决了我的问题。我终于发现我的一些文件在上传过程中发生了变化。因为文件是由另一个线程生成的,所以上传和生成是同时完成的。文件不能立即生成,在生成文件的过程中,它可能同时在上传,文件在上传过程中实际上发生了变化。
文件的md5是AmazonS3Client在上传开始时创建的,然后整个文件被上传到S3,此时文件与开始上传的文件不同,所以md5实际上发生了变化。我把程序修改成了单线程程序,这个问题再也没有出现过。
出现此问题的另一个原因是运行这样的代码(python)
with open(filename, 'r') as fd:
self._bucket1.put_object(Key=key, Body=fd)
self._bucket2.put_object(Key=key, Body=fd)
在这种情况下,当文件对象(fd)到达第3行时,它指向文件的末尾,因此我们将得到"ContentMD5"错误,为了避免它,我们需要将文件读取器指向文件中的起始位置
with open(filename, 'r') as fd:
bucket1.put_object(Key=key, Body=fd)
fd.seek(0)
bucket2.put_object(Key=key, Body=fd)
这样我们就不会得到前面提到的Boto错误。
FWIW,我设法找到了一种完全不同的方式来触发这个问题,这需要一个不同的解决方案。
事实证明,如果您决定显式地将ObjectMetadata
分配给PutObjectRequest
,例如指定cacheControl
设置或contentType,那么AWS SDK会对ObjectMetadata
实例进行变异,以隐藏它为put请求计算的MD5。这意味着,如果您要放置多个对象,您认为所有对象都应该分配相同的元数据,那么您仍然需要为每个PutObjectRequest
创建一个新的ObjectMetadata
实例。如果你不这样做,那么它会重用从上一个put请求中计算出的MD5,并且在你试图放入的第二个对象上会出现MD5不匹配的错误。
所以,明确地说,这样做在第二次迭代时会失败:
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("text/html");
for(Put obj: thingsToPut)
{
PutObjectRequest por =
new PutObjectRequest(bucketName, obj.s3Key, obj.file);
por = por.withMetadata(metadata);
PutObjectResult res = s3.putObject(por);
}
你需要这样做:
for(Put obj: thingsToPut)
{
ObjectMetadata metadata = new ObjectMetadata(); // <<-- New ObjectMetadata every time!
metadata.setContentType("text/html");
PutObjectRequest por =
new PutObjectRequest(bucketName, obj.s3Key, obj.file);
por = por.withMetadata(metadata);
PutObjectResult res = s3.putObject(por);
}
对我来说,在执行upload
时,我在params中使用了ContentLength
。当它被评论出来时,它运行得很好。
const params = {
Bucket: "",
ContentType: "application/json",
Key: "filename.json",
// ContentLength: body.length, <--- what I have commented out
Body: body
};
await s3.upload(params).promise();
我在做这样的事情时也遇到了这个错误:
InputStream productInputStream = convertImageFileToInputStream(file);
InputStream thumbnailInputStream = generateThumbnail(productInputStream);
String uploadedFileUrl = amazonS3Uploader.uploadToS3(BUCKET_PRODUCTS_IMAGES, productFilename, productInputStream);
String uploadedThumbnailUrl = amazonS3Uploader.uploadToS3(BUCKET_PRODUCTS_IMAGES, productThumbnailFilename, thumbnailInputStream);
generateThumbnail方法使用第三方库操作productInputStream。因为我无法修改第三方库,所以我只是先上传:
InputStream productInputStream = convertImageFileToInputStream(file);
// do this first...
String uploadedFileUrl = amazonS3Uploader.uploadToS3(BUCKET_PRODUCTS_IMAGES, productFilename, productInputStream);
/// and then this...
InputStream thumbnailInputStream = generateThumbnail(productInputStream);
String uploadedThumbnailUrl = amazonS3Uploader.uploadToS3(BUCKET_PRODUCTS_IMAGES, productThumbnailFilename, thumbnailInputStream);
并在我的generateThumbnail方法中添加了这一行:
productInputStream.reset();
我也遇到了这个问题。我是如何解决这个问题的:
我有一个处理AWS SQS消息的微服务。每条消息将创建多个临时文件,这些文件必须上传到S3。
问题是,临时文件是用固定名称命名的,没有添加任何盐。
因此,在两条消息之间,可以重写要上传的原始文件。
我通过在文件名中添加一个随机salt(这可以是UUID,也可以是以毫秒为单位的当前时间,具体取决于你想要什么)来修复它,之后文件不会被重写,并成功上传到S3。