具有InputStream长度示例的AmazonS3 putObject


82

我正在使用Java将文件上传到S3-到目前为止,这是我得到的:

AmazonS3 s3 = new AmazonS3Client(new BasicAWSCredentials("XX","YY"));

List<Bucket> buckets = s3.listBuckets();

s3.putObject(new PutObjectRequest(buckets.get(0).getName(), fileName, stream, new ObjectMetadata()));

文件正在上传,但是未设置内容长度时会发出警告:

com.amazonaws.services.s3.AmazonS3Client putObject: No content length specified for stream > data.  Stream contents will be buffered in memory and could result in out of memory errors.

这是我上传文件和stream变量是InputStream,从中我可以得到字节数组是这样的:IOUtils.toByteArray(stream)

因此,当我尝试像这样设置内容长度和MD5(从此处获取)时:

// get MD5 base64 hash
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update(IOUtils.toByteArray(stream));
byte[] resultByte = messageDigest.digest();
String hashtext = new String(Hex.encodeHex(resultByte));

ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(IOUtils.toByteArray(stream).length);
meta.setContentMD5(hashtext);

这会导致以下错误从S3返回:

您指定的Content-MD5无效。

我究竟做错了什么?

任何帮助表示赞赏!

PS:我在Google App Engine上-无法将文件写入磁盘或创建临时文件,因为AppEngine不支持FileOutputStream。

Answers:


69

因为从未回答过原始问题,而且我也不得不遇到相同的问题,所以针对MD5问题的解决方案是S3不需要我们通常考虑的十六进制编码的MD5字符串。

相反,我必须这样做。

// content is a passed in InputStream
byte[] resultByte = DigestUtils.md5(content);
String streamMD5 = new String(Base64.encodeBase64(resultByte));
metaData.setContentMD5(streamMD5);

本质上,他们想要MD5值的是Base64编码的原始MD5字节数组,而不是十六进制字符串。当我切换到此功能时,它开始对我非常有用。


我们有一个winnahhhh!感谢您为解决MD5问题付出的额外努力。那就是我正在挖掘的部分...
Geek Stocks 2013年

在这种情况下内容是什么?我不明白。我也有同样的警告。请帮忙。
Shaonline '16

@Shaonline的内容是inputStream
sirvon

从十六进制转换回MD5字节数组的任何方法?那就是我们存储在数据库中的内容。
乔尔

请注意,meta.setContentLength(IOUtils.toByteArray(stream).length); 消耗InputStream。当AWS API尝试读取它时,它的长度为零,因此会失败。您需要从ByteArrayInputStream创建新的输入流byteArrayInputStream = new ByteArrayInputStream(bytes);
伯尼·伦兹

43

如果您只是想解决来自亚马逊的内容长度错误,那么您可以将字节从输入流中读取到Long中,然后将其添加到元数据中。

/*
 * Obtain the Content length of the Input stream for S3 header
 */
try {
    InputStream is = event.getFile().getInputstream();
    contentBytes = IOUtils.toByteArray(is);
} catch (IOException e) {
    System.err.printf("Failed while reading bytes from %s", e.getMessage());
} 

Long contentLength = Long.valueOf(contentBytes.length);

ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(contentLength);

/*
 * Reobtain the tmp uploaded file as input stream
 */
InputStream inputStream = event.getFile().getInputstream();

/*
 * Put the object in S3
 */
try {

    s3client.putObject(new PutObjectRequest(bucketName, keyName, inputStream, metadata));

} catch (AmazonServiceException ase) {
    System.out.println("Error Message:    " + ase.getMessage());
    System.out.println("HTTP Status Code: " + ase.getStatusCode());
    System.out.println("AWS Error Code:   " + ase.getErrorCode());
    System.out.println("Error Type:       " + ase.getErrorType());
    System.out.println("Request ID:       " + ase.getRequestId());
} catch (AmazonClientException ace) {
    System.out.println("Error Message: " + ace.getMessage());
} finally {
    if (inputStream != null) {
        inputStream.close();
    }
}

您将需要使用此确切方法读取输入流两次,因此,如果要上传非常大的文件,则可能需要先将其读取到数组中,然后再从那里读取。


24
因此,您的决定是两次读取流!然后将整个文件保存在内存中。S3警告可能会导致OOM!
Pavel Vyazankin 2014年

3
能够使用输入流的点在于,您可以流式传输数据,而不是将所有数据立即加载到内存中。
乔丹·戴维森

对于AmazonServiceException,无需打印那么多行。getMessage方法将打印除getErrorType之外的所有内容。
saurabheights

33

对于上传,S3 SDK具有两个putObject方法:

PutObjectRequest(String bucketName, String key, File file)

PutObjectRequest(String bucketName, String key, InputStream input, ObjectMetadata metadata)

inputstream + ObjectMetadata方法需要输入流的Content Length的最小元数据。如果您不这样做,那么它将在内存中缓冲以获取该信息,这可能会导致OOM。或者,您可以执行自己的内存缓冲来获取长度,但随后需要获取第二个输入流。

OP并没有要求他(他的环境的限制),而是要求其他人,例如我。我发现将输入流写入临时文件并放入临时文件更容易,更安全(如果您可以访问临时文件)。没有内存缓冲区,也不需要创建第二个输入流。

