一個C#的與web服務器交互的HttpClient類
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">文件字段的名稱(相當于<input type=file name=fieldName>)里的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">文件字段的名稱(相當于<input type=file name=fieldName>)里的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>
.Net類庫里提供了HttpWebRequest等類,方便我們編程與Web服務器進行交互. 但是實際使用中我們經常會遇到以下需求,
基礎類里沒有直接提供相應的功能(WebClient類包含這些功能,只是用起來稍微麻煩一點--謝謝網友東吳居士的提醒):
- 對HttpWebResponse獲取的HTML進行文字編碼轉換,使之不會出現亂碼;
- 自動在Session間保持Cookie,Referer等相關信息;
- 模擬HTML表單提交;
- 向服務器上傳文件;
- 對二進制的資源,直接獲取返回的字節數組(byte[]),或者保存為文件
為了解決這些問題,我開發了HttpClient類.下面是使用的方法:
- 獲取編碼轉換后的字符串
HttpClient client=new HttpClient(url);
string html=client.GetString();
GetString()函數內部會查找Http Headers, 以及HTML的Meta標簽,試圖找出獲取的內容的編碼信息.如果都找不到,它會使用client.DefaultEncoding, 這個屬性默認為utf-8, 也可以手動設置.
- 自動保持Cookie, Referer
HttpClient client=new HttpClient(url1, null, true);
string html1=client.GetString();
client.Url=url2;
string html2=client.GetString();
這里HttpClient的第三個參數,keepContext設置為真時,HttpClient會自動記錄每次交互時服務器對Cookies進行的操作,同時會以前一次請求的Url為Referer.在這個例子里,獲取html2時,會把url1作為Referer, 同時會向服務器傳遞在獲取html1時服務器設置的Cookies. 當然,你也可以在構造HttpClient時直接提供第一次請求要發出的Cookies與Referer:
HttpClient client=new HttpClient(url, new WebContext(cookies, referer), true);
或者,在使用過程中隨時修改這些信息:
client.Context.Cookies=cookies;
client.Context.referer=referer;
- 模擬HTML表單提交
HttpClient client=new HttpClient(url);
client.PostingData.Add(fieldName1, filedValue1);
client.PostingData.Add(fieldName2, fieldValue2);
string html=client.GetString();
上面的代碼相當于提交了一個有兩個input的表單. 在PostingData非空,或者附加了要上傳的文件時(請看下面的上傳和文件), HttpClient會自動把HttpVerb改成POST, 并將相應的信息附加到Request上.
- 向服務器上傳文件
HttpClient client=new HttpClient(url);
client.AttachFile(fileName, fieldName);
client.AttachFile(byteArray, fileName, fieldName);
string html=client.GetString();
這里面的fieldName相當于<input type="file" name="fieldName" />里的fieldName. fileName當然就是你想要上傳的文件路徑了. 你也可以直接提供一個byte[] 作為文件內容, 但即使如此,你也必須提供一個文件名,以滿足HTTP規范的要求.
- 不同的返回形式
字符串: string html = client.GetString();
流: Stream stream = client.GetStream();
字節數組: byte[] data = client.GetBytes();
保存到文件: client.SaveAsFile(fileName);
或者,你也可以直接操作HttpWebResponse: HttpWebResponse res = client.GetResponse();
每調用一次上述任何一個方法,都會導致發出一個HTTP Request, 也就是說,你不能同時得到某個Response的兩種返回形式.
另外,調用后它們任意一個之后,你可以通過client.ResponseHeaders來獲取服務器返回的HTTP頭.- 下載資源的指定部分(用于斷點續傳,多線程下載)
HttpClient client=new HttpClient(url);
//發出HEAD請求,獲取資源長度
int length=client.HeadContentLength();
//只獲取后一半內容
client.StartPoint=length/2;
byte[] data=client.GetBytes();
HeadContentLength()只會發出HTTP HEAD請求.根據HTTP協議, HEAD與GET的作用等同, 但是,只返回HTTP頭,而不返回資源主體內容. 也就是說,用這個方法,你沒法獲取一個需要通過POST才能得到的資源的長度,如果你確實有這樣的需求,建議你可以通過GetResponse(),然后從ResponseHeader里獲取Content-Length.計劃中還有另外一些功能要加進來,比如斷點續傳, 多線程下載, 下載進度更新的事件機制等, 正在思考如何與現在的代碼融合到一起,期待你的反饋.
注意:使用時應該添加對System.Web.dll的引用,并在使用此類的代碼前添加"using System.Web;",不然會無法通過編譯.
本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!