InputStreamの長さの例を使用したAmazonS3putObject


83

Javaを使用してS3にファイルをアップロードしています-これは私がこれまでに得たものです:

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(ここから取得)を次のように設定しようとすると、次のようになります。

// 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が無効でした。

私は何が間違っているのですか?

助けていただければ幸いです。

PS私はGoogleApp Engineを使用しています-AppEngineがFileOutputStreamをサポートしていないため、ファイルをディスクに書き込んだり、一時ファイルを作成したりできません。


IOUtils.toByteArrayはファイル全体をメモリに読み込むため、ファイルのサイズによっては、適切な解決策にはなりません。より良い解決策は、ファイルプロバイダーにファイルサイズについて要求し、それをS3にストリーミングすることです。この方法では、サイズに関する情報がすでにあるため、メモリ内のすべてのファイルをダウンロードする必要はありません
Hamdi

回答:


69

元の質問には答えられず、同じ問題に遭遇しなければならなかったため、MD5問題の解決策は、S3が通常考えている16進数でエンコードされたMD5文字列を望まないことです。

代わりに、私はこれをしなければなりませんでした。

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

基本的に、MD5値に必要なのは、16進文字列ではなく、Base64でエンコードされた生のMD5バイト配列です。私がこれに切り替えたとき、それは私にとって素晴らしい働きを始めました。


そして、私たちはwinnahhhhを持っています!MD5の問題に答える余分な努力に感謝します。それは私が...のために掘った部分です
オタク株式

この場合のコンテンツは何ですか?聞き取れませんでした。同じ警告が表示されます。少し助けてください。
Shaonline 2016年

@ShaonlineコンテンツはinputStreamです
sirvon 2016年

16進数からMD5バイト配列に戻す方法はありますか?それが私たちのDBに保存するものです。
ジョエル

meta.setContentLength(IOUtils.toByteArray(stream).length);に注意してください。InputStreamを消費します。AWS APIがそれを読み取ろうとすると、長さがゼロであるため失敗します。ByteArrayInputStreamから新しい入力ストリームを作成する必要がありますbyteArrayInputStream = new ByteArrayInputStream(bytes);
バーニー・レンズ2017年

43

Amazonからのコンテンツの長さのエラーを解決するだけの場合は、入力ストリームからLongにバイトを読み取り、それをメタデータに追加するだけです。

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

この正確な方法を使用して入力ストリームを2回読み取る必要があるため、非常に大きなファイルをアップロードする場合は、配列に1回読み取ってから、そこから読み取る必要がある場合があります。


24
したがって、ストリームを2回読み取ることにします。そして、ファイル全体をメモリに保存します。これにより、S3が警告するOOMが発生する可能性があります。
Pavel Vyazankin 2014年

3
入力ストリームを使用できることのポイントは、データを一度にメモリにロードするのではなく、ストリーミングできることです。
ジョーダンデビッドソン

AmazonServiceExceptionの場合、それほど多くのsoutを出力する必要はありません。getMessageメソッドは、getErrorTypeを除くすべてを出力します。
saurabheights 2017年

33

アップロード用に、S3SDKには2つのputObjectメソッドがあります。

PutObjectRequest(String bucketName, String key, File file)

そして

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

inputstream + ObjectMetadataメソッドには、inputstreamのコンテンツ長の最小メタデータが必要です。そうしないと、その情報を取得するためにメモリ内にバッファリングされ、OOMが発生する可能性があります。または、独自のメモリ内バッファリングを実行して長さを取得することもできますが、その場合は2番目の入力ストリームを取得する必要があります。

OP(彼の環境の制限)からではなく、私のような他の誰かのために尋ねられました。入力ストリームを一時ファイルに書き込み、一時ファイルを配置する方が簡単で安全だと思います(一時ファイルにアクセスできる場合)。インメモリバッファがなく、2番目の入力ストリームを作成する必要もありません。

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)の2番目の引数は、Type FileまたはOutputStream?
Shaonline 2016

1
これはIOを集中的に使用しますが、私はまだこれに投票します。これは、大きなファイルオブジェクトでOOMを回避するための最良の方法である可能性があるためです。ただし、誰でも特定のn * bytesを読み取り、パーツファイルを作成して、s3に個別にアップロードすることもできます。
linehrr 2018

7

S3への書き込み中に、S3オブジェクトの長さを指定して、メモリ不足エラーが発生しないようにする必要があります。

IOUtils.toByteArray(stream)これはByteArrayOutputStreamによってサポートされているため、使用するとOOMエラーが発生しやすくなります。

したがって、最良のオプションは、最初に入力ストリームをローカルディスク上の一時ファイルに書き込み、次にそのファイルを使用して一時ファイルの長さを指定してS3に書き込むことです。


1
おかげで、私はグーグルアプリエンジンを使用しています(更新された質問)-ファイルをディスクに書き込むことができません、それができれば、ファイルを受け取るputObjectオーバーロードを使用できます:(
JohnIdol

@srikantaちょうどあなたのアドバイスを受けました。一時ファイルの長さを指定する必要はありません。一時ファイルをそのまま渡すだけです。
Siya Sosibo 2016年

参考までに、私のようにObjectMetadataで行われるサーバー側の暗号化を指定する場合、一時ファイルアプローチはオプションではありません。残念ながら、PutObjectRequest(文字列bucketName、文字列キー、ファイルファイル、ObjectMetadataメタデータ)はありません
Kevin Pauli

@kevin pauliできることrequest.setMetadata();
dbaq 2016

6

私は実際には多少同じことをしていますが、AWS S3ストレージで:-

アップロードされたファイルを受信して​​いるサーブレットのコード:-

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プロパティファイルを使用しています。

お役に立てれば。



-1

ファイルオブジェクトをputobjectメソッドに渡すだけでうまくいきました。ストリームを取得している場合は、S3に渡す前に一時ファイルに書き込んでみてください。

amazonS3.putObject(bucketName, id,fileObject);

Aws SDKv1.11.414を使用しています

https://stackoverflow.com/a/35904801/2373449での回答は私を助けました


ストリームがある場合は、そのストリームを使用します。データを取得するためだけに(一時)ファイルにストリームを書き込むことは非効率的であり、追加の頭痛の種(ファイルの削除、ディスク使用量)が発生します
devstructor 2010

これにより、AWSに保存するときに一般的な方法である暗号化などのメタデータを渡すことができなくなります
user14125 2320年

-15

log4j-1.2.12.jarファイルを追加すると、問題が解決しました


2
-1:これはログ警告を非表示にするだけで、エラー自体は解決しないと思います。大変申し訳ありませんが、結局のところ、これが最初の答えですが、これではこの質問は解決されません。
romualdr 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.