【问题标题】:Upload to Azure Blob using SAS and REST使用 SAS 和 REST 上传到 Azure Blob
【发布时间】:2016-10-07 06:41:44
【问题描述】:

我在使用 SAS(共享访问签名)从 C++ 写入 Azure 块 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?

【问题讨论】:

  • sas 不是您要查找的 SAS...

标签: rest azure blob poco


【解决方案1】:

我相信您的大部分代码都是正确的,您只需在 SAS URL 中插入文件名即可。

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

您正在 Blob 容器 (my-blob) 上创建 SAS,并使用此 SAS 上传文件(我们称之为 fname.ext)。但是,您没有在 SAS URL 中包含文件名,因此当 Azure Blob 服务尝试验证时,Azure 存储服务假定您正在尝试在 $root 容器中上传名为 my-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 时这些并不是真正需要的。

【讨论】:

  • 如果我这样做,session-&gt;receiveResponse(*httpResponse); 行会挂起半分钟左右,然后SSLConnectionUnexpectedlyClosedException 就会被抛出。我也没有看到该文件出现在 Azure 门户上的 blob 中。我尝试了有无版本和日期行。您的解释类似于 Gaurav Mantri 在对他自己的答案的评论中发布的内容,但他建议将 "&amp;comp=list&amp;restype=container" 添加到 SAS 中,这会产生不同的错误。我找不到任何关于 comprestype 可以在其中包含哪些值的文档。
  • 实际上我提供了两个答案:)。你不需要做“comp = list ...”的事情,因为它是为了列出blob并且你正在尝试上传一个blob。你能尝试做一个HTTP请求吗?只需将请求 URL 中的“https”更改为“http”即可。
  • 另外,请参阅此线程以了解您遇到的异常:stackoverflow.com/questions/24773000/…
  • 如果我将“https”更改为“http”,Poco 会抛出异常。如果我也将HTTPSClientSession 更改为HTTPClientSession,它仍然会引发异常。
  • 我仍然遇到这样的问题,即如果我将文件名添加到 URL Poco 的 receiveResponse() 函数会挂起半分钟然后超时。那是问题的更新代码,只添加了几行来插入文件名。
【解决方案2】:

我终于弄清楚这里出了什么问题。 :)

上面的代码有两个问题。首先是文件名需要插入到 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");

同样,似乎不需要添加:"&amp;restype=container"

最后,编写SharedAccessBlobPermissions.List 权限不需要,因此可以在服务器端的 SAS 生成中忽略这些权限。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 感谢您的建议! :) 我现在已经尝试对日期进行硬编码,但这似乎并没有解决它。我输入了这个日期:“2016 年 6 月 7 日星期二 11:49:00 GMT”
    • 不要硬编码日期,请将当前时间输入 UTC。另外,请删除 request-&gt;add("x-ms-version", "2015-02-21");,因为它已包含在您的 SAS URL 中。
    • 诊断错误的一种方法是读取错误响应流。它应该是包含有关错误的更多详细信息的 XML。我注意到的另一件事是您的 SAS 已过期 (se=2016-06-07T11:13:19Z)。请使用未过期的 SAS。过期的 SAS 也会导致 403 错误。 HTH。
    • 我在文档中看到 x-ms-version 确实应该没有必要,但如果我删除它,我会得到 HTTP_NOT_FOUND,这似乎表明需要这一行。
    • 您首先提到了 GMT,然后是 UTC。文档说UTC,所以我想这就是我应该使用的?我现在都试过了,它们都不起作用。此外,文档表明在使用 SAS 时根本不需要此日期。我试过完全删除日期,但这也没有用,所以我现在把它放回去了。一个相关的问题:如果用户更改了他的系统时间,Blob 存储会不会工作?毕竟在那种情况下,我会在数据包中添加错误的日期。
    猜你喜欢
    • 2014-07-13
    • 2013-09-21
    • 2012-02-18
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2013-10-30
    • 2015-11-18
    • 2021-10-08
    相关资源
    最近更新 更多