AmazonS3 putObject з прикладом довжини InputStream


83

Я завантажую файл на S3 за допомогою Java - це те, що я отримав на даний момент:

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.


IOUtils.toByteArray читає весь файл у вашій пам’яті, тому, залежно від розміру ваших файлів, це не може бути адекватним рішенням. Кращим рішенням буде запит у постачальника файлів про розмір файлу, а потім його потокове передавання на S3, таким чином, вам не доведеться завантажувати всі файли в пам’яті, оскільки ви вже маєте інформацію про розмір
Хамді

Відповіді:


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, - це базовий масив байтів MD5, кодований Base64, а не шістнадцятковий рядок. Коли я перейшов на це, це почало чудово працювати для мене.


І у нас є winnahhhh! Дякуємо за додаткові зусилля, які відповідають на питання MD5. Це та частина, яку я копав ...
Geek Stocks

Що таке вміст у цьому випадку? я не зрозумів. У мене таке саме попередження. Невелика допомога, будь ласка.
Shaonline

Вміст @Shaonline
вхідний

Будь-який спосіб перетворити з Hex назад на байтовий масив MD5? Це те, що ми зберігаємо в нашій БД.
Джоель

Зверніть увагу, що meta.setContentLength (IOUtils.toByteArray (stream) .length); споживає InputStream. Коли AWS API намагається прочитати його, це нульова довжина, і тому не вдається. Вам потрібно створити новий вхідний потік з ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream (байти);
Берні Ленц

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
Тож ваше рішення - прочитати потік двічі! І ви зберігаєте цілий файл у пам'яті. Це може спричинити OOM, як попереджає S3!
Павло Вязанкін

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 потребує мінімальних метаданих довжини вмісту вашого потоку вхідних даних. Якщо ви цього не зробите, він буде буферизувати пам'ять, щоб отримати цю інформацію, це може спричинити 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) є тип файлу або OutputStream?
Shaonline

1
хоч це інтенсивний IO, але я все одно за це голосую. оскільки це може бути найкращим способом уникнути OOM для більшого файлового об'єкта. Однак кожен міг також прочитати певні n * байти та створити файли деталей та завантажити їх у s3 окремо.
linehrr

7

Під час запису на S3 потрібно вказати довжину об’єкта S3, щоб переконатися, що помилок, що не вистачає, не було.

Використання IOUtils.toByteArray(stream)також схильне до помилок OOM, оскільки це підтримується ByteArrayOutputStream

Отже, найкращий варіант - спочатку записати вхідний потік у тимчасовий файл на локальному диску, а потім використовувати цей файл для запису в S3, вказавши довжину тимчасового файлу.


1
Дякую, але я перебуваю на двигуні додатків Google (оновлене запитання) - не можу записати файл на диск, якщо б я міг зробити це, я міг би використати перевантаження putObject, яке займає файл :(
JohnIdol

@srikanta Просто послухав вашої поради. Не потрібно вказувати довжину тимчасового файлу. Просто передайте тимчасовий файл як є.
Siya Sosibo

FYI підхід до тимчасового файлу НЕ є варіантом, якщо, як і я, ви хочете вказати шифрування на стороні сервера, що виконується в ObjectMetadata. На жаль, немає PutObjectRequest (String bucketName, String key, File file, ObjectMetadata metadata)
Кевін Паулі,

@kevin pauli Ви можете це зробитиrequest.setMetadata();
dbaq

6

Я фактично роблю дещо те саме, але на моєму сховищі 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 02

це не дозволить вам передавати метадані, такі як Шифрування, що є звичайною практикою при зберіганні в AWS
user1412523

-15

додавання файла log4j-1.2.12.jar вирішило проблему для мене


2
-1: Я думаю, це просто приховає попередження журналу, але не вирішить саму помилку. Вибачте, що я такий жорсткий, зрештою, це ваша перша відповідь, але це не вирішує цього питання.
romualdr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.