使用SAS和REST上传到Azure Blob

时间:2016-06-07 11:24:12

标签: rest azure blob poco

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

我用C#在服务器上生成SAS(似乎工作正常):

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=%%%%%%%%%%%%%%%%%%%%%%%

然后我尝试使用Poco和Blob 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?

3 个答案:

答案 0 :(得分:3)

我相信您的大多数代码都是正确的,您需要做的就是在SAS网址中插入文件名。

现在我已经更仔细地看到了这个问题,这就是正在发生的事情:

您正在blob容器(my-blob)上创建SAS并使用此SAS上传文件(让我们称之为fname.ext)。但是,您不在SAS URL中包含文件名,因此Azure Storage Service假定您尝试在my-blob容器中上传名为$root的文件,以便在服务上当Azure Blob服务尝试验证SAS时,它会根据$root容器对其进行验证。由于您为my-blob容器创建了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时并不需要这些。

答案 1 :(得分: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生成中省略这些权限。

答案 2 :(得分:1)

您的错误的一个可能原因可能是请求日期太旧。您今晚将请求日期设置为午夜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")来获取正确格式的日期/时间。

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