从URL获取图片并上传到Amazon S3

4

我想从URL直接加载图片,但不想将其保存在服务器上,我想直接从内存中将其上传到Amazon S3服务器。

这是我的代码:

Dim wc As New WebClient
Dim fileStream As IO.Stream = wc.OpenRead("http://www.domain.com/image.jpg")

Dim request As New PutObjectRequest()
request.BucketName = "mybucket"
request.Key = "file.jpg"
request.InputStream = fileStream 
client.PutObject(request)

亚马逊 API 给我返回了错误信息“无法确定内容长度”。流文件流最终变成了“System.Net.ConnectStream”,我不确定这是否正确。
相同的代码在使用 HttpPostedFile 的文件时可以正常工作,但现在我需要以这种方式使用它。
您有任何想法如何将流转换为符合亚马逊 API 预期的(包含完整长度的)格式吗?
3个回答

10
我在类似情景下遇到了同样的问题。
错误的原因是,为了上传一个对象,SDK需要知道将要上传的内容的完整长度。要能够获取流长度,必须具有可寻址性,但WebClient返回的流不具备可寻址性。要指示预期的长度,请在PutObjectRequest中设置Headers.ContentLength。如果无法从流对象中确定长度,SDK将使用此值。
为了使您的代码正常工作,请从WebClient调用返回的响应标头获取内容长度。然后设置PutObjectRequest.Headers.ContentLength,当然这取决于服务器返回的内容长度值。
Dim wc As New WebClient
Dim fileStream As IO.Stream = wc.OpenRead("http://www.example.com/image.jpg")
Dim contentLength As Long = Long.Parse(client.ResponseHeaders("Content-Length"))

Dim request As New PutObjectRequest()
request.BucketName = "mybucket"
request.Key = "file.jpg"
request.InputStream = fileStream 
request.Headers.ContentLength = contentLength
client.PutObject(request)

1
这应该是最佳答案。它不涉及复制内存流。我所要做的就是添加一行代码来消除错误。(c#)request.Headers.ContentLength = long.Parse(client.ResponseHeaders.Get("Content-Length")); - herostwist
2
这对我在从Zip归档中上传文件时很有帮助。使用ZipArchiveEntry,因为我能够使用未压缩的长度。 - Hastarin

9
当我使用GetObjectResponse()方法及其属性ResponseStream将文件从一个文件夹复制到另一个文件夹时,我遇到了同样的问题。我注意到AWS SDK(2.3.45)存在一些缺陷,例如另一个名为WriteResponseStreamToFile的方法在GetObjectResponse()中根本无法工作。这些缺失的功能需要一些解决方法。
我通过打开文件并将其放入MemoryStream对象的字节数组中来解决了这个问题。
请尝试以下代码(C#代码)
WebClient wc = new WebClient();
Stream fileStream =  wc.OpenRead("http://www.domain.com/image.jpg");

byte[] fileBytes = fileStream.ToArrayBytes();

PutObjectRequest request  = new PutObjectRequest();
request.BucketName = "mybucket";
request.Key = "file.jpg";
request.InputStream = new MemoryStream(fileBytes);
client.PutObject(request);

扩展方法

public static byte[] ToArrayBytes(this Stream input)
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }

您也可以创建一个没有字节数组的MemoryStream。但是,在S3中进行第一次PutObject之后,MemoryStream将被丢弃。如果您需要放置其他对象,我建议选择第一种选项。

 WebClient wc = new WebClient();
 Stream fileStream =  wc.OpenRead("http://www.domain.com/image.jpg");

 MemoryStream fileMemoryStream = fileStream.ToMemoryStream();  

 PutObjectRequest request  = new PutObjectRequest();
 request.BucketName = "mybucket";
 request.Key = "file.jpg";
 request.InputStream = fileMemoryStream ;
 client.PutObject(request);

The extesion method

    public static MemoryStream ToMemoryStream(this Stream input)
    {
        byte[] buffer = new byte[16 * 1024];

        int read;
        MemoryStream ms = new MemoryStream();
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            ms.Write(buffer, 0, read);
        }
        return ms;
    }

1

我想出了一种解决方案,当其他方法无法获取长度时,使用UploadPart,并且这不会将整个文件加载到内存中。

        if (args.DocumentContents.CanSeek)
        {
            PutObjectRequest r = new PutObjectRequest();
            r.InputStream = args.DocumentContents;
            r.BucketName = s3Id.BucketName;
            r.Key = s3Id.ObjectKey;
            foreach (var item in args.CustomData)
            {
                r.Metadata[item.Key] = item.Value;
            }
            await S3Client.PutObjectAsync(r);
        }
        else
        {
            // if stream does not allow seeking, S3 client will throw error:
            // Amazon.S3.AmazonS3Exception : Could not determine content length
            // as a work around, if cannot use length property, will chunk
            // file into sections and use UploadPart, so do not have to load
            // entire file into memory as a single MemoryStream.

            var r = new InitiateMultipartUploadRequest();
            r.BucketName = s3Id.BucketName;
            r.Key = s3Id.ObjectKey;
            foreach (var item in args.CustomData)
            {
                r.Metadata[item.Key] = item.Value;
            }
            var multipartResponse = await S3Client.InitiateMultipartUploadAsync(r);
            try
            {
                var completeRequest = new CompleteMultipartUploadRequest
                {
                    UploadId = multipartResponse.UploadId,
                    BucketName = s3Id.BucketName,
                    Key = s3Id.ObjectKey,
                };

                // just using this size, because it is the max for Azure File Share, but it could be any size
                // for S3, even a configured value
                const int blockSize = 4194304;

                // BinaryReader gives us access to ReadBytes
                using (var reader = new BinaryReader(args.DocumentContents))
                {
                    var partCounter = 1;
                    while (true)
                    {
                        byte[] buffer = reader.ReadBytes(blockSize);
                        if (buffer.Length == 0)
                            break;

                        using (MemoryStream uploadChunk = new MemoryStream(buffer))
                        {
                            uploadChunk.Position = 0;

                            var uploadRequest = new UploadPartRequest
                            {
                                BucketName = s3Id.BucketName,
                                Key = s3Id.ObjectKey,
                                UploadId = multipartResponse.UploadId,
                                PartNumber = partCounter,
                                InputStream = uploadChunk,
                            };
                            // could call UploadPart on multiple threads, instead of using await, but that would 
                            // cause more data to be loaded into memory, which might be too much
                            var part2Task = await S3Client.UploadPartAsync(uploadRequest);
                            completeRequest.AddPartETags(part2Task);
                        }

                        partCounter++;
                    }

                    var completeResponse = await S3Client.CompleteMultipartUploadAsync(completeRequest);

                }
            }
            catch
            {
                await S3Client.AbortMultipartUploadAsync(s3Id.BucketName, s3Id.ObjectKey
                    , multipartResponse.UploadId);

                throw;
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接