Ví dụ về AmazonS3 putObject với độ dài InputStream


82

Tôi đang tải một tệp lên S3 bằng Java - đây là những gì tôi nhận được cho đến nay:

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()));

Tệp đang được tải lên nhưng CẢNH BÁO được đưa ra khi tôi không đặt độ dài nội dung:

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.

Đây là một tập tin tôi tải lên và streambiến là một InputStream, từ đó tôi có thể lấy mảng byte như thế này: IOUtils.toByteArray(stream).

Vì vậy, khi tôi cố gắng đặt độ dài nội dung và MD5 (lấy từ đây ) như thế này:

// 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);

Nó gây ra lỗi sau quay lại từ S3:

Nội dung-MD5 bạn đã chỉ định không hợp lệ.

Tôi đang làm gì sai?

Bất kỳ sự giúp đỡ nào được đánh giá cao!

Tái bút Tôi đang sử dụng Google App Engine - Tôi không thể ghi tệp vào đĩa hoặc tạo tệp tạm thời vì AppEngine không hỗ trợ FileOutputStream.

Câu trả lời:


69

Bởi vì câu hỏi ban đầu không bao giờ được trả lời và tôi đã gặp phải vấn đề tương tự này, giải pháp cho vấn đề MD5 là S3 không muốn chuỗi MD5 được mã hóa Hex mà chúng ta thường nghĩ đến.

Thay vào đó, tôi phải làm điều này.

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

Về cơ bản những gì họ muốn cho giá trị MD5 là mảng byte MD5 thô được mã hóa Base64, không phải chuỗi Hex. Khi tôi chuyển sang điều này, nó bắt đầu hoạt động tuyệt vời đối với tôi.


Và chúng tôi có một winnahhhh! Cảm ơn bạn đã nỗ lực trả lời vấn đề MD5. Đó là một phần tôi đã đào cho ...
Geek Chứng khoán

Nội dung trong trường hợp này là gì? tôi không hiểu. Tôi đang có cùng một cảnh báo. Giúp đỡ một chút, xin vui lòng.?
Shaonline

@Shaonline content is the inputStream
sirvon

Bất kỳ cách nào để chuyển đổi từ Hex trở lại mảng byte MD5? Đó là những gì chúng tôi lưu trữ trong DB của mình.
Joel

Xin lưu ý rằng meta.setContentLength (IOUtils.toByteArray (stream) .length); sử dụng InputStream. Khi API AWS cố gắng đọc nó, độ dài của nó bằng 0 và do đó không thành công. Bạn cần tạo một luồng đầu vào mới từ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (byte);
Bernie Lenz

43

Nếu tất cả những gì bạn đang cố gắng làm là giải quyết lỗi độ dài nội dung từ amazon thì bạn chỉ có thể đọc các byte từ luồng đầu vào thành một Dài và thêm nó vào siêu dữ liệu.

