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가 잘못되었습니다.

내가 도대체 ​​뭘 잘못하고있는 겁니까?

도움을 주시면 감사하겠습니다!

추신 : Google App Engine을 사용 중 입니다. AppEngine이 FileOutputStream을 지원하지 않기 때문에 디스크에 파일을 쓰거나 임시 파일을 만들 수 없습니다 .

답변:


69

원래 질문에 대한 답변이 없었고 동일한 문제가 발생해야했기 때문에 MD5 문제에 대한 해결책은 S3가 우리가 일반적으로 생각하는 Hex 인코딩 MD5 문자열을 원하지 않는다는 것입니다.

대신 나는 이것을해야했다.

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

기본적으로 그들이 MD5 값에 대해 원하는 것은 Hex 문자열이 아니라 Base64로 인코딩 된 원시 MD5 바이트 배열입니다. 내가 이것으로 전환했을 때 그것은 나를 위해 잘 작동하기 시작했습니다.


그리고 우리는 winnahhhh가 있습니다! MD5 문제에 대한 추가 노력에 감사드립니다. 그건 ... 내가 파고 된 부분
긱 주식

이 경우 내용은 무엇입니까? 이해하지 못했습니다. 나는 같은 경고를 받고있다. 좀 도와주세요.?
Shaonline

@Shaonline 내용은 inputStream을하다
sirvon

Hex에서 MD5 바이트 배열로 다시 변환하는 방법이 있습니까? 그것이 우리가 DB에 저장하는 것입니다.
Joel

meta.setContentLength (IOUtils.toByteArray (stream) .length); InputStream을 소비합니다. AWS API가 읽기를 시도 할 때 길이가 0이므로 실패합니다. ByteArrayInputStream에서 새 입력 스트림을 만들어야합니다. byteArrayInputStream = new ByteArrayInputStream (bytes);
Bernie Lenz

43

amazon의 콘텐츠 길이 오류를 해결하기 만하면 입력 스트림의 바이트를 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
입력 스트림을 사용할 수 있다는 점은 한 번에 모든 데이터를 메모리에로드하지 않고 데이터를 스트리밍 할 수 있다는 것입니다.
Jordan Davidson

AmazonServiceException의 경우 너무 많은 sout을 인쇄 할 필요가 없습니다. 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 메서드에는 입력 스트림의 콘텐츠 길이의 최소 메타 데이터가 필요합니다. 그렇지 않은 경우 해당 정보를 얻기 위해 메모리 내에서 버퍼링되므로 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

1
이것은 IO 집약적이지만 여전히 이것에 투표합니다. 이것이 더 큰 파일 객체에서 OOM을 피하는 가장 좋은 방법 일 수 있기 때문입니다. 그러나 누구나 특정 n * 바이트를 읽고 파트 파일을 만들고 s3에 개별적으로 업로드 할 수도 있습니다.
linehrr

7

S3에 쓰는 동안 메모리 부족 오류가 없는지 확인하려면 S3 객체의 길이를 지정해야합니다.

사용은 IOUtils.toByteArray(stream)이가있는 ByteArrayOutputStream에 의해 뒷받침되기 때문에 또한 OOM 오류를하는 경향이있다

따라서 가장 좋은 방법은 먼저 로컬 디스크의 임시 파일에 입력 스트림을 쓴 다음 해당 파일을 사용하여 임시 파일의 길이를 지정하여 S3에 쓰는 것입니다.


1
감사합니다.하지만 저는 Google 앱 엔진에 있습니다 (업데이트 된 질문)-파일을 디스크에 쓸 수없는 경우 파일을받는 putObject 오버로드를 사용할 수 있습니다 :(
JohnIdol

@srikanta 방금 조언을 받았습니다. 임시 파일의 길이를 지정할 필요가 없습니다. 임시 파일을 그대로 전달하십시오.
Siya Sosibo 2016 년

참고로 나처럼 ObjectMetadata에서 수행되는 서버 측 암호화를 지정하려는 경우 임시 파일 접근 방식은 옵션이 아닙니다. 불행하게도 PutObjectRequest (문자열 bucketName, 문자열 키, 파일 파일, ObjectMetadata 메타 데이터)가 없다
케빈 파울리

@kevin 파울리 당신은 할 수request.setMetadata();
dbaq

5

나는 실제로 다소 같은 일을하고 있지만 AWS S3 스토리지에서 :-

업로드 된 파일을 수신하는 서블릿에 대한 코드 :-

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 의 답변이 도움이되었습니다.


스트림이있는 경우 해당 스트림을 사용하고 싶습니다. 데이터를 얻기 위해 (임시) 파일에 스트림을 쓰는 것은 비효율적이며 추가 골칫거리 (파일 삭제, 디스크 사용량)
devstructor

이렇게하면 암호화와 같은 메타 데이터를 전달할 수 없습니다. 이는 AWS에 저장할 때 일반적인 관행입니다
user1412523

-14

log4j-1.2.12.jar 파일을 추가하면 문제가 해결되었습니다.


2
-1 : 이것은 로그 경고를 숨기지 만 오류 자체를 해결하지는 않습니다. 너무 가혹해서 죄송합니다. 결국 첫 번째 답변이지만이 질문은 해결되지 않습니다.
romualdr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.