web-dev-qa-db-fra.com

AmazonS3 putObject avec exemple de longueur InputStream

Je télécharge un fichier sur S3 à l'aide de Java. Voici ce que j'ai obtenu jusqu'à présent:

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

Le fichier est en cours de téléchargement, mais un AVERTISSEMENT est émis lorsque je ne définis pas la longueur du contenu:

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.

Ceci est un fichier que je télécharge et la variable stream est une InputStream, à partir de laquelle je peux obtenir le tableau d'octets comme ceci: IOUtils.toByteArray(stream).

Donc, quand j'essaie de définir la longueur du contenu et MD5 (pris de ici ) comme ceci:

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

Cela provoque le retour de l'erreur suivante de S3:

Le contenu-MD5 que vous avez spécifié n'était pas valide.

Qu'est-ce que je fais mal?

Toute aide appréciée!

P.S. Je suis sur Google App Engine - Je ne peux pas écrire le fichier sur le disque ni créer un fichier temporaire car AppEngine ne prend pas en charge FileOutputStream.

71
JohnIdol

Comme la question d'origine n'avait jamais été résolue et que je devais rencontrer le même problème, la solution au problème MD5 est que S3 ne veut pas de la chaîne codée Hex MD5 à laquelle nous pensons normalement. 

Au lieu de cela, je devais le faire. 

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

Ce qu'ils veulent essentiellement pour la valeur MD5 est le tableau d'octets MD5 brut codé en Base64, pas la chaîne Hex. Quand je suis passé à cela, cela a commencé à bien fonctionner pour moi. 

62
user2413809

Si vous essayez uniquement de résoudre l'erreur de longueur du contenu d'Amazon, vous pouvez simplement lire les octets du flux d'entrée sur un long et les ajouter aux métadonnées.

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

Vous devrez lire le flux d'entrée deux fois à l'aide de cette méthode. Ainsi, si vous téléchargez un très gros fichier, vous devrez peut-être le lire une fois dans un tableau, puis le lire à partir de là.

40
tarka

Pour le téléchargement, le SDK S3 utilise deux méthodes putObject:

PutObjectRequest(String bucketName, String key, File file)

et

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

La méthode inputstream + ObjectMetadata nécessite une métadonnée minimale de Content Length de votre flux d'entrée. Si vous ne le faites pas, la mémoire tampon sera alors mise en mémoire tampon pour obtenir cette information, ce qui pourrait causer un problème de MOO. Vous pouvez également créer votre propre mémoire tampon en mémoire pour obtenir la longueur, mais vous devez ensuite obtenir un deuxième flux d'entrée.

Pas demandé par l'OP (limitations de son environnement), mais par quelqu'un d'autre, comme moi. Je trouve qu'il est plus facile et plus sûr (si vous avez accès à un fichier temporaire) d'écrire le flux d'entrée dans un fichier temporaire et de placer le fichier temporaire. Pas de mémoire tampon en mémoire et pas besoin de créer un deuxième flux d'entrée.

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

Lors de l’écriture sur S3, vous devez spécifier la longueur de l’objet S3 pour vous assurer qu’il n’ya pas d’erreur de mémoire insuffisante. 

L'utilisation de IOUtils.toByteArray(stream) est également sujette aux erreurs de MOO car elle est supportée par ByteArrayOutputStream 

La meilleure option est donc d’écrire d’abord le flux d’entrée dans un fichier temporaire sur un disque local, puis d’utiliser ce fichier pour écrire dans S3 en spécifiant la longueur du fichier temporaire.

6
srikfreak

en fait, je fais un peu la même chose, mais sur mon stockage AWS S3: -

Code pour le servlet qui reçoit le fichier téléchargé: -

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 qui télécharge ces données en tant qu'objet 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;
    }
}

Remarque: - J'utilise le fichier de propriétés aws pour les informations d'identification.

J'espère que cela t'aides.

4
streak

J'ai créé une bibliothèque qui utilise des téléchargements multipart en arrière-plan pour éviter de tout mettre en mémoire tampon dans la mémoire et n'écrit pas non plus sur le disque: https://github.com/alexmojaki/s3-stream-upload

3
Alex Hall

Le simple fait de passer l'objet fichier à la méthode putobject a fonctionné pour moi. Si vous obtenez un flux, essayez de l'écrire dans un fichier temporaire avant de le transmettre à S3. 

amazonS3.putObject(bucketName, id,fileObject);

J'utilise le SDK d'Aws v1.11.414

La réponse sur https://stackoverflow.com/a/35904801/2373449 m'a aidé 

0
Vikram