AmazonS3 s3Service = new AmazonS3Client(awsCredentials);
File scratchFile = File.createTempFile("prefix", "suffix");
try {
    FileUtils.copyInputStreamToFile(inputStream, scratchFile);    
    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, id, scratchFile);
    PutObjectResult putObjectResult = s3Service.putObject(putObjectRequest);

} finally {
    if(scratchFile.exists()) {
        scratchFile.delete();
    }
}

copyInputStreamToFile(inputStream,scratchFile)中的第二个参数是Type File还是OutputStream?
Shaonline '16

1
尽管这是IO密集型工作,但我仍然对此表示赞成。因为这可能是避免在较大文件对象上进行OOM的最佳方法。但是,任何人都可以读取某些n * bytes并创建零件文件,然后分别上传到s3。
linehrr18年

7

写入S3时,需要指定S3对象的长度,以确保没有内存不足错误。

使用IOUtils.toByteArray(stream)还容易发生OOM错误,因为这由ByteArrayOutputStream支持

因此,最好的选择是先将输入流写入本地磁盘上的临时文件,然后通过指定临时文件的长度使用该文件写入S3。


1
谢谢,但是我在谷歌应用程序引擎上(更新的问题)-无法将文件写入磁盘,如果可以,我可以使用putObject重载,该重载需要一个文件:(
JohnIdol

@srikanta接受了您的建议。无需指定临时文件的长度。只需按原样传递临时文件即可。
Siya Sosibo

仅供参考,如果像我一样要指定服务器端加密(在ObjectMetadata中完成),则不能使用临时文件方法。不幸的是,没有PutObjectRequest(String bucketName,String key,File file,ObjectMetadata元数据)
Kevin Pauli

@kevin pauli您可以request.setMetadata();
dbaq

5

我实际上在做一些相同的事情,但是在我的AWS S3存储上:-

正在接收上传文件的servlet的代码:-

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.src.code.s3.S3FileUploader;

public class FileUploadHandler extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();

        try{
            List<FileItem> multipartfiledata = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);

            //upload to S3
            S3FileUploader s3 = new S3FileUploader();
            String result = s3.fileUploader(multipartfiledata);

            out.print(result);
        } catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
}

将数据作为AWS对象上传的代码:-

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

import org.apache.commons.fileupload.FileItem;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;

public class S3FileUploader {


    private static String bucketName     = "***NAME OF YOUR BUCKET***";
    private static String keyName        = "Object-"+UUID.randomUUID();

    public String fileUploader(List<FileItem> fileData) throws IOException {
        AmazonS3 s3 = new AmazonS3Client(new ClasspathPropertiesFileCredentialsProvider());
        String result = "Upload unsuccessfull because ";
        try {

            S3Object s3Object = new S3Object();

            ObjectMetadata omd = new ObjectMetadata();
            omd.setContentType(fileData.get(0).getContentType());
            omd.setContentLength(fileData.get(0).getSize());
            omd.setHeader("filename", fileData.get(0).getName());

            ByteArrayInputStream bis = new ByteArrayInputStream(fileData.get(0).get());

            s3Object.setObjectContent(bis);
            s3.putObject(new PutObjectRequest(bucketName, keyName, bis, omd));
            s3Object.close();

            result = "Uploaded Successfully.";
        } catch (AmazonServiceException ase) {
           System.out.println("Caught an AmazonServiceException, which means your request made it to Amazon S3, but was "
                + "rejected with an error response for some reason.");

           System.out.println("Error Message:    " + ase.getMessage());
           System.out.println("HTTP Status Code: " + ase.getStatusCode());
           System.out.println("AWS Error Code:   " + ase.getErrorCode());
           System.out.println("Error Type:       " + ase.getErrorType());
           System.out.println("Request ID:       " + ase.getRequestId());

           result = result + ase.getMessage();
        } catch (AmazonClientException ace) {
           System.out.println("Caught an AmazonClientException, which means the client encountered an internal error while "
                + "trying to communicate with S3, such as not being able to access the network.");

           result = result + ace.getMessage();
         }catch (Exception e) {
             result = result + e.getMessage();
       }

        return result;
    }
}

注意:-我正在使用aws属性文件获取凭据。

希望这可以帮助。



-1

只是将文件对象传递给putobject方法对我来说有效。如果要获取流,请在将其传递到S3之前尝试将其写入临时文件。

amazonS3.putObject(bucketName, id,fileObject);

我正在使用Aws SDK v1.11.414

https://stackoverflow.com/a/35904801/2373449的答案对我有帮助


如果您有流,则要使用该流。仅将流写入(临时)文件以获取其数据效率低下,并且使您更加头疼(删除文件,磁盘使用情况)
开发者

这将不允许您传递元数据,例如Encryption,这是在AWS中存储时的常见做法
user1412523

-14

添加log4j-1.2.12.jar文件已为我解决了此问题


2
-1:我想这只会隐藏日志警告,而不能解决错误本身。抱歉,这么苛刻,毕竟这是您的第一个答案,但这不能解决这个问题。
romualdr
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.