2016-06-07 132 views
2

我在使用SAS(共享访问签名)从C++写入Azure块Blob时遇到问题。我正在使用Blob REST API和Poco。 HTTP请求返回错误404(资源不存在),但我无法弄清楚我做错了什么。使用SAS和REST上传到Azure Blob

我生成SAS在C#这样的服务器上(似乎很好地工作):

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString")); 
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); 
CloudBlobContainer container = blobClient.GetContainerReference("my-blob"); 
container.CreateIfNotExists(); 
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy(); 
sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(40); 
sasConstraints.Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List; 
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints); 
return Request.CreateResponse(HttpStatusCode.OK, container.Uri + sasContainerToken); 

在Azure的门户我的确可以看到如预期正在创建的Blob容器。我使用HTTP请求在C++中接收此SAS。我能得到什么看起来像这样(一些名字和签名代替出于安全原因):

https://myname.blob.core.windows.net/my-blob?sv=2012-02-12&se=2016-06-07T11%3A13%3A19Z&sr=c&sp=wl&sig=%%%%%%%%%%%%%%%%%%%%%%%

然后我尝试创建使用波索和斑点REST API的文件。这看起来是这样的:

std::string cloudUrl = sasURI + "&restype=container"; 
std::string fileName = "fname.ext"; 
Poco::URI* uri = new Poco::URI(cloudUrl.c_str()); 
std::string* path = new std::string(uri->getPathAndQuery()); 
Poco::Net::HTTPSClientSession* session = new Poco::Net::HTTPSClientSession(uri->getHost(), uri->getPort()); 
std::string method = Poco::Net::HTTPRequest::HTTP_PUT; 
Poco::Net::HTTPRequest* request = new Poco::Net::HTTPRequest(method, *path, Poco::Net::HTTPMessage::HTTP_1_1); 
request->add("x-ms-blob-content-disposition", "attachment; filename=\"" + fileName + "\""); 
request->add("x-ms-blob-type", "BlockBlob"); 
request->add("x-ms-meta-m1", "v1"); 
request->add("x-ms-meta-m2", "v2"); 
Poco::Net::HTTPResponse* httpResponse = new Poco::Net::HTTPResponse(); 
int fileContent = 42; 
request->setContentLength(sizeof(int)); 
request->setKeepAlive(true); 
std::ostream& outputStream = session->sendRequest(*request); 
outputStream << fileContent; 
std::istream &is = session->receiveResponse(*httpResponse); 
Poco::Net::HTTPResponse::HTTPStatus status = httpResponse->getStatus(); 
std::ostringstream outString; 
Poco::StreamCopier::copyStream(is, outString); 
if (status != Poco::Net::HTTPResponse::HTTP_OK) 
{ 
    Logger::log("Connection failed\nstatus:", status, "\nreason:", httpResponse->getReason(), "\nreasonForStatus:", httpResponse->getReasonForStatus(status), "\nresponseContent:", outString.str()); 
} 

我抬头here如何REST API的作品。我发现here,当使用SAS时,我不需要进行常规身份验证。

我在这里做错了什么?为什么我会收到错误404?

+0

[标签:sas]不是你正在寻找的SAS ... – Joe

回答

3

我终于明白这里出了什么问题。 :)

在上面的代码中有两个问题。首先是文件名需要插入到URL中,正如Gaurav Mantri解释的那样。这是诀窍:

int indexOfQuestionMark = cloudUrl.find('?'); 
cloudUrl = cloudUrl.substr(0, indexOfQuestionMark) + "/" + fileName + cloudUrl.substr(indexOfQuestionMark); 

另一个问题是,我没有上传足够的字节。 sizeof(int)是4个字节,而将42个字符转换为字符时,只有2个字节。服务器继续等待剩下的2个字节。这使得这个在上面的示例代码的正确路线:

request->setContentLength(2); 

而且,它的工作原理没有这三条线,所以我想不是在需要的时候:

