一个C#的与web服务器交互的HttpClient类

openkk 12年前
     <pre class="brush:c#; toolbar: true; auto-links: false;">using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Net; using System.Web;  namespace Deerchao.Utility {     public class HttpClient     {         #region fields         private bool keepContext;         private string defaultLanguage = "zh-CN";         private Encoding defaultEncoding = Encoding.UTF8;         private string accept = "*/*";         private string userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";         private HttpVerb verb = HttpVerb.GET;         private HttpClientContext context;         private readonly List<HttpUploadingFile> files = new List<HttpUploadingFile>();         private readonly Dictionary<string, string> postingData = new Dictionary<string, string>();         private string url;         private WebHeaderCollection responseHeaders;         private int startPoint;         private int endPoint;         #endregion          #region events         public event EventHandler<StatusUpdateEventArgs> StatusUpdate;          private void OnStatusUpdate(StatusUpdateEventArgs e)         {             EventHandler<StatusUpdateEventArgs> temp = StatusUpdate;              if (temp != null)                 temp(this, e);         }         #endregion          #region properties         /// <summary>         /// 是否自动在不同的请求间保留Cookie, Referer         /// </summary>         public bool KeepContext         {             get { return keepContext; }             set { keepContext = value; }         }          /// <summary>         /// 期望的回应的语言         /// </summary>         public string DefaultLanguage         {             get { return defaultLanguage; }             set { defaultLanguage = value; }         }          /// <summary>         /// GetString()如果不能从HTTP头或Meta标签中获取编码信息,则使用此编码来获取字符串         /// </summary>         public Encoding DefaultEncoding         {             get { return defaultEncoding; }             set { defaultEncoding = value; }         }          /// <summary>         /// 指示发出Get请求还是Post请求         /// </summary>         public HttpVerb Verb         {             get { return verb; }             set { verb = value; }         }          /// <summary>         /// 要上传的文件.如果不为空则自动转为Post请求         /// </summary>         public List<HttpUploadingFile> Files         {             get { return files; }         }          /// <summary>         /// 要发送的Form表单信息         /// </summary>         public Dictionary<string, string> PostingData         {             get { return postingData; }         }          /// <summary>         /// 获取或设置请求资源的地址         /// </summary>         public string Url         {             get { return url; }             set { url = value; }         }          /// <summary>         /// 用于在获取回应后,暂时记录回应的HTTP头         /// </summary>         public WebHeaderCollection ResponseHeaders         {             get { return responseHeaders; }         }          /// <summary>         /// 获取或设置期望的资源类型         /// </summary>         public string Accept         {             get { return accept; }             set { accept = value; }         }          /// <summary>         /// 获取或设置请求中的Http头User-Agent的值         /// </summary>         public string UserAgent         {             get { return userAgent; }             set { userAgent = value; }         }          /// <summary>         /// 获取或设置Cookie及Referer         /// </summary>         public HttpClientContext Context         {             get { return context; }             set { context = value; }         }          /// <summary>         /// 获取或设置获取内容的起始点,用于断点续传,多线程下载等         /// </summary>         public int StartPoint         {             get { return startPoint; }             set { startPoint = value; }         }          /// <summary>         /// 获取或设置获取内容的结束点,用于断点续传,多下程下载等.         /// 如果为0,表示获取资源从StartPoint开始的剩余内容         /// </summary>         public int EndPoint         {             get { return endPoint; }             set { endPoint = value; }         }          #endregion          #region constructors         /// <summary>         /// 构造新的HttpClient实例         /// </summary>         public HttpClient()             : this(null)         {         }          /// <summary>         /// 构造新的HttpClient实例         /// </summary>         /// <param name="url">要获取的资源的地址</param>         public HttpClient(string url)             : this(url, null)         {         }          /// <summary>         /// 构造新的HttpClient实例         /// </summary>         /// <param name="url">要获取的资源的地址</param>         /// <param name="context">Cookie及Referer</param>         public HttpClient(string url, HttpClientContext context)             : this(url, context, false)         {         }          /// <summary>         /// 构造新的HttpClient实例         /// </summary>         /// <param name="url">要获取的资源的地址</param>         /// <param name="context">Cookie及Referer</param>         /// <param name="keepContext">是否自动在不同的请求间保留Cookie, Referer</param>         public HttpClient(string url, HttpClientContext context, bool keepContext)         {             this.url = url;             this.context = context;             this.keepContext = keepContext;             if (this.context == null)                 this.context = new HttpClientContext();         }         #endregion          #region AttachFile         /// <summary>         /// 在请求中添加要上传的文件         /// </summary>         /// <param name="fileName">要上传的文件路径</param>         /// <param name="fieldName">文件字段的名称(相当于&lt;input type=file name=fieldName&gt;)里的fieldName)</param>         public void AttachFile(string fileName, string fieldName)         {             HttpUploadingFile file = new HttpUploadingFile(fileName, fieldName);             files.Add(file);         }          /// <summary>         /// 在请求中添加要上传的文件         /// </summary>         /// <param name="data">要上传的文件内容</param>         /// <param name="fileName">文件名</param>         /// <param name="fieldName">文件字段的名称(相当于&lt;input type=file name=fieldName&gt;)里的fieldName)</param>         public void AttachFile(byte[] data, string fileName, string fieldName)         {             HttpUploadingFile file = new HttpUploadingFile(data, fileName, fieldName);             files.Add(file);         }         #endregion          /// <summary>         /// 清空PostingData, Files, StartPoint, EndPoint, ResponseHeaders, 并把Verb设置为Get.         /// 在发出一个包含上述信息的请求后,必须调用此方法或手工设置相应属性以使下一次请求不会受到影响.         /// </summary>         public void Reset()         {             verb = HttpVerb.GET;             files.Clear();             postingData.Clear();             responseHeaders = null;             startPoint = 0;             endPoint = 0;         }          private HttpWebRequest CreateRequest()         {             HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);             req.AllowAutoRedirect = false;             req.CookieContainer = new CookieContainer();             req.Headers.Add("Accept-Language", defaultLanguage);             req.Accept = accept;             req.UserAgent = userAgent;             req.KeepAlive = false;              if (context.Cookies != null)                 req.CookieContainer.Add(context.Cookies);             if (!string.IsNullOrEmpty(context.Referer))                 req.Referer = context.Referer;              if (verb == HttpVerb.HEAD)             {                 req.Method = "HEAD";                 return req;             }              if (postingData.Count > 0 || files.Count > 0)                 verb = HttpVerb.POST;              if (verb == HttpVerb.POST)             {                 req.Method = "POST";                  MemoryStream memoryStream = new MemoryStream();                 StreamWriter writer = new StreamWriter(memoryStream);                  if (files.Count > 0)                 {                     string newLine = "\r\n";                     string boundary = Guid.NewGuid().ToString().Replace("-", "");                     req.ContentType = "multipart/form-data; boundary=" + boundary;                      foreach (string key in postingData.Keys)                     {                         writer.Write("--" + boundary + newLine);                         writer.Write("Content-Disposition: form-data; name=\"{0}\"{1}{1}", key, newLine);                         writer.Write(postingData[key] + newLine);                     }                      foreach (HttpUploadingFile file in files)                     {                         writer.Write("--" + boundary + newLine);                         writer.Write(                             "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}",                             file.FieldName,                             file.FileName,                             newLine                             );                         writer.Write("Content-Type: application/octet-stream" + newLine + newLine);                         writer.Flush();                         memoryStream.Write(file.Data, 0, file.Data.Length);                         writer.Write(newLine);                         writer.Write("--" + boundary + newLine);                     }                 }                 else                 {                     req.ContentType = "application/x-www-form-urlencoded";                     StringBuilder sb = new StringBuilder();                     foreach (string key in postingData.Keys)                     {                         sb.AppendFormat("{0}={1}&", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(postingData[key]));                     }                     if (sb.Length > 0)                         sb.Length--;                     writer.Write(sb.ToString());                 }                  writer.Flush();                  using (Stream stream = req.GetRequestStream())                 {                     memoryStream.WriteTo(stream);                 }             }              if (startPoint != 0 && endPoint != 0)                 req.AddRange(startPoint, endPoint);             else if (startPoint != 0 && endPoint == 0)                 req.AddRange(startPoint);              return req;         }          /// <summary>         /// 发出一次新的请求,并返回获得的回应         /// 调用此方法永远不会触发StatusUpdate事件.         /// </summary>         /// <returns>相应的HttpWebResponse</returns>         public HttpWebResponse GetResponse()         {             HttpWebRequest req = CreateRequest();             HttpWebResponse res = (HttpWebResponse)req.GetResponse();             responseHeaders = res.Headers;             if (keepContext)             {                 context.Cookies = res.Cookies;                 context.Referer = url;             }             return res;         }          /// <summary>         /// 发出一次新的请求,并返回回应内容的流         /// 调用此方法永远不会触发StatusUpdate事件.         /// </summary>         /// <returns>包含回应主体内容的流</returns>         public Stream GetStream()         {             return GetResponse().GetResponseStream();         }          /// <summary>         /// 发出一次新的请求,并以字节数组形式返回回应的内容         /// 调用此方法会触发StatusUpdate事件         /// </summary>         /// <returns>包含回应主体内容的字节数组</returns>         public byte[] GetBytes()         {             HttpWebResponse res = GetResponse();             int length = (int)res.ContentLength;              MemoryStream memoryStream = new MemoryStream();             byte[] buffer = new byte[0x100];             Stream rs = res.GetResponseStream();             for (int i = rs.Read(buffer, 0, buffer.Length); i > 0; i = rs.Read(buffer, 0, buffer.Length))             {                 memoryStream.Write(buffer, 0, i);                 OnStatusUpdate(new StatusUpdateEventArgs((int)memoryStream.Length, length));             }             rs.Close();              return memoryStream.ToArray();         }          /// <summary>         /// 发出一次新的请求,以Http头,或Html Meta标签,或DefaultEncoding指示的编码信息对回应主体解码         /// 调用此方法会触发StatusUpdate事件         /// </summary>         /// <returns>解码后的字符串</returns>         public string GetString()         {             byte[] data = GetBytes();             string encodingName = GetEncodingFromHeaders();              if (encodingName == null)                 encodingName = GetEncodingFromBody(data);              Encoding encoding;             if (encodingName == null)                 encoding = defaultEncoding;             else             {                 try                 {                     encoding = Encoding.GetEncoding(encodingName);                 }                 catch (ArgumentException)                 {                     encoding = defaultEncoding;                 }             }             return encoding.GetString(data);         }          /// <summary>         /// 发出一次新的请求,对回应的主体内容以指定的编码进行解码         /// 调用此方法会触发StatusUpdate事件         /// </summary>         /// <param name="encoding">指定的编码</param>         /// <returns>解码后的字符串</returns>         public string GetString(Encoding encoding)         {             byte[] data = GetBytes();             return encoding.GetString(data);         }          private string GetEncodingFromHeaders()         {             string encoding = null;             string contentType = responseHeaders["Content-Type"];             if (contentType != null)             {                 int i = contentType.IndexOf("charset=");                 if (i != -1)                 {                     encoding = contentType.Substring(i + 8);                 }             }             return encoding;         }          private string GetEncodingFromBody(byte[] data)         {             string encodingName = null;             string dataAsAscii = Encoding.ASCII.GetString(data);             if (dataAsAscii != null)             {                 int i = dataAsAscii.IndexOf("charset=");                 if (i != -1)                 {                     int j = dataAsAscii.IndexOf("\"", i);                     if (j != -1)                     {                         int k = i + 8;                         encodingName = dataAsAscii.Substring(k, (j - k) + 1);                         char[] chArray = new char[2] { '>', '"' };                         encodingName = encodingName.TrimEnd(chArray);                     }                 }             }             return encodingName;         }          /// <summary>         /// 发出一次新的Head请求,获取资源的长度         /// 此请求会忽略PostingData, Files, StartPoint, EndPoint, Verb         /// </summary>         /// <returns>返回的资源长度</returns>         public int HeadContentLength()         {             Reset();             HttpVerb lastVerb = verb;             verb = HttpVerb.HEAD;             using (HttpWebResponse res = GetResponse())             {                 verb = lastVerb;                 return (int)res.ContentLength;             }         }          /// <summary>         /// 发出一次新的请求,把回应的主体内容保存到文件         /// 调用此方法会触发StatusUpdate事件         /// 如果指定的文件存在,它会被覆盖         /// </summary>         /// <param name="fileName">要保存的文件路径</param>         public void SaveAsFile(string fileName)         {             SaveAsFile(fileName, FileExistsAction.Overwrite);         }          /// <summary>         /// 发出一次新的请求,把回应的主体内容保存到文件         /// 调用此方法会触发StatusUpdate事件         /// </summary>         /// <param name="fileName">要保存的文件路径</param>         /// <param name="existsAction">指定的文件存在时的选项</param>         /// <returns>是否向目标文件写入了数据</returns>         public bool SaveAsFile(string fileName, FileExistsAction existsAction)         {             byte[] data = GetBytes();             switch (existsAction)             {                 case FileExistsAction.Overwrite:                     using (BinaryWriter writer = new BinaryWriter(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write)))                         writer.Write(data);                     return true;                  case FileExistsAction.Append:                     using (BinaryWriter writer = new BinaryWriter(new FileStream(fileName, FileMode.Append, FileAccess.Write)))                         writer.Write(data);                     return true;                  default:                     if (!File.Exists(fileName))                     {                         using (                             BinaryWriter writer =                                 new BinaryWriter(new FileStream(fileName, FileMode.Create, FileAccess.Write)))                             writer.Write(data);                         return true;                     }                     else                     {                         return false;                     }             }         }     }      public class HttpClientContext     {         private CookieCollection cookies;         private string referer;          public CookieCollection Cookies         {             get { return cookies; }             set { cookies = value; }         }          public string Referer         {             get { return referer; }             set { referer = value; }         }     }      public enum HttpVerb     {         GET,         POST,         HEAD,     }      public enum FileExistsAction     {         Overwrite,         Append,         Cancel,     }      public class HttpUploadingFile     {         private string fileName;         private string fieldName;         private byte[] data;          public string FileName         {             get { return fileName; }             set { fileName = value; }         }          public string FieldName         {             get { return fieldName; }             set { fieldName = value; }         }          public byte[] Data         {             get { return data; }             set { data = value; }         }          public HttpUploadingFile(string fileName, string fieldName)         {             this.fileName = fileName;             this.fieldName = fieldName;             using (FileStream stream = new FileStream(fileName, FileMode.Open))             {                 byte[] inBytes = new byte[stream.Length];                 stream.Read(inBytes, 0, inBytes.Length);                 data = inBytes;             }         }          public HttpUploadingFile(byte[] data, string fileName, string fieldName)         {             this.data = data;             this.fileName = fileName;             this.fieldName = fieldName;         }     }      public class StatusUpdateEventArgs : EventArgs     {         private readonly int bytesGot;         private readonly int bytesTotal;          public StatusUpdateEventArgs(int got, int total)         {             bytesGot = got;             bytesTotal = total;         }          /// <summary>         /// 已经下载的字节数         /// </summary>         public int BytesGot         {             get { return bytesGot; }         }          /// <summary>         /// 资源的总字节数         /// </summary>         public int BytesTotal         {             get { return bytesTotal; }         }     } }</pre>    <br />    <p><span style="font-family:Verdana;">.Net类库里提供了HttpWebRequest等类,方便我们编程与Web服务器进行交互. 但是实际使用中我们经常会遇到以下需求,      <strike>       基础类里没有直接提供相应的功能      </strike>(<strong><a href="/misc/goto?guid=4959499320429973815" rel="nofollow">WebClient</a>类包含这些功能,只是用起来稍微麻烦一点--谢谢网友东吴居士的提醒</strong>): </span></p>    <span style="font-family:Verdana;">     <ul>      <li>对HttpWebResponse获取的HTML进行文字编码转换,使之不会出现乱码;</li>      <li>自动在Session间保持Cookie,Referer等相关信息;</li>      <li>模拟HTML表单提交;</li>      <li>向服务器上传文件;</li>      <li>对二进制的资源,直接获取返回的字节数组(byte[]),或者保存为文件</li>     </ul> <p>为了解决这些问题,我开发了HttpClient类.下面是使用的方法:</p>     <ul>      <li>获取编码转换后的字符串<br /> <br /> HttpClient client=new HttpClient(url);<br /> string html=client.GetString();<br /> <br /> GetString()函数内部会查找Http Headers, 以及HTML的Meta标签,试图找出获取的内容的编码信息.如果都找不到,它会使用client.DefaultEncoding, 这个属性默认为utf-8, 也可以手动设置.<br /> </li>      <li>自动保持Cookie, Referer<br /> <br /> HttpClient client=new HttpClient(url1, null, true);<br /> string html1=client.GetString();<br /> client.Url=url2;<br /> string html2=client.GetString();<br /> <br /> 这里HttpClient的第三个参数,keepContext设置为真时,HttpClient会自动记录每次交互时服务器对Cookies进行的操作,同时会以前一次请求的Url为Referer.在这个例子里,获取html2时,会把url1作为Referer, 同时会向服务器传递在获取html1时服务器设置的Cookies. 当然,你也可以在构造HttpClient时直接提供第一次请求要发出的Cookies与Referer:<br /> <br /> HttpClient client=new HttpClient(url, new WebContext(cookies, referer), true);<br /> <br /> 或者,在使用过程中随时修改这些信息:<br /> <br /> client.Context.Cookies=cookies;<br /> client.Context.referer=referer;<br /> </li>      <li>模拟HTML表单提交<br /> <br /> HttpClient client=new HttpClient(url);<br /> client.PostingData.Add(fieldName1, filedValue1);<br /> client.PostingData.Add(fieldName2, fieldValue2);<br /> string html=client.GetString();<br /> <br /> 上面的代码相当于提交了一个有两个input的表单. 在PostingData非空,或者附加了要上传的文件时(请看下面的上传和文件), HttpClient会自动把HttpVerb改成POST, 并将相应的信息附加到Request上.<br /> </li>      <li>向服务器上传文件<br /> <br /> HttpClient client=new HttpClient(url);<br /> client.AttachFile(fileName, fieldName);<br /> client.AttachFile(byteArray, fileName, fieldName);<br /> string html=client.GetString();<br /> <br /> 这里面的fieldName相当于<input type="file" name="fieldName" />里的fieldName. fileName当然就是你想要上传的文件路径了. 你也可以直接提供一个byte[] 作为文件内容, 但即使如此,你也必须提供一个文件名,以满足HTTP规范的要求.<br /> </li>      <li>不同的返回形式<br /> <br /> 字符串: string html = client.GetString();<br /> 流: Stream stream = client.GetStream();<br /> 字节数组: byte[] data = client.GetBytes();<br /> 保存到文件:  client.SaveAsFile(fileName);<br /> 或者,你也可以直接操作HttpWebResponse: HttpWebResponse res = client.GetResponse();<br /> <br /> 每调用一次上述任何一个方法,都会导致发出一个HTTP Request, 也就是说,你不能同时得到某个Response的两种返回形式.<br /> 另外,调用后它们任意一个之后,你可以通过client.ResponseHeaders来获取服务器返回的HTTP头.</li>      <li>下载资源的指定部分(用于断点续传,多线程下载)<br /> <br /> HttpClient client=new HttpClient(url);<br /> //发出HEAD请求,获取资源长度<br /> int length=client.HeadContentLength();<br /> <br /> //只获取后一半内容<br /> client.StartPoint=length/2;<br /> byte[] data=client.GetBytes();<br /> <br /> HeadContentLength()只会发出HTTP HEAD请求.根据HTTP协议, HEAD与GET的作用等同, 但是,只返回HTTP头,而不返回资源主体内容. 也就是说,用这个方法,你没法获取一个需要通过POST才能得到的资源的长度,如果你确实有这样的需求,建议你可以通过GetResponse(),然后从ResponseHeader里获取Content-Length.</li>     </ul> <p>计划中还有另外一些功能要加进来,比如断点续传, 多线程下载, 下载进度更新的事件机制等, 正在思考如何与现在的代码融合到一起,期待你的反馈.</p> <p>注意:<strong>使用时应该添加对System.Web.dll的引用,并在使用此类的代码前添加"using System.Web;",不然会无法通过编译.</strong></p> </span>