/*
 * 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();
    }
}

Bạn sẽ cần đọc luồng đầu vào hai lần bằng cách sử dụng phương pháp chính xác này, vì vậy nếu bạn đang tải lên một tệp rất lớn, bạn có thể cần xem xét việc đọc nó một lần vào một mảng và sau đó đọc nó từ đó.


24
Vì vậy, quyết định của bạn là đọc stream hai lần! Và bạn lưu toàn bộ tệp trong bộ nhớ. Điều này có thể gây ra OOM như S3 cảnh báo!
Pavel Vyazankin

3
Điểm của việc có thể sử dụng luồng đầu vào là bạn có thể truyền dữ liệu mà không phải tải tất cả vào bộ nhớ cùng một lúc.
Jordan Davidson

Đối với AmazonServiceException, không cần phải in nhiều miền nam như vậy. Phương thức getMessage in mọi thứ ngoại trừ getErrorType.
saurabheights

33

Để tải lên, SDK S3 có hai phương thức putObject:

PutObjectRequest(String bucketName, String key, File file)

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

Phương thức inputstream + ObjectMetadata cần một siêu dữ liệu tối thiểu về Độ dài nội dung của luồng đầu vào của bạn. Nếu bạn không, thì nó sẽ đệm trong bộ nhớ để lấy thông tin đó, điều này có thể gây ra OOM. Ngoài ra, bạn có thể thực hiện đệm trong bộ nhớ của riêng mình để lấy độ dài, nhưng sau đó bạn cần lấy dòng đầu vào thứ hai.

Không phải do OP hỏi (những hạn chế trong môi trường của anh ấy), mà là đối với một người khác, chẳng hạn như tôi. Tôi thấy việc ghi dòng đầu vào vào tệp tạm thời và đặt tệp tạm thời sẽ dễ dàng và an toàn hơn (nếu bạn có quyền truy cập vào tệp tạm thời). Không có bộ đệm trong bộ nhớ và không yêu cầu tạo dòng đầu vào thứ hai.

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();
    }
}

Đối số thứ hai trong copyInputStreamToFile (inputStream, xướcFile) là Type File hay OutputStream?
Shaonline

1
mặc dù đây là IO chuyên sâu, nhưng tôi vẫn bỏ phiếu cho điều này. vì đây có thể là cách tốt nhất để tránh OOM trên đối tượng tệp lớn hơn. Tuy nhiên, bất kỳ ai cũng có thể đọc n * byte nhất định và tạo các tệp phần và tải lên s3 riêng biệt.
linehrr 14/02/18

7

Trong khi ghi vào S3, bạn cần chỉ định độ dài của đối tượng S3 để chắc chắn rằng không có lỗi hết bộ nhớ.

Sử dụng IOUtils.toByteArray(stream)cũng dễ bị lỗi OOM vì cái này được hỗ trợ bởi ByteArrayOutputStream

Vì vậy, tùy chọn tốt nhất là đầu tiên ghi dòng đầu vào vào tệp tạm thời trên đĩa cục bộ và sau đó sử dụng tệp đó để ghi vào S3 bằng cách chỉ định độ dài của tệp tạm thời.


1
Cảm ơn nhưng tôi đang sử dụng công cụ ứng dụng google (câu hỏi đã cập nhật) - không thể ghi tệp vào đĩa, nếu tôi có thể làm điều đó, tôi có thể sử dụng quá tải putObject có tệp :(
JohnIdol

@srikanta Chỉ cần nghe lời khuyên của bạn. Không cần chỉ định độ dài của tệp tạm thời. Chỉ cần chuyển tệp tạm thời như hiện tại.
Siya Sosibo

FYI cách tiếp cận tệp tạm thời KHÔNG phải là một tùy chọn nếu, giống như tôi, bạn muốn chỉ định mã hóa phía máy chủ, được thực hiện trong ObjectMetadata. Rất tiếc là không có PutObjectRequest (String bucketName, String key, File file, ObjectMetadata metadata)
Kevin Pauli

@kevin Pauli Bạn có thể làmrequest.setMetadata();
dbaq

5

tôi thực sự đang làm điều tương tự nhưng trên bộ nhớ AWS S3 của mình: -

Mã cho servlet đang nhận tệp tải lên: -

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());
        }
    }
}

Mã đang tải lên dữ liệu này dưới dạng đối tượng 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;
    }
}

Lưu ý: - Tôi đang sử dụng tệp thuộc tính aws cho thông tin đăng nhập.

Hi vọng điêu nay co ich.



-1

Tôi chỉ cần chuyển đối tượng tệp sang phương thức putobject. Nếu bạn đang nhận được một luồng, hãy thử ghi nó vào một tệp tạm thời trước khi chuyển nó cho S3.

amazonS3.putObject(bucketName, id,fileObject);

Tôi đang sử dụng Aws SDK v1.11.414

Câu trả lời tại https://stackoverflow.com/a/35904801/2373449 đã giúp tôi


Nếu bạn có một luồng, bạn muốn sử dụng luồng đó. Việc ghi luồng vào tệp (tạm thời) chỉ để lấy dữ liệu của nó là không hiệu quả và khiến bạn thêm đau đầu (xóa tệp, sử dụng đĩa)
devstructor

điều này sẽ không cho phép bạn chuyển siêu dữ liệu, chẳng hạn như Mã hóa, đây là phương pháp phổ biến khi lưu trữ trong AWS
user1412523

-14

thêm tệp log4j-1.2.12.jar đã giải quyết được sự cố cho tôi


2
-1: Tôi đoán điều này sẽ chỉ ẩn cảnh báo nhật ký nhưng không tự giải quyết lỗi. Xin lỗi vì quá gay gắt, đó là câu trả lời đầu tiên của bạn, nhưng điều này không giải quyết được câu hỏi này.
romualdr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.