我正在尝试从HTTP流直接上传到S3,而不是先存储在内存中或作为文件存储。我已经使用Rackspace Cloud Files作为HTTP到HTTP进行此操作,但AWS身份验证超出了我的范围,因此我尝试使用SDK。
问题是上传流失败并出现此异常:
"This stream does not support seek operations."
我尝试使用PutObject
和TransferUtility.Upload
,两者都失败了。
有没有办法在流进入时流入S3,而不是将整个内容缓冲到MemoryStream
或FileStream
?
或是否有使用HTTPWebRequest对S3请求进行身份验证的好例子,所以我可以复制我对Cloud Files的处理方式?
编辑: 或 AWSSDK中是否有辅助功能来生成授权标头?
CODE:
这是失败的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到云文件成功的方法:
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();
}
}
答案 0 :(得分: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所期望的。
从那时起,只是重复我已经在做的事情。
答案 1 :(得分:2)
看看Amazon S3 Authentication Tool for Curl。从该网页:
Curl是一种流行的命令行工具,用于与HTTP交互 服务。此Perl脚本计算正确的签名,然后调用 使用适当的参数进行卷曲。
你可能会调整它或它的输出供你使用。
答案 2 :(得分:1)
我认为问题是根据AWS Documentation Content-Length是必需,并且您不知道在流完成之前的长度是多少。
(我猜想Amazon.S3.Util.AmazonS3Util.MakeStreamSeekable例程正在将整个流读入内存以解决此问题,这使得它不适合您的场景。)
您可以做的是以块的形式阅读文件并使用MultiPart upload上传它们。
PS,我假设您知道dotnet的AWSSDK的C#源位于Github。
答案 3 :(得分:1)
这是一个真正的黑客攻击(它可能会破坏AWSSDK的新实现),它需要知道所请求文件的长度,但是如果你用this class (a gist)所示包装响应流如下图所示:
long length = fileLength;
您可以通过多种方式获取文件长度。我从Dropbox链接上传,所以他们给了我 长度与网址一起。或者,您可以执行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或提供的长度与文件的实际长度不匹配,它可能不起作用。
在我的测试中,我还向PutObjectRequest提供了Content-Type,但我不认为这是必要的。
答案 4 :(得分:0)
正如sgmoore所说,问题是你的内容长度不能从HTTP响应中找到。但是,HttpWebResponse确实具有可用的内容长度属性。因此,您实际上可以自己向S3发送Http post请求,而不是使用Amazon库。
Here's another Stackoverflow question设法使用看起来像我的完整代码。