看了张善友老师的几篇文章后决定认真学习一下Restfull风格的API开发和调用,于是先选用了RestSharp 作为客户端进行练习。调用的服务为阿里巴巴提供的开放存储服务。
阿里巴巴云存储服务提供了50G的免费空间以及每个月10G的流量,非常适合我们做点小应用。具体的API规则请查阅OSS存储服务开发文档。
1、推荐的使用方式
直接上代码啦~
public class AliyunApi { const string BaseUrl = "http://storage.aliyun.com"; readonly string _accountSid; readonly string _secretKey; public AliyunApi(string accountSid, string secretKey) { _accountSid = accountSid; _secretKey = secretKey; } //返回的XML或Josn对象直接反序列为实体对象 public T Execute<T>(RestRequest request) where T : new() { var client = new RestClient(); client.BaseUrl = BaseUrl; client.Authenticator = new AliyunAuthenticator(_accountSid, _secretKey); string date = DateTime.UtcNow.ToString("r"); request.AddHeader(Constants.DATE, date); var response = client.Execute<T>(request); if ((int)response.StatusCode / 100 != 2) { throw new OssException(response); } return response.Data; } //返回信息没有Body的对象
public IRestResponse Execute(RestRequest request) { var client = new RestClient(); client.BaseUrl = BaseUrl; client.Authenticator = new AliyunAuthenticator(_accountSid, _secretKey); string date = DateTime.UtcNow.ToString("r"); request.AddHeader(Constants.DATE, date); var response = client.Execute(request); if ((int)response.StatusCode / 100 != 2) { throw new OssException(response); } return response; } //列出所有的Bucket public ListAllMyBucketsResult ListAllMyBuckets() { var request = new RestRequest(); request.Resource = "/"; request.Method = Method.GET; //request.AddParameter("CallSid", callSid, ParameterType.UrlSegment); return Execute<ListAllMyBucketsResult>(request); } //创建一个Bucket
public void PutBucket(string bucket, string per) { if (!Utils.ValidateBucketName(bucket)) { throw new ArgumentException("Unsupported bucket name:" + bucket); } var request = new RestRequest(); request.Resource = "/" + bucket + "/"; request.Method = Method.PUT; request.AddHeader(Constants.ACL, per); var response = Execute(request); } }
上面定义了两个Execute方法,一个可以返回泛型结果T一个用于没有返回实体对象操作的。下面分别定义了ListAllMyBuckets来列出我所创建的所有Bucket,PutBucket用于创建一个新的Bucket。
2、错误处理
细心的朋友一定注意到了返回错误的处理方式:
if ((int)response.StatusCode / 100 != 2)
{
throw new OssException(response);
}
在RestSharp的HttpResponse 类型中定义了两个有关Http状态的字段分别为:
1:System.Net.HttpStatusCode枚举类型的StatusCode
2:RestSharp.ResponseStatus枚举类型的ResponseStatus
除网络连接失败,DNS请求失败等原因ResponseStatus均为 Completed。
HttpStatusCode才是用来描述web服务器发回来的状态,也就是我们常说的200、301、404、500等http头。
System.Net.HttpStatusCode 在定义中关于200的一共有一下7种
OK = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
也就是说200以内的都是成功的请求,因此单纯判断(response.StatusCode==HttpStatusCode.OK),可能出现意外。
3、自定义验证
自定义验证相对简单了,根据OSS文档要求就可以了代码如下:
public class AliyunAuthenticator : IAuthenticator { private const string DEFINE_PREFIX = "x-oss-"; private const string ALGORITHM = "HmacSHA1"; private readonly string accessKeyId; private readonly string secretAccessKey; public AliyunAuthenticator(string accessKeyId, string secretAccessKey) { this.secretAccessKey = secretAccessKey; this.accessKeyId = accessKeyId; } public void Authenticate(IRestClient client, IRestRequest request) { IEnumerable<Parameter> headers = request.Parameters.Where(m => m.Type == ParameterType.HttpHeader); IDictionary<string, string> dHeaders = new Dictionary<string, string>(); foreach (Parameter p in headers) { dHeaders.Add(p.Name, p.Value.ToString()); } if (!string.IsNullOrEmpty(secretAccessKey) && !string.IsNullOrEmpty(accessKeyId)) { string resource = request.Resource; if(request.Resource.IndexOf("client.BaseUrl")>0) resource = request.Resource.Remove(0, client.BaseUrl.Length); string authValue = "OSS " + this.accessKeyId + ":" + GetAssign(secretAccessKey, request.Method.ToString(), dHeaders, resource); request.AddHeader(Constants.AUTHORIZATION, authValue); } else if (!string.IsNullOrEmpty(accessKeyId)) { request.AddHeader(Constants.AUTHORIZATION, accessKeyId); } } private static string GetAssign(string secretAccessKey, string method, IDictionary<string, string> headers, string resource) { StringBuilder canonicalizedOssHeaders = new StringBuilder(); StringBuilder stringToSign = new StringBuilder(); byte[] byteHMAC = null; string contentMd5 = SafeGetElement(Constants.CONTENT_MD5, headers); string contentType = SafeGetElement(Constants.CONTENT_TYPE, headers); string date = SafeGetElement(Constants.DATE, headers); string canonicalizedResource = resource; SortedDictionary<string, string> tmpHeaders = formatHeader(headers); if (tmpHeaders.Count > 0) { foreach (string key in tmpHeaders.Keys) { if (key.ToLower().StartsWith(DEFINE_PREFIX)) { canonicalizedOssHeaders.Append(key).Append(":") .Append(tmpHeaders[key]).Append("\n"); } } } stringToSign.Append(method).Append("\n").Append(contentMd5) .Append("\n").Append(contentType).Append("\n").Append(date) .Append("\n").Append(canonicalizedOssHeaders) .Append(canonicalizedResource); try { Encoding encoding = ASCIIEncoding.GetEncoding(Constants.CHARSET); byte[] keyBytes = encoding.GetBytes(secretAccessKey); KeyedHashAlgorithm hmac = KeyedHashAlgorithm.Create(ALGORITHM); hmac.Key = keyBytes; byteHMAC = hmac.ComputeHash(encoding.GetBytes(stringToSign.ToString())); } catch (Exception e) { } return Convert.ToBase64String(byteHMAC); } private static SortedDictionary<string, string> formatHeader( IDictionary<string, string> headers) { SortedDictionary<string, string> tmpHeaders = new SortedDictionary<string, string>(); foreach (string key in headers.Keys) { if (key.ToLower().StartsWith(DEFINE_PREFIX)) { tmpHeaders[key.ToLower()] = headers[key]; } else { tmpHeaders[key] = headers[key]; } } return tmpHeaders; } private static string SafeGetElement(string key, IDictionary<string, string> map) { if (map == null || !map.ContainsKey(key)) { return ""; } return map[key]; } }
public class AliyunAuthenticator : IAuthenticator { private const string DEFINE_PREFIX = "x-oss-"; private const string ALGORITHM = "HmacSHA1"; private readonly string accessKeyId; private readonly string secretAccessKey; public AliyunAuthenticator(string accessKeyId, string secretAccessKey) { this.secretAccessKey = secretAccessKey; this.accessKeyId = accessKeyId; } public void Authenticate(IRestClient client, IRestRequest request) { IEnumerable<Parameter> headers = request.Parameters.Where(m => m.Type == ParameterType.HttpHeader); IDictionary<string, string> dHeaders = new Dictionary<string, string>(); foreach (Parameter p in headers) { dHeaders.Add(p.Name, p.Value.ToString()); } if (!string.IsNullOrEmpty(secretAccessKey) && !string.IsNullOrEmpty(accessKeyId)) { string resource = request.Resource; if(request.Resource.IndexOf("client.BaseUrl")>0) resource = request.Resource.Remove(0, client.BaseUrl.Length); string authValue = "OSS " + this.accessKeyId + ":" + GetAssign(secretAccessKey, request.Method.ToString(), dHeaders, resource); request.AddHeader(Constants.AUTHORIZATION, authValue); } else if (!string.IsNullOrEmpty(accessKeyId)) { request.AddHeader(Constants.AUTHORIZATION, accessKeyId); } } private static string GetAssign(string secretAccessKey, string method, IDictionary<string, string> headers, string resource) { StringBuilder canonicalizedOssHeaders = new StringBuilder(); StringBuilder stringToSign = new StringBuilder(); byte[] byteHMAC = null; string contentMd5 = SafeGetElement(Constants.CONTENT_MD5, headers); string contentType = SafeGetElement(Constants.CONTENT_TYPE, headers); string date = SafeGetElement(Constants.DATE, headers); string canonicalizedResource = resource; SortedDictionary<string, string> tmpHeaders = formatHeader(headers); if (tmpHeaders.Count > 0) { foreach (string key in tmpHeaders.Keys) { if (key.ToLower().StartsWith(DEFINE_PREFIX)) { canonicalizedOssHeaders.Append(key).Append(":") .Append(tmpHeaders[key]).Append("\n"); } } } stringToSign.Append(method).Append("\n").Append(contentMd5) .Append("\n").Append(contentType).Append("\n").Append(date) .Append("\n").Append(canonicalizedOssHeaders) .Append(canonicalizedResource); try { Encoding encoding = ASCIIEncoding.GetEncoding(Constants.CHARSET); byte[] keyBytes = encoding.GetBytes(secretAccessKey); KeyedHashAlgorithm hmac = KeyedHashAlgorithm.Create(ALGORITHM); hmac.Key = keyBytes; byteHMAC = hmac.ComputeHash(encoding.GetBytes(stringToSign.ToString())); } catch (Exception e) { } return Convert.ToBase64String(byteHMAC); } private static SortedDictionary<string, string> formatHeader( IDictionary<string, string> headers) { SortedDictionary<string, string> tmpHeaders = new SortedDictionary<string, string>(); foreach (string key in headers.Keys) { if (key.ToLower().StartsWith(DEFINE_PREFIX)) { tmpHeaders[key.ToLower()] = headers[key]; } else { tmpHeaders[key] = headers[key]; } } return tmpHeaders; } private static string SafeGetElement(string key, IDictionary<string, string> map) { if (map == null || !map.ContainsKey(key)) { return ""; } return map[key]; } }
上面只是把基本的结构分析了一下,看似简单,在应用过程中,遇到了很多麻烦。下一篇在分析使用过程中遇到的具体问题了,欢迎拍砖。