InputStream uzunluk örneği ile AmazonS3 putObject


83

Java kullanarak S3'e bir dosya yüklüyorum - şu ana kadar elde ettiğim şey bu:

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

Dosya yükleniyor ancak içerik uzunluğunu ayarlamadığım zaman bir UYARI gönderiliyor:

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.

Bu benim yüklüyorum bir dosyadır ve streamdeğişken bir olduğunu InputStreamböyle bayt dizisi alabilirsiniz hangi,: IOUtils.toByteArray(stream).

Dolayısıyla, içerik uzunluğunu ve MD5'i ( buradan alınmıştır ) şu şekilde ayarlamaya çalıştığımda :

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

Aşağıdaki hatanın S3'ten geri gelmesine neden olur:

Belirttiğiniz Content-MD5 geçersizdi.

Neyi yanlış yapıyorum?

Herhangi bir yardım için minnettarız!

Not : Google App Engine kullanıyorum - AppEngine FileOutputStream'i desteklemediği için dosyayı diske yazamıyorum veya geçici dosya oluşturamıyorum .


IOUtils.toByteArray tüm dosyayı belleğinize okur, bu nedenle dosyalarınızın boyutuna bağlı olarak yeterli çözüm olamaz. Daha iyi bir çözüm, dosya sağlayıcısından dosya boyutu hakkında bilgi istemek ve ardından onu S3'e aktarmaktır; bu şekilde, boyutla ilgili bilgilere zaten sahip olduğunuz için bellekteki tüm dosyaları indirmeniz gerekmez
Hamdi

Yanıtlar:


69

Asıl soru hiçbir zaman yanıtlanmadığı ve aynı problemle karşılaşmam gerektiğinden, MD5 sorununun çözümü, S3'ün normalde düşündüğümüz Hex kodlu MD5 dizesini istememesidir.

Bunun yerine bunu yapmak zorundaydım.

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

Esasen MD5 değeri için istedikleri şey, Hex dizisi değil, Base64 kodlu ham MD5 bayt dizisidir. Buna geçtiğimde benim için harika çalışmaya başladı.


Ve bir winnahhhh var! MD5 sorununu yanıtlarken gösterdiğiniz ekstra çaba için teşekkür ederiz. Araştırdığım kısım bu ...
Geek Stocks

Bu durumda içerik nedir? anlamadım. Ben de aynı uyarıyı alıyorum. Biraz yardım lütfen.
Shaonline

@Shaonline içerik inputStream olduğunu
sirvon

Hex'ten MD5 bayt dizisine dönüştürmenin bir yolu var mı? DB'mizde sakladığımız şey budur.
Joel

Lütfen meta.setContentLength (IOUtils.toByteArray (stream) .length); InputStream'i tüketir. AWS API onu okumaya çalıştığında sıfır uzunluktadır ve bu nedenle başarısız olur. ByteArrayInputStream'den yeni bir girdi akışı oluşturmanız gerekir byteArrayInputStream = new ByteArrayInputStream (bayt);
Bernie Lenz

43

Yapmaya çalıştığınız tek şey amazon'dan gelen içerik uzunluğu hatasını çözmekse, giriş akışındaki baytları bir Long'a okuyabilir ve bunu meta verilere ekleyebilirsiniz.

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

Bu tam yöntemi kullanarak giriş akışını iki kez okumanız gerekecek, bu nedenle çok büyük bir dosya yüklüyorsanız, onu bir diziye bir kez okumaya ve ardından oradan okumaya bakmanız gerekebilir.


24
Yani kararınız akışı iki kez okumak! Ve tüm dosyayı hafızaya kaydedersiniz. S3'ün uyardığı gibi bu OOM'ye neden olabilir!
Pavel Vyazankin

3
Bir giriş akışını kullanabilmenin amacı, verileri tek seferde belleğe yüklemeden akışa alabilmenizdir.
Jordan Davidson

AmazonServiceException için bu kadar çok sout yazdırmaya gerek yoktur. getMessage yöntemi, getErrorType dışındaki her şeyi yazdırır.
saurabheights

33

Yükleme için, S3 SDK'nın iki putObject yöntemi vardır:

PutObjectRequest(String bucketName, String key, File file)

ve

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

