问题
after reading all posts that come close to this, I still find no solution and after "fiddling" around for hours, here goes a post, in hopes that someone has an idea:
The closest response I have found is here
but two things from these posts do not apply: 1. I'm using example code that certainly works in most cases. 2. I do not have access to any Azure StorageClient simplifiers, since I am required to use the .NET client profile. 3. It is probably a stupid error that I just don't see :-)
Here is the call:
// Create Blob
// using REST: http://msdn.microsoft.com/en-us/library/windowsazure/dd135733.aspx
BlobHelper blobHelper = new BlobHelper(_storageAccountName, _storageKey);
Console.WriteLine("Create container...");
if (!blobHelper.CreateContainer("mytestcontainer")) //(blobInfo.ContainerName))
{
Console.WriteLine("REST: Create Blob Failed");
}
And here is wire request (fiddler):
PUT /devstoreaccount1/mytestcontainer?restype=container HTTP/1.1
x-ms-date: Mon, 25 Jun 2012 16:54:01 GMT
x-ms-version: 2009-09-19
Authorization: SharedKey devstoreaccount1:REpHdtTSQrwGtXuEbLJmRQdpe/j2l5icmGUeFkQ09jw=
Host: 127.0.0.1:10000
Content-Length: 0
Connection: Keep-Alive
The same thing happens whether I use live Azure Storage or the Developer Storage.
Here is the wire response:
HTTP/1.1 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
Content-Length: 698
Content-Type: application/xml
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: d5430ddc-f146-4102-b8db-a8bfab0ed82f
Date: Mon, 25 Jun 2012 16:54:00 GMT
<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
RequestId:d5430ddc-f146-4102-b8db-a8bfab0ed82f
Time:2012-06-25T16:54:01.3354093Z</Message><AuthenticationErrorDetail>The MAC signature found in the HTTP request 'REpHdtTSQrwGtXuEbLJmRQdpe/j2l5icmGUeFkQ09jw=' is not the same as any computed signature. Server used following string to sign: 'PUT
0
x-ms-date:Mon, 25 Jun 2012 16:54:01 GMT
x-ms-version:2009-09-19
/devstoreaccount1/devstoreaccount1/mytestcontainer
restype:container'.</AuthenticationErrorDetail></Error>
Needless to say, this driving me nuts, so any tips appreciated.
The source code, which is directly from the Azure Storage REST API sample, RESTHelper.cs. This works in many scenarios, but not this one:
#region REST HTTP Request Helper Methods
// Construct and issue a REST request and return the response.
public HttpWebRequest CreateRESTRequest(string method, string resource, string requestBody = null, SortedList<string, string> headers = null,
string ifMatch = "", string md5 = "")
{
byte[] byteArray = null;
DateTime now = DateTime.UtcNow;
string uri = Endpoint + resource;
HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Method = method;
request.ContentLength = 0;
request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture));
request.Headers.Add("x-ms-version", "2009-09-19"); //2009-09-19, 2011-08-18
if (IsTableStorage)
{
request.ContentType = "application/atom+xml";
request.Headers.Add("DataServiceVersion", "1.0;NetFx");
request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx");
}
if (headers != null)
{
foreach (KeyValuePair<string, string> header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
if (!String.IsNullOrEmpty(requestBody))
{
request.Headers.Add("Accept-Charset", "UTF-8");
byteArray = Encoding.UTF8.GetBytes(requestBody);
request.ContentLength = byteArray.Length;
}
request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, ifMatch, md5));
if (!String.IsNullOrEmpty(requestBody))
{
request.GetRequestStream().Write(byteArray, 0, byteArray.Length);
}
return request;
}
// Generate an authorization header.
//RHT: http://msdn.microsoft.com/en-us/library/dd179428.aspx
public string AuthorizationHeader(string method, DateTime now, HttpWebRequest request, string ifMatch = "", string md5 = "")
{
string MessageSignature;
if (IsTableStorage)
{
MessageSignature = String.Format("{0}\n\n{1}\n{2}\n{3}",
method,
"application/atom+xml",
now.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
GetCanonicalizedResource(request.RequestUri, StorageAccount)
);
}
else
{
MessageSignature = String.Format("{0}\n\n\n{1}\n{5}\n\n\n\n{2}\n\n\n\n{3}{4}",
method,
(method == "GET" || method == "HEAD") ? String.Empty : request.ContentLength.ToString(),
ifMatch,
GetCanonicalizedHeaders(request),
GetCanonicalizedResource(request.RequestUri, StorageAccount),
md5
);
}
byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature);
System.Security.Cryptography.HMACSHA256 SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(StorageKey));
String AuthorizationHeader = "SharedKey " + StorageAccount + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
return AuthorizationHeader;
}
// Get canonicalized headers.
public string GetCanonicalizedHeaders(HttpWebRequest request)
{
ArrayList headerNameList = new ArrayList();
StringBuilder sb = new StringBuilder();
foreach (string headerName in request.Headers.Keys)
{
if (headerName.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
{
headerNameList.Add(headerName.ToLowerInvariant());
}
}
headerNameList.Sort();
foreach (string headerName in headerNameList)
{
StringBuilder builder = new StringBuilder(headerName);
string separator = ":";
foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
{
string trimmedValue = headerValue.Replace("\r\n", String.Empty);
builder.Append(separator);
builder.Append(trimmedValue);
separator = ",";
}
sb.Append(builder.ToString());
sb.Append("\n");
}
return sb.ToString();
}
// Get header values.
public ArrayList GetHeaderValues(NameValueCollection headers, string headerName)
{
ArrayList list = new ArrayList();
string[] values = headers.GetValues(headerName);
if (values != null)
{
foreach (string str in values)
{
list.Add(str.TrimStart(null));
}
}
return list;
}
// Get canonicalized resource.
public string GetCanonicalizedResource(Uri address, string accountName)
{
StringBuilder str = new StringBuilder();
StringBuilder builder = new StringBuilder("/");
builder.Append(accountName);
builder.Append(address.AbsolutePath);
str.Append(builder.ToString());
NameValueCollection values2 = new NameValueCollection();
if (!IsTableStorage)
{
//Uri.EscapeDataString(...)
//WebUtility.HtmlEncode(...)
//https://stackoverflow.com/questions/36315/alternative-to-httputility-for-net-3-5-sp1-client-framework
//NameValueCollection values = HttpUtility.ParseQueryString(address.Query);
//foreach (string str2 in values.Keys)
//{
// ArrayList list = new ArrayList(values.GetValues(str2));
// list.Sort();
// StringBuilder builder2 = new StringBuilder();
// foreach (object obj2 in list)
// {
// if (builder2.Length > 0)
// {
// builder2.Append(",");
// }
// builder2.Append(obj2.ToString());
// }
// values2.Add((str2 == null) ? str2 : str2.ToLowerInvariant(), builder2.ToString());
//}
}
ArrayList list2 = new ArrayList(values2.AllKeys);
list2.Sort();
foreach (string str3 in list2)
{
StringBuilder builder3 = new StringBuilder(string.Empty);
builder3.Append(str3);
builder3.Append(":");
builder3.Append(values2[str3]);
str.Append("\n");
str.Append(builder3.ToString());
}
return str.ToString();
}
#endregion
回答1:
Thanks Smarx,
Indeed, an eye opener, and it led me to the solution:
The .NET Client Profile is missing HttpUtility, so I replaced it with a regular expression that was not providing the format required by the builder. So, I replaced it with this one:
http://google-gdata.googlecode.com/svn/trunk/clients/cs/src/core/HttpUtility.cs
and it works. Here is what the header now looks like:
PUT /devstoreaccount1/mytestcontainer?restype=container HTTP/1.1
x-ms-date: Mon, 25 Jun 2012 18:57:04 GMT
x-ms-version: 2009-09-19
Authorization: SharedKey devstoreaccount1:J5D1E7PK/yNBgQITHmYgVuu4cHtcGad+YKGb1Lh/YUU=
Host: 127.0.0.1:10000
Content-Length: 0
And result:
HTTP/1.1 201 Created
Transfer-Encoding: chunked
Last-Modified: Mon, 25 Jun 2012 18:57:04 GMT
ETag: 0x8CF211B72040930
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 079bcdc1-a7fa-4207-99cd-b7f1c2d1b981
x-ms-version: 2009-09-19
Date: Mon, 25 Jun 2012 18:57:04 GMT
0
I had stepped through the code, but wasn't seeing this difference, thanks again, R.
来源:https://stackoverflow.com/questions/11194481/azure-rest-api-create-container-net-client-profile-403-forbidden-the-mac-sig