request->add("x-ms-blob-content-disposition", "attachment; filename=\"" + fileName + "\""); 
request->add("x-ms-meta-m1", "v1"); 
request->add("x-ms-meta-m2", "v2"); 

同样,加入这不似乎需要:"&restype=container"

最后,为了编写SharedAccessBlobPermissions.List权限是不需要的,因此可以在服务器端的SAS一代中省略这些权限。

1

您的错误的一个可能的原因可能是请求日期太旧。您将请求日期设置为今晚Midnight UTC。 Azure存储允许约15分钟的时钟偏差。请求日期/时间太“太旧”是这个403错误的主要原因之一(除了错误的账户密钥和SAS情况下的过期令牌)。

这就是你如何设置x-ms-date请求标题。

request->add("x-ms-date", "2016-06-07"); 

这个头的值应该采用以下格式进行格式化:

request->add("x-ms-date", "Sun, 11 Oct 2009 21:49:13 GMT"); 

通常在C#中的世界里,我们会做一个DateTime.UtcNow.ToString("R")得到正确格式的日期/时间。

请相应地更改您的代码,看看是否可以解决问题。

+0

感谢您的建议! :)我已经尝试过对日期进行硬编码,但似乎没有解决它。我在这个日期输入:“2016年6月7日星期二11:49:00 GMT” – Oogst

+1

请勿将日期硬编码,请将当前时间设为UTC。另外,请删除'request-> add(“x-ms-version”,“2015-02-21”);'因为它已经包含在SAS URL中。 –

+1

诊断错误的一种方法是读取错误响应流。它应该是一个关于错误的更多细节的XML。我注意到的另一件事是你的SAS已经过期(se = 2016-06-07T11:13:19Z)。请使用尚未过期的SAS。到期的SAS也会导致403错误。 HTH。 –

3

我相信你的大部分代码都是正确的,你需要做的就是在SAS URL中插入文件名。

现在,我已经更加仔细地看到了这个问题,这是发生了什么事:

你在一个blob容器(my-blob)创造了SAS和使用该SAS上传的文件(让我们称之为fname.ext )。但是,您不在SAS URL中包含文件名,因此Azure存储服务假设您尝试在$root容器中上载名为my-blob的文件,所以在Azure Blob服务尝试验证SAS时,根据$root容器对其进行验证。由于您为容器创建了SAS,并且Azure服务使用$root容器,因此SAS不匹配,这就是为什么您会收到403错误。

您需要做的是在SAS URL中插入文件名。所以,你的SAS URL(或请求URL)会是这样的(请注意,我说fname.ext有):

https://myname.blob.core.windows.net/my-blob/fname.ext SV = 2012-02-12 & SE = 2016-06-07T11 %3A13%3A19Z & SR = C & SP = WL & SIG = %%%%%%%%%%%%%%%%%%%%%%%

而且,你不需要以下两行代码:

request->add("x-ms-version", "2015-02-21"); 
request->add("x-ms-date", "2016-06-07"); 

因为使用SAS时不需要这些。

+0

如果我这样做''session-> receiveResponse(* httpResponse)行'挂了半分钟左右,然后'SSLConnectionUnexpectedlyClosedException'抛出。我也没有看到该文件出现在Azure门户上的blob中。我尝试使用和不使用版本和日期行。你的解释与Gaurav Mantri在他自己的回答中发表的评论类似,但他建议向SAS添加“&comp = list&restype = container”,这给出了不同的错误。我无法找到任何有关“comp”和“restype”值的文档。 – Oogst

+0

其实我提供了两个答案:)。你不需要做“comp = list ...”的事情,因为它是列举blob的,而你正在尝试上传一个blob。你可以尝试做一个HTTP请求吗?只需在请求URL中将“h​​ttps”更改为“http”即可。 –

+0

另请参阅此线程获取您遇到的异常:http://stackoverflow.com/questions/24773000/upload-a-file-using-poco-ssl-connection-unexpectedly-closed-exception –