关于sina api上传图片接口(upload) c#版
之前我写了sinaapi c#的sdk:opensinaapi,没有实现上传图片的接口。因为要忙实验室和其他项目,一直没有去研究。期间很多人询问过我有关上传图片接口的问题,最近刚好有些时间,花了一个晚上实现出来,需要的同学可以去http://code.google.com/p/opensinaapi/downloads/list下载。
在研究的过程中我发现相关的资料非常少,基本上不能找到有用的信息。相对来说,twitter api的upload借口实现有其借鉴意义,但是其并不适用在sina api上,如果有人去研究的话应该会发现大多会返回401错误(未能授权)。原因是:sina api和twitter api中post的参数不一样,sina api中多了一个参数:source,少了oauth_verify。POST的参数和Update的接口一样,需要将status加入签名。
我在研究过程中主要是抓取upload的POST包,对包结构进行分析,从而用c#构造POST包(抓包工具HttpAnalyzer)。以下为一个真实的POST包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | POST /statuses/upload.json HTTP/ 1.1 Authorization: OAuth oauth_consumer_key= "3587646579" , oauth_signature_method= "HMAC-SHA1" ,oauth_timestamp= "1289113593" , oauth_nonce= "7472747" , oauth_version= "1.0" , oauth_token= "9f273ceccf51e528b302a3a47053badd" ,oauth_signature= "O5b4XTaazF6T50YqSsyLM6RtRsk%3d" User-Agent: Jakarta Commons-HttpClient/ 3.1 Content-Type: multipart/form-data; boundary=bb9f588b-0eaf-4d95-aaec-b10f057bccd7 Host: api.t.sina.com.cn Content-Length: 26509 --bb9f588b-0eaf-4d95-aaec-b10f057bccd7 Content-Disposition: form-data; name= "status" Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 8bit %e6% 88 % 91 %e6% 98 %af%e9% 99 % 88 %e7%9d% 80 --bb9f588b-0eaf-4d95-aaec-b10f057bccd7 Content-Disposition: form-data; name= "source" Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 8bit 3587646579 --bb9f588b-0eaf-4d95-aaec-b10f057bccd7 Content-Disposition: form-data; name= "pic" ; filename= "sample_image.png" Content-Type: application/octet-stream; charset=UTF- 8 Content-Transfer-Encoding: binary XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --bb9f588b-0eaf-4d95-aaec-b10f057bccd7
|
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX为二进制文件串
以下是构造POST包的c#代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | public string oAuthWebRequestWithPic(Method method, string url, string postData, string filepath, string picName) { var UploadApiUrl = url; string status = postData.Split( '=' ).GetValue(1).ToString(); postData += "&source=" + _consumerKey; //多了一个source参数 if (postData.Length > 0) { NameValueCollection qs = HttpUtility.ParseQueryString(postData); postData = "" ; foreach ( string key in qs.AllKeys) { if (postData.Length > 0) { postData += "&" ; } qs[key] = HttpUtility.UrlEncode(qs[key]); qs[key] = this .UrlEncode(qs[key]); postData += (key + "=" + qs[key]); } if (url.IndexOf( "?" ) > 0) { url += "&" ; } else { url += "?" ; } url += postData; } var oauthSignaturePattern = "OAuth oauth_consumer_key=\"{0}\", oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"{1}\", oauth_nonce=\"{2}\", oauth_version=\"1.0\", oauth_token=\"{3}\",oauth_signature=\"{4}\"" ; var contentEncoding = "iso-8859-1" ; string normalizedString, normalizedParameters; var timestamp = this .GenerateTimeStamp(); var nounce = this .GenerateNonce(); var signature = this .GenerateSignature( new Uri(url), this .ConsumerKey, this .ConsumerSecret, this .Token, this .TokenSecret, method.ToString(), timestamp, nounce, out normalizedString, out normalizedParameters); signature = HttpUtility.UrlEncode(signature); var boundary = Guid.NewGuid().ToString(); var request = (HttpWebRequest)System.Net.WebRequest.Create(UploadApiUrl); request.PreAuthenticate = true ; request.AllowWriteStreamBuffering = true ; request.Method = method.ToString(); request.UserAgent = "Jakarta Commons-HttpClient/3.1" ; var authorizationHeader = string .Format( CultureInfo.InvariantCulture, oauthSignaturePattern, this .ConsumerKey, timestamp, nounce, this .Token, signature); request.Headers.Add( "Authorization" , authorizationHeader); var header = string .Format( "--{0}" , boundary); var footer = string .Format( "--{0}--" , boundary); var contents = new StringBuilder(); request.ContentType = string .Format( "multipart/form-data; boundary={0}" , boundary); contents.AppendLine(header); contents.AppendLine(String.Format( "Content-Disposition: form-data; name=\"{0}\"" , "status" )); contents.AppendLine( "Content-Type: text/plain; charset=US-ASCII" ); contents.AppendLine( "Content-Transfer-Encoding: 8bit" ); contents.AppendLine(); contents.AppendLine(status); contents.AppendLine(header); contents.AppendLine( string .Format( "Content-Disposition: form-data; name=\"{0}\"" , "source" )); contents.AppendLine( "Content-Type: text/plain; charset=US-ASCII" ); contents.AppendLine( "Content-Transfer-Encoding: 8bit" ); contents.AppendLine(); contents.AppendLine( this .ConsumerKey); contents.AppendLine(header); string fileHeader = string .Format( "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" , "pic" , picName); string fileData = System.Text.Encoding.GetEncoding(contentEncoding).GetString(File.ReadAllBytes(@filepath)); contents.AppendLine(fileHeader); contents.AppendLine( "Content-Type: application/octet-stream; charset=UTF-8" ); contents.AppendLine( "Content-Transfer-Encoding: binary" ); contents.AppendLine(); contents.AppendLine(fileData); contents.AppendLine(footer); byte [] bytes = Encoding.GetEncoding(contentEncoding).GetBytes(contents.ToString()); request.ContentLength = bytes.Length; var requestStream = request.GetRequestStream(); try { requestStream.Write(bytes, 0, bytes.Length); } catch { throw ; } finally { requestStream.Close(); requestStream = null ; } return WebResponseGet(request); } |
其次,需要修改oAuthBase.cs,这是因为签名参数的问题:多了source少了oauth_verify
1 2 3 4 5 6 | 将 public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters)中 if (! string .IsNullOrEmpty(oauth_verifier)) 改为 if (! string .IsNullOrEmpty(oauth_verifier) && httpMethod == "GET" ) |
有个比较奇怪的问题,sina api的update接口可以不需要将oauth_verify换为source,而upload接口则必须将oauth_verify换为source。
最后,本版本的sdk中,upload的status必须为英文,暂时还没解决中文问题。
注:由于本人时间有限,而sdk还相当不完善,如有同学想加入opensinaapi,请联系sarlmolapple@gmail.com,不胜荣幸,希望能帮助更多的人。
14 年前
你吧status这里的一段中的charset=US-ASCII改为charset=UTF-8貌似就能支持中文了。
14 年前
@steven: 貌似不改也是可以发中文的,当时写博客的时候灵异了。。。
14 年前
你提供的方法没有添加地理坐标信息,我插入2段后,竟然提示401错误,不知道错在那里
//contents.AppendLine(header);
//contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "lat"));
//contents.AppendLine("Content-Type: text/plain; charset=UTF-8");
//contents.AppendLine("Content-Transfer-Encoding: 8bit");
//contents.AppendLine();
//contents.AppendLine(lat.ToString());
//contents.AppendLine(header);
//contents.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", "long"));
//contents.AppendLine("Content-Type: text/plain; charset=UTF-8");
//contents.AppendLine("Content-Transfer-Encoding: 8bit");
//contents.AppendLine();
//contents.AppendLine(longitude.ToString());
14 年前
下个版本会解决这个问题。