2011-12-02 38 views
64

我上傳使用Java文件S3 - 這是我走到這一步:AmazonS3 putObject隨同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())); 

文件被上傳,但是當我沒有設定則會出現警告內容長度:

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(從here拍攝)這樣的:

// 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無效。

我在做什麼錯?

任何幫助表示讚賞!

P.S.我在谷歌應用程序引擎 - 我無法將文件寫入到磁盤或者create a temp file因爲AppEngine上不支持FileOutputStream中。

回答

56

因爲原來的問題一直沒有回答,我只好碰上同樣的問題,對於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值什麼是Base64編碼的原始MD5字節數組,而不是十六進制字符串。當我轉向這個時,它開始爲我工作。

+0

我們有一個winnahhhh!感謝您解答MD5問題的額外努力。這是我正在挖掘的部分... –

+0

Yoh真正的MVP男人! –

+0

這種情況下的內容是什麼?我沒有得到它。我有同樣的警告。請給我一點幫助。? – Shaonline

6

雖然寫S3,你需要指定S3對象的長度,以確保不存在內存不足的錯誤。

使用IOUtils.toByteArray(stream)也容易出現OOM錯誤,因爲這是由ByteArrayOutputStream

所以支持下,最好的選擇是對的InputStream先寫在本地磁盤上的臨時文件,然後使用該文件被寫入到S3指定臨時文件的長度。

+0

謝謝,但我在谷歌應用程序引擎(更新問題) - 不能將文件寫入到磁盤,如果我能做到這一點,我可以使用putObject重載需要一個文件:( – JohnIdol

+0

@srikanta剛接手你的建議,不需要指定臨時文件的長度,只需傳遞臨時文件即可 –

+0

FYI臨時文件a如果像我一樣,想要指定服務器端加密(在ObjectMetadata中完成),則pproach不是一種選項。不幸的是,沒有PutObjectRequest(String bucketName,String key,File file,ObjectMetadata metadata) –

35

如果你正在嘗試做的是解決從亞馬遜的內容長度錯誤,那麼你可以只讀取輸入流中的字節到長,並添加元數據。

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

你需要讀取輸入流使用該確切的方法兩次,所以如果你上傳你可能需要看讀一次到一個數組,然後從那裏閱讀它非常大的文件。

+16

所以你的決定是讀取流兩次!並且您將整個文件保存在內存中。這可能會導致OOM,因爲S3警告! – dart

+2

能夠使用輸入流的一點是,您可以流式傳輸數據而不是一次將所有數據加載到內存中。 –

+0

對於AmazonServiceException,不需要打印太多的sout。 getMessage方法打印除getErrorType之外的所有內容。 – saurabheights

4

我實際上做的有點同樣的事情,但在我的AWS S3存儲: -

代碼的servlet正在接收上傳的文件: -

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屬性文件憑據。

希望這會有所幫助。

19

對於上傳,S3的SDK具有兩個putObject方法:

PutObjectRequest(String bucketName, String key, File file) 

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

InputStream的+ ObjectMetadata方法需要你的InputStream的內容長度的最小的元數據。如果你不這樣做,那麼它會緩衝內存中的信息,這可能會導致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(); 
    } 
} 
+0

copyInputStreamToFile(inputStream,scratchFile)中的第二個參數是Type File或OutputStream? – Shaonline

+0

雖然這是IO密集型,但我仍然投爲此。因爲這可能是在更大的文件對象上避免OOM的最佳方法。但是,任何人都可以讀取某些n *字節並創建零件文件並分別上傳到s3。 – linehrr

-9

加入log4j的-1.2.12.jar文件有解決了這個問題對我來說

+1

-1:我想這隻會隱藏日誌警告,但不能解決錯誤本身。對不起,這是如此苛刻,這是你的第一個答案,但這並不能解決這個問題。 – romualdr