2011-12-02 6 views
64

sto caricando un file da S3 utilizzando Java - questo è quello che ho ottenuto finora:AmazonS3 putObject con l'esempio di lunghezza InputStream

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

Il file viene caricato, ma un WARNING viene generato quando non sto impostando la lunghezza del contenuto:

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. 

questo è un file sto caricando e la variabile stream è un InputStream, da cui posso ottenere l'array di byte in questo modo: 0.123..

Così, quando provo a impostare la lunghezza del contenuto e MD5 (tratto da here) come questo:

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

provoca il seguente errore di tornare da S3:

Il Contenuto: MD5 specificato non è valido.

Cosa sto sbagliando?

Qualsiasi aiuto apprezzato!

P.S. Sono su Google App Engine - Non riesco a scrivere il file su disco o create a temp file perché AppEngine non supporta FileOutputStream.

risposta

56

Poiché la domanda originale non ha mai avuto risposta, e ho dovuto incappare in questo stesso problema, la soluzione per il problema MD5 è che S3 non vuole la stringa MD5 codificata Hex che normalmente si pensa.

Invece, ho dovuto farlo.

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

Essenzialmente ciò che vogliono per il valore MD5 è il grezzo MD5 byte-array Base64, non la stringa esadecimale. Quando sono passato a questo ha iniziato a funzionare alla grande per me.

+0

E abbiamo una winnahhhh! Grazie per lo sforzo extra di risposta al problema MD5. Questa è la parte che stavo cercando ... –

+0

Yoh, i veri uomini MVP! –

+0

Qual è il contenuto in questo caso? non l'ho capito Sto avendo lo stesso avvertimento. Un piccolo aiuto, per favore.? – Shaonline

6

Durante la scrittura su S3, è necessario specificare la lunghezza dell'oggetto S3 per accertarsi che non vi siano errori di memoria esaurita.

Utilizzando IOUtils.toByteArray(stream) è anche soggetto a errori OOM perché questo è supportato da ByteArrayOutputStream

Così, l'opzione migliore è quella di scrivere prima l'InputStream in un file temporaneo sul disco locale e quindi utilizzare tale file per scrivere a S3 specificando la lunghezza del file temporaneo.

+0

Grazie, ma io sono su google app engine (domanda aggiornata) - non è possibile scrivere il file su disco, se potessi farlo potrei usare il sovraccarico putObject che prende un file :( – JohnIdol

+0

@srikanta Appena preso il tuo consiglio: non è necessario specificare la lunghezza del file temporaneo, basta passare il file temp così com'è. –

+0

FYI il file temp a pproach NON è un'opzione se, come me, si desidera specificare la crittografia lato server, che viene eseguita in ObjectMetadata. Sfortunatamente non esiste PutObjectRequest (String bucketName, String key, File file, ObjectMetadata metadata) –

35

Se tutto ciò che si sta tentando di fare è risolvere l'errore di lunghezza del contenuto da Amazon, è sufficiente leggere i byte dallo stream di input a Long e aggiungerlo ai metadati.

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

Avrete bisogno di leggere il flusso di input due volte utilizzando questo metodo esatto, quindi se si sta caricando un file molto grande si potrebbe aver bisogno di guardare la lettura di una volta in un array e poi la lettura da lì.

+16

Quindi la tua decisione è di leggere lo streaming due volte! E tu salva tutto il file in memoria. Ciò potrebbe causare OOM come avvisa S3! – dart

+2

Il punto di essere in grado di utilizzare un flusso di input è che è possibile eseguire lo streaming dei dati non caricarli tutti in memoria contemporaneamente. –

+0

Per AmazonServiceException, non è necessario stampare così tanti sout. metodo getMessage stampa tutto tranne getErrorType. – saurabheights

4

Io sono in realtà facendo un po stessa cosa, ma il mio storage AWS S3: -

Codice in materia di servlet che sta ricevendo file caricato: -

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

codice che viene caricando questi dati come oggetto 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; 
    } 
} 

Nota: - sto usando il file AWS proprietà per le credenziali.

Spero che questo aiuti.

19

per il caricamento, l'S3 SDK ha due metodi: putObject

PutObjectRequest(String bucketName, String key, File file) 

e

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

InputStream + ObjectMetadata metodo ha bisogno di un minimo di metadati contenuti Lunghezza del InputStream. Se non lo fai, allora buffererà in memoria per ottenere quell'informazione, questo potrebbe causare OOM. In alternativa, è possibile eseguire il buffering in memoria per ottenere la lunghezza, ma è necessario ottenere un secondo inputstream.

Non chiesto dall'OP (limitazioni del suo ambiente), ma per qualcun altro, come me. Trovo più facile e più sicuro (se si ha accesso al file temporaneo), scrivere l'inputstream in un file temporaneo e inserire il file temporaneo. Nessun buffer in memoria e nessun requisito per creare un secondo inputstream.

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

Il secondo argomento in copyInputStreamToFile (inputStream, scratchFile) è Type File o OutputStream? – Shaonline

+0

anche se questo è IO intensivo, ma continuo a votare per questo. poiché questo potrebbe essere il modo migliore per evitare OOM su file oggetto più grande. Tuttavia, chiunque potrebbe anche leggere determinati n * byte e creare file di parti e caricarli separatamente su s3. – linehrr

-9

l'aggiunta di file log4j-1.2.12.jar ha risolto il problema per me

+1

-1: Immagino che questo nasconderà solo l'avviso del registro ma non risolverà l'errore stesso. Mi spiace essere così duro, è la tua prima risposta dopo tutto, ma questo non risolve questa domanda. – romualdr