Giriş akışı + ObjectMetadata yöntemi, giriş akışınızın İçerik Uzunluğunun minimum meta verisine ihtiyaç duyar. Bunu yapmazsanız, o bilgiyi almak için bellek içi arabelleğe alır, bu OOM'ye neden olabilir. Alternatif olarak, uzunluğu elde etmek için kendi bellek içi arabelleğe alma işleminizi yapabilirsiniz, ancak daha sonra ikinci bir giriş akışı almanız gerekir.

OP tarafından sorulmadı (çevresinin kısıtlamaları), ama benim gibi başka biri için. Girdi akışını geçici bir dosyaya yazmayı ve geçici dosyayı koymayı (geçici dosyaya erişiminiz varsa) daha kolay ve daha güvenli buluyorum. Bellek içi arabellek yok ve ikinci bir giriş akışı oluşturmaya gerek yok.

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) içindeki ikinci argüman Type File mı yoksa OutputStream mi?
Shaonline

1
IO yoğun olmasına rağmen, yine de buna oy veriyorum. çünkü bu, daha büyük dosya nesnesinde OOM'den kaçınmanın en iyi yolu olabilir. Ancak, herkes belirli n * baytları okuyabilir ve parça dosyaları oluşturabilir ve s3'e ayrı olarak yükleyebilir.
linehrr

7

S3'e yazarken, yetersiz bellek hatası olmadığından emin olmak için S3 nesnesinin uzunluğunu belirtmeniz gerekir.

Kullanarak IOUtils.toByteArray(stream)bu ByteArrayOutputStream tarafından desteklenmektedir çünkü ayrıca OOM hataları eğilimli

Bu nedenle, en iyi seçenek ilk olarak girdi akışını yerel diskteki bir geçici dosyaya yazmak ve ardından bu dosyayı geçici dosyanın uzunluğunu belirterek S3'e yazmak için kullanmaktır.


1
Teşekkürler ama google uygulama motorundayım (güncellenmiş soru) - dosyayı diske yazamıyorum, eğer yapabilirsem Dosya alan putObject aşırı yüklemesini kullanabilirim :(
JohnIdol

@srikanta Tavsiyene uydum. Geçici dosyanın uzunluğunu belirtmeye gerek yok. Geçici dosyayı olduğu gibi geçirmeniz yeterli.
Siya Sosibo

Bilginize, benim gibi, ObjectMetadata'da yapılan sunucu tarafı şifrelemesini belirtmek istiyorsanız geçici dosya yaklaşımı bir seçenek DEĞİLDİR. Maalesef PutObjectRequest (Dize kovasıAdı, Dize anahtarı, Dosya dosyası, ObjectMetadata meta verisi) yok
Kevin Pauli

@kevin pauli Yapabilirsinrequest.setMetadata();
dbaq

6

aslında biraz aynı şeyi yapıyorum ama AWS S3 depolamada: -

Yüklenen dosyayı alan sunucu uygulamasının kodu: -

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

Bu verileri AWS nesnesi olarak yükleyen kod: -

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

Not: - Kimlik bilgileri için aws özellikler dosyasını kullanıyorum.

Bu yardımcı olur umarım.



-1

Dosya nesnesini putobject yöntemine geçirmek benim için çalıştı. Bir akış alıyorsanız, onu S3'e iletmeden önce geçici bir dosyaya yazmayı deneyin.

amazonS3.putObject(bucketName, id,fileObject);

Aws SDK v1.11.414 kullanıyorum

Https://stackoverflow.com/a/35904801/2373449 adresindeki cevap bana yardımcı oldu


Bir akışınız varsa, o akışı kullanmak istersiniz. Sadece kendi veri almak için (geçici) dosyasına akışı Yazma verimsiz ve size ek bir baş ağrısı (silme dosya, disk kullanımı) verir
devstructor

bu,
AWS'de

-15

log4j-1.2.12.jar dosyasının eklenmesi sorunu benim için çözdü


2
-1: Sanırım bu sadece günlük uyarısını gizleyecek ancak hatanın kendisini çözmeyecek. Bu kadar sert olduğum için üzgünüm, sonuçta ilk cevabınız ama bu, bu soruyu çözmüyor.
romualdr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.