关于sina api上传图片接口(upload) c#版

陈着 posted @ Nov 07, 2010 10:26:13 PM in Other , 8306 阅读

    之前我写了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包:

 

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#代码

 

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

 

将
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,不胜荣幸,希望能帮助更多的人。

Avatar_small
steven 说:
Jan 05, 2011 09:33:37 PM

你吧status这里的一段中的charset=US-ASCII改为charset=UTF-8貌似就能支持中文了。

Avatar_small
陈着 说:
Jan 06, 2011 05:20:16 AM

@steven: 貌似不改也是可以发中文的,当时写博客的时候灵异了。。。

Avatar_small
steven 说:
Jan 06, 2011 06:56:38 PM

你提供的方法没有添加地理坐标信息,我插入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());

Avatar_small
陈着 说:
Jan 14, 2011 04:37:18 AM

下个版本会解决这个问题。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter