wake-up-neo.net

AmazonS3 putObject mit InputStream-Längenbeispiel

Ich lade eine Datei mit Java in S3 hoch.

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

Die Datei wird hochgeladen, aber eine Warnung wird ausgelöst, wenn ich die Länge des Inhalts nicht festlege:

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.

Dies ist eine Datei, die ich hochlade, und die Variable stream ist eine InputStream, aus der ich das Byte-Array wie folgt abrufen kann: IOUtils.toByteArray(stream).

Wenn ich also versuche, die Länge und den Inhalt des MD5 (aus hier ) wie folgt einzustellen:

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

Der folgende Fehler wird von S3 zurückgegeben:

Das von Ihnen angegebene Content-MD5 war ungültig.

Was mache ich falsch?

Jede Hilfe geschätzt!

P.S. Ich bin bei Google App Engine - Ich kann die Datei nicht auf die Festplatte schreiben oder eine temporäre Datei erstellen weil AppEngine FileOutputStream nicht unterstützt.

71
JohnIdol

Da die ursprüngliche Frage nie beantwortet wurde und ich auf dieses Problem stoßen musste, besteht die Lösung für das MD5-Problem darin, dass S3 die hexadezimierte MD5-Zeichenfolge nicht will, über die wir normalerweise nachdenken. 

Stattdessen musste ich das tun. 

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

Was sie für den MD5-Wert wollen, ist im Wesentlichen das Base64-codierte rohe MD5-Byte-Array, nicht die Hex-Zeichenfolge. Als ich darauf wechselte, funktionierte es für mich großartig. 

62
user2413809

Wenn Sie nur versuchen, den Inhaltslängenfehler von Amazon zu lösen, können Sie die Bytes aus dem Eingabestrom in einen Long-Wert lesen und den Metadaten hinzufügen.

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

Sie müssen den Eingabestrom mit dieser exakten Methode zweimal lesen. Wenn Sie also eine sehr große Datei hochladen, müssen Sie sie vielleicht einmal in ein Array lesen und dann von dort lesen.

40
tarka

Für das Hochladen verfügt das S3-SDK über zwei putObject-Methoden:

PutObjectRequest(String bucketName, String key, File file)

und

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

Die inputstream + ObjectMetadata-Methode erfordert ein Minimum an Metadaten der Content Length Ihres Inputstream. Wenn Sie dies nicht tun, wird der Arbeitsspeicher zwischengespeichert, um diese Informationen zu erhalten. Dies kann OOM verursachen. Alternativ können Sie Ihre eigene Zwischenspeicherung im Speicher durchführen, um die Länge zu ermitteln, aber dann müssen Sie einen zweiten Eingangsstrom erhalten.

Nicht vom OP gefragt (Einschränkungen seiner Umgebung), sondern nach jemandem wie mir. Ich finde es einfacher und sicherer (wenn Sie Zugriff auf die temporäre Datei haben), den Inputstream in eine temporäre Datei zu schreiben und die temporäre Datei zu setzen. Kein Zwischenspeicher und keine Notwendigkeit, einen zweiten Eingangsstrom zu erstellen.

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

Beim Schreiben in S3 müssen Sie die Länge des S3-Objekts angeben, um sicherzustellen, dass kein Speicherfehler vorliegt. 

Die Verwendung von IOUtils.toByteArray(stream) ist auch anfällig für OOM-Fehler, da dies durch ByteArrayOutputStream unterstützt wird 

Die beste Option ist also, den Inputstream zuerst in eine temporäre Datei auf der lokalen Festplatte zu schreiben und dann diese Datei zum Schreiben in S3 zu verwenden, indem die Länge der temporären Datei angegeben wird.

6
srikfreak

ich mache eigentlich etwas dasselbe, aber auf meinem AWS S3-Speicher: -

Code für das Servlet, das die hochgeladene Datei empfängt: -

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

Code, der diese Daten als AWS-Objekt hochlädt: -

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

Hinweis: - Ich verwende eine aws-Eigenschaftendatei für Anmeldeinformationen.

Hoffe das hilft.

4
streak

Ich habe eine Bibliothek erstellt, die mehrteilige Uploads im Hintergrund verwendet, um das Puffern des gesamten Speichers zu vermeiden und auch nicht auf die Festplatte zu schreiben: https://github.com/alexmojaki/s3-stream-upload

3
Alex Hall

Das File-Objekt einfach an die putobject-Methode zu übergeben, funktionierte für mich. Wenn Sie einen Stream erhalten, schreiben Sie ihn in eine temporäre Datei, bevor Sie ihn an S3 übergeben. 

amazonS3.putObject(bucketName, id,fileObject);

Ich verwende Aws SDK v1.11.414

Die Antwort unter https://stackoverflow.com/a/35904801/2373449 hat mir geholfen 

0
Vikram