使用 SAS 和 REST 上传到 Azure Blob
Upload to Azure Blob using SAS and REST
我在使用 SAS(共享访问签名)从 C++ 写入 Azure 块 Blob 时遇到问题。我正在使用 Blob REST API 和 Poco。 HTTP 请求 returns 错误 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。我得到的看起来像这样(出于安全原因替换了一些名称和签名):
然后我尝试使用 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 how the REST API works. I found here 使用 SAS 时不需要进行常规身份验证。
我在这里做错了什么?为什么我会收到错误 404?
导致错误的一个可能原因是请求日期太旧。您将请求日期设置为今晚午夜 UTC。 Azure 存储允许大约 15 分钟的时钟偏差。请求 date/time 为 "too old" 是此 403 错误的主要原因之一(除了不正确的帐户密钥和 SAS 的过期令牌)。
这就是您设置 x-ms-date
请求 header 的方式。
request->add("x-ms-date", "2016-06-07");
此 header 的值应采用以下格式:
request->add("x-ms-date", "Sun, 11 Oct 2009 21:49:13 GMT");
通常在 C# 世界中,我们会执行 DateTime.UtcNow.ToString("R")
以获得正确格式的 date/time。
请相应地更改您的代码,看看是否能解决问题。
我相信你的大部分代码都是正确的,你需要做的就是在你的 SAS 中插入文件名 URL。
现在我已经更仔细地看到了这个问题,这是正在发生的事情:
您正在 blob 容器 (my-blob
) 上创建 SAS 并使用此 SAS 上传文件(我们称之为 fname.ext
)。但是,您没有在 SAS URL 中包含文件名,因此 Azure 存储服务假设您正在尝试在服务上的 $root
容器中上传名为 my-blob
的文件当 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 时并不真正需要这些。
我终于弄清楚这里出了什么问题。 :)
上面的代码有两个问题。首先是文件名需要插入 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 生成中忽略这些权限。
我在使用 SAS(共享访问签名)从 C++ 写入 Azure 块 Blob 时遇到问题。我正在使用 Blob REST API 和 Poco。 HTTP 请求 returns 错误 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。我得到的看起来像这样(出于安全原因替换了一些名称和签名):
然后我尝试使用 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 how the REST API works. I found here 使用 SAS 时不需要进行常规身份验证。
我在这里做错了什么?为什么我会收到错误 404?
导致错误的一个可能原因是请求日期太旧。您将请求日期设置为今晚午夜 UTC。 Azure 存储允许大约 15 分钟的时钟偏差。请求 date/time 为 "too old" 是此 403 错误的主要原因之一(除了不正确的帐户密钥和 SAS 的过期令牌)。
这就是您设置 x-ms-date
请求 header 的方式。
request->add("x-ms-date", "2016-06-07");
此 header 的值应采用以下格式:
request->add("x-ms-date", "Sun, 11 Oct 2009 21:49:13 GMT");
通常在 C# 世界中,我们会执行 DateTime.UtcNow.ToString("R")
以获得正确格式的 date/time。
请相应地更改您的代码,看看是否能解决问题。
我相信你的大部分代码都是正确的,你需要做的就是在你的 SAS 中插入文件名 URL。
现在我已经更仔细地看到了这个问题,这是正在发生的事情:
您正在 blob 容器 (my-blob
) 上创建 SAS 并使用此 SAS 上传文件(我们称之为 fname.ext
)。但是,您没有在 SAS URL 中包含文件名,因此 Azure 存储服务假设您正在尝试在服务上的 $root
容器中上传名为 my-blob
的文件当 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 时并不真正需要这些。
我终于弄清楚这里出了什么问题。 :)
上面的代码有两个问题。首先是文件名需要插入 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 生成中忽略这些权限。