7

我正在尝试从 HTTP 流直接上传到 S3,而不是先存储在内存中或作为文件。我已经使用 Rackspace 云文件作为 HTTP 到 HTTP 执行此操作,但是 AWS 身份验证超出了我的范围,因此我尝试使用 SDK。

问题是上传流失败,出现以下异常:

"This stream does not support seek operations."

我试过PutObjectand TransferUtility.Upload,都失败了。

有什么方法可以在流进入时流入 S3,而不是将整个内容缓冲到 a MemoryStreamor FileStream

或者有没有使用 HTTPWebRequest 对 S3 请求进行身份验证的好例子,所以我可以复制我对 Cloud Files 所做的事情?

编辑: 或者AWSSDK 中是否有用于生成授权标头的辅助函数?

代码:

这是失败的 S3 部分(为了完整性而包含两种方法):

string uri = RSConnection.StorageUrl + "/" + container + "/" + file.SelectSingleNode("name").InnerText;
var req = (HttpWebRequest)WebRequest.Create(uri);
req.Headers.Add("X-Auth-Token", RSConnection.AuthToken);
req.Method = "GET";

using (var resp = req.GetResponse() as HttpWebResponse)
{
    using (Stream stream = resp.GetResponseStream())
    {
        Amazon.S3.Transfer.TransferUtility trans = new Amazon.S3.Transfer.TransferUtility(S3Client);
        trans.Upload(stream, config.Element("root").Element("S3BackupBucket").Value, container + file.SelectSingleNode("name").InnerText);

        //Use EITHER the above OR the below

        PutObjectRequest putReq = new PutObjectRequest();
        putReq.WithBucketName(config.Element("root").Element("S3BackupBucket").Value);
        putReq.WithKey(container + file.SelectSingleNode("name").InnerText);
        putReq.WithInputStream(Amazon.S3.Util.AmazonS3Util.MakeStreamSeekable(stream));
        putReq.WithMetaData("content-length", file.SelectSingleNode("bytes").InnerText);

        using (S3Response putResp = S3Client.PutObject(putReq))
        {

        }
    }

}

这就是我从 S3 到 Cloud Files 的成功方法:

using (GetObjectResponse getResponse = S3Client.GetObject(new GetObjectRequest().WithBucketName(bucket.BucketName).WithKey(file.Key)))
{
    using (Stream s = getResponse.ResponseStream)
    {
        //We can stream right from s3 to CF, no need to store in memory or filesystem.                                            
        var req = (HttpWebRequest)WebRequest.Create(uri);
        req.Headers.Add("X-Auth-Token", RSConnection.AuthToken);
        req.Method = "PUT";

        req.AllowWriteStreamBuffering = false;
        if (req.ContentLength == -1L)
            req.SendChunked = true;


        using (Stream stream = req.GetRequestStream())
        {
            byte[] data = new byte[32768];
            int bytesRead = 0;
            while ((bytesRead = s.Read(data, 0, data.Length)) > 0)
            {
                stream.Write(data, 0, bytesRead);
            }
            stream.Flush();
            stream.Close();
        }
        req.GetResponse().Close();
    }
}   
4

5 回答 5

6

由于似乎没有人回答,我花了时间根据史蒂夫回答的指导来解决这个问题:

为了回答这个问题“是否有任何使用 HTTPWebRequest 对 S3 请求进行身份验证的好例子,所以我可以复制我对 Cloud Files 所做的事情?”,这里是如何手动生成 auth 标头:

string today = String.Format("{0:ddd,' 'dd' 'MMM' 'yyyy' 'HH':'mm':'ss' 'zz00}", DateTime.Now);

string stringToSign = "PUT\n" +
    "\n" +
    file.SelectSingleNode("content_type").InnerText + "\n" +
    "\n" +
    "x-amz-date:" + today + "\n" +
    "/" + strBucketName + "/" + strKey;

Encoding ae = new UTF8Encoding();
HMACSHA1 signature = new HMACSHA1(ae.GetBytes(AWSSecret));
string encodedCanonical = Convert.ToBase64String(signature.ComputeHash(ae.GetBytes(stringToSign)));

string authHeader = "AWS " + AWSKey + ":" + encodedCanonical;

string uriS3 = "https://" + strBucketName + ".s3.amazonaws.com/" + strKey;
var reqS3 = (HttpWebRequest)WebRequest.Create(uriS3);
reqS3.Headers.Add("Authorization", authHeader);
reqS3.Headers.Add("x-amz-date", today);
reqS3.ContentType = file.SelectSingleNode("content_type").InnerText;
reqS3.ContentLength = Convert.ToInt32(file.SelectSingleNode("bytes").InnerText);
reqS3.Method = "PUT";

请注意添加的x-amz-date标头,因为 HTTPWebRequest 以与 AWS 预期不同的格式发送日期。

从那里开始,这只是重复我已经在做的事情。

于 2013-06-13T16:16:47.833 回答
2

查看适用于 Curl 的 Amazon S3 身份验证工具。从那个网页:

Curl 是一种流行的用于与 HTTP 服务交互的命令行工具。这个 Perl 脚本计算正确的签名,然后用适当的参数调用 Curl。

您可能可以调整它或其输出以供您使用。

于 2013-06-07T16:04:58.970 回答
1

我认为问题在于,根据AWS 文档Content-Length 是必需的,并且在流完成之前您不知道长度是多少。

(我猜想 Amazon.S3.Util.AmazonS3Util.MakeStreamSeekable 例程正在将整个流读入内存以解决这个问题,这使得它不适合您的场景。)

您可以做的是分块读取文件并使用MultiPart upload上传它们。

PS,我假设您知道用于 dotnet 的 AWSSDK 的 C# 源代码在Github上。

于 2013-06-07T17:52:43.293 回答
1

这是一个真正的 hack(可能会因 AWSSDK 的新实现而中断),并且它需要知道所请求文件的长度,但是如果您使用此类(要点)包装响应流,如图所示以下:

long length = fileLength;  

您可以通过多种方式获取文件长度。我是从 Dropbox 链接上传的,所以他们给了我长度和 url。或者,您可以执行 HEAD 请求并获取 Content-Length。

string uri = RSConnection.StorageUrl + "/" + container + "/" + file.SelectSingleNode("name").InnerText;
var req = (HttpWebRequest)WebRequest.Create(uri);
req.Headers.Add("X-Auth-Token", RSConnection.AuthToken);
req.Method = "GET";

using (var resp = req.GetResponse() as HttpWebResponse)
{
    using (Stream stream = resp.GetResponseStream())
    {
        //I haven't tested this path
        Amazon.S3.Transfer.TransferUtility trans = new Amazon.S3.Transfer.TransferUtility(S3Client);
        trans.Upload(new HttpResponseStream(stream, length), config.Element("root").Element("S3BackupBucket").Value, container + file.SelectSingleNode("name").InnerText);

        //Use EITHER the above OR the below
        //I have tested this with dropbox data
        PutObjectRequest putReq = new PutObjectRequest();
        putReq.WithBucketName(config.Element("root").Element("S3BackupBucket").Value);
        putReq.WithKey(container + file.SelectSingleNode("name").InnerText);
        putReq.WithInputStream(new HttpResponseStream(stream, length)));
        //These are necessary for really large files to work
        putReq.WithTimeout(System.Threading.Timeout.Infinite);
        putReq.WithReadWriteTimeout(System.Thread.Timeout.Infinite);


        using (S3Response putResp = S3Client.PutObject(putReq))
        {

        }
    }

}

hack 覆盖 Position 和 Length 属性,并为 Position{get} 返回 0,noop'ing Position{set},并为 Length 返回已知长度。

我认识到,如果您没有长度或者提供源的服务器不支持 HEAD 请求和 Content-Length 标头,这可能不起作用。我也意识到如果报告的 Content-Length 或提供的长度与文件的实际长度不匹配,它可能不起作用。

在我的测试中,我还将 Content-Type 提供给 PutObjectRequest,但我认为这不是必需的。

于 2013-11-14T02:07:56.530 回答
0

正如 sgmoore 所说,问题在于您的内容长度无法从 HTTP 响应中找到。但是 HttpWebResponse 确实有一个可用的内容长度属性。因此,您实际上可以自己形成对 S3 的 Http 发布请求,而不是使用 Amazon 库。

这是另一个 Stackoverflow 问题,它设法用我看来完整的代码来做到这一点。

于 2013-06-07T18:11:12.180 回答