using System; using System.Collections.Generic; using System.Linq; using System.Text; /* Name: Socket 实现 http 协议全功能版 * Version: v0.6 * Description: 此版本实现了 http 及 https 的 get 或 post 访问, 自动处理301,302跳转, 支持 gzip 解压, 分块传输. * 支持的操作: 获取文本,图片,文件形式的内容. * 使用方法: new HttpWebSocket(); 调用实例的 Get.... 方法. */ using System.Net; using System.Net.Sockets; using System.Net.Security; using System.Text.RegularExpressions; using System.IO; using System.IO.Compression; using System.Web; using System.Security.Cryptography.X509Certificates; namespace CommonNetwork.Http { public class HttpWebSocket { /// /// 获取或设置请求与回应的超时时间,默认3秒. /// public int TimeOut { get; set; } /// /// 获取或设置请求cookie /// public List Cookies { get; set; } /// /// 获取请求返回的 HTTP 头部内容 /// public HttpHeader HttpHeaders { get; internal set; } /// /// 获取或设置错误信息分隔符 /// private string ErrorMessageSeparate; public HttpWebSocket() { this.TimeOut = 3; this.Cookies = new List(); this.ErrorMessageSeparate = ";;"; this.HttpHeaders = new HttpHeader(); } /// /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 返回图像 public static MemoryStream GetImageUseSocket(string url, string referer, string postData = null) { HttpWebSocket www = new HttpWebSocket(); //Image result = null; MemoryStream result = www.GetSocketResult(url, referer, postData); return result; } public static string GetTextUseSocket(string url) { HttpWebSocket www = new HttpWebSocket(); string txt = www.GetHtmlUseSocket(url, null, null); return txt; } /// /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 /// /// 请求绝对地址 /// post请求参数. 设置空值为get方式请求 /// 返回 html 内容,如果发生异常将返回上次http状态码及异常信息 public string GetHtmlUseSocket(string url, string postData = null) { return this.GetHtmlUseSocket(url, null, postData); } /// /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 返回 html 内容,如果发生异常将返回上次http状态码及异常信息 public string GetHtmlUseSocket(string url, string referer, string postData = null) { string result = string.Empty; try { MemoryStream ms = this.GetSocketResult(url, referer, postData); if (ms != null) { result = Encoding.GetEncoding(string.IsNullOrEmpty(this.HttpHeaders.Charset) ? "UTF-8" : this.HttpHeaders.Charset).GetString(ms.ToArray()); } } catch (SocketException se) { result = this.HttpHeaders.ResponseStatusCode + this.ErrorMessageSeparate + se.ErrorCode.ToString() + this.ErrorMessageSeparate + se.SocketErrorCode.ToString("G") + this.ErrorMessageSeparate + se.Message; } catch (Exception e) { result = this.HttpHeaders.ResponseStatusCode + this.ErrorMessageSeparate + e.Message; } return result; } /// /// get或post方式请求一个 http 或 https 地址. /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 返回的已解压的数据内容 private MemoryStream GetSocketResult(string url, string referer, string postData) { if (string.IsNullOrEmpty(url)) { throw new UriFormatException("'Url' cannot be empty."); } MemoryStream result = null; Uri uri = new Uri(url); if (uri.Scheme == "http") { result = this.GetHttpResult(uri, referer, postData); } else if (uri.Scheme == "https") { result = this.GetSslResult(uri, referer, postData); } else { throw new ArgumentException("url must start with HTTP or HTTPS.", "url"); } if (!string.IsNullOrEmpty(this.HttpHeaders.Location)) { result = GetSocketResult(this.HttpHeaders.Location, uri.AbsoluteUri, null); } else { result = unGzip(result); } return result; } /// /// get或post方式请求一个 http 地址. /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 输出包含头部内容的StringBuilder /// 返回未解压的数据流 private MemoryStream GetHttpResult(Uri uri, string referer, string postData) { MemoryStream result = new MemoryStream(10240); Socket HttpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); HttpSocket.SendTimeout = this.TimeOut * 1000; HttpSocket.ReceiveTimeout = this.TimeOut * 1000; try { byte[] send = GetSendHeaders(uri, referer, postData); HttpSocket.Connect(uri.Host, uri.Port); if (HttpSocket.Connected) { HttpSocket.Send(send, SocketFlags.None); this.ProcessData(HttpSocket, ref result); } result.Flush(); } finally { HttpSocket.Shutdown(SocketShutdown.Both); HttpSocket.Close(); } result.Seek(0, SeekOrigin.Begin); return result; } /// /// get或post方式请求一个 https 地址. /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 输出包含头部内容的StringBuilder /// 返回未解压的数据流 private MemoryStream GetSslResult(Uri uri, string referer, string postData) { MemoryStream result = new MemoryStream(10240); StringBuilder sb = new StringBuilder(1024); byte[] send = GetSendHeaders(uri, referer, postData); TcpClient client = new TcpClient(uri.Host, uri.Port); try { SslStream sslStream = new SslStream(client.GetStream(), true , new RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => { return sslPolicyErrors == SslPolicyErrors.None; } ), null); sslStream.ReadTimeout = this.TimeOut * 1000; sslStream.WriteTimeout = this.TimeOut * 1000; X509Store store = new X509Store(StoreName.My); sslStream.AuthenticateAsClient(uri.Host, store.Certificates, System.Security.Authentication.SslProtocols.Default, false); if (sslStream.IsAuthenticated) { sslStream.Write(send, 0, send.Length); sslStream.Flush(); this.ProcessData(sslStream, ref result); } result.Flush(); } finally { client.Close(); } result.Seek(0, SeekOrigin.Begin); return result; } /// /// 返回请求的头部内容 /// /// 请求绝对地址 /// 请求来源地址,可为空 /// post请求参数. 设置空值为get方式请求 /// 请求头部数据 private byte[] GetSendHeaders(Uri uri, string referer, string postData) { string sendString = @"{0} {1} HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: {2} Accept-Language: zh-CN User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) Accept-Encoding: gzip, deflate Host: {3} Connection: Keep-Alive Cache-Control: no-cache "; sendString = string.Format(sendString, string.IsNullOrEmpty(postData) ? "GET" : "POST", uri.PathAndQuery , string.IsNullOrEmpty(referer) ? uri.AbsoluteUri : referer, uri.Host); if (this.Cookies != null && this.Cookies.Count > 0) { sendString += string.Format("Cookie: {0}\r\n", string.Join("; ", this.Cookies.ToArray())); } if (string.IsNullOrEmpty(postData)) { sendString += "\r\n"; } else { int dlength = Encoding.UTF8.GetBytes(postData).Length; sendString += string.Format(@"Content-Type: application/x-www-form-urlencoded Content-Length: {0} {1} ", postData.Length, postData); } return Encoding.UTF8.GetBytes(sendString); ; } /// /// 设置此类的字段 /// /// 头部文本 private void SetThisHeaders(string headText) { if (string.IsNullOrEmpty(headText)) { throw new ArgumentNullException("'WithHeadersText' cannot be empty."); } //Match m = Regex.Match( withHeadersText,@".*(?=\r\n\r\n)", RegexOptions.Singleline | RegexOptions.IgnoreCase ); //if ( m == null || string.IsNullOrWhiteSpace( m.Value ) ) //{ // throw new HttpParseException( "'SetThisHeaders' method has bug." ); //} string[] headers = headText.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); if (headers == null || headers.Length == 0) { throw new ArgumentException("'WithHeadersText' param format error."); } this.HttpHeaders = new HttpHeader(); foreach (string head in headers) { if (head.StartsWith("HTTP", StringComparison.OrdinalIgnoreCase)) { string[] ts = head.Split(' '); if (ts.Length > 1) { this.HttpHeaders.ResponseStatusCode = ts[1]; } } else if (head.StartsWith("Set-Cookie:", StringComparison.OrdinalIgnoreCase)) { this.Cookies = this.Cookies ?? new List(); string tCookie = head.Substring(11, head.IndexOf(";") < 0 ? head.Length - 11 : head.IndexOf(";") - 10).Trim(); if (!this.Cookies.Exists(f => f.Split('=')[0] == tCookie.Split('=')[0])) { this.Cookies.Add(tCookie); } } else if (head.StartsWith("Location:", StringComparison.OrdinalIgnoreCase)) { this.HttpHeaders.Location = head.Substring(9).Trim(); } else if (head.StartsWith("Content-Encoding:", StringComparison.OrdinalIgnoreCase)) { if (head.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) >= 0) { this.HttpHeaders.IsGzip = true; } } else if (head.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) { string[] types = head.Substring(13).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string t in types) { if (t.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) >= 0) { this.HttpHeaders.Charset = t.Trim().Substring(8); } else if (t.IndexOf('/') >= 0) { this.HttpHeaders.ContentType = t.Trim(); } } } else if (head.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase)) { this.HttpHeaders.ContentLength = long.Parse(head.Substring(15).Trim()); } else if (head.StartsWith("Transfer-Encoding:", StringComparison.OrdinalIgnoreCase) && head.EndsWith("chunked", StringComparison.OrdinalIgnoreCase)) { this.HttpHeaders.IsChunk = true; } } } /// /// 解压数据流 /// /// 数据流, 压缩或未压缩的. /// 返回解压缩的数据流 private MemoryStream unGzip(MemoryStream data) { if (data == null) { throw new ArgumentNullException("data cannot be null.", "data"); } data.Seek(0, SeekOrigin.Begin); MemoryStream result = data; if (this.HttpHeaders.IsGzip) { GZipStream gs = new GZipStream(data, CompressionMode.Decompress); result = new MemoryStream(1024); try { byte[] buffer = new byte[1024]; int length = -1; do { length = gs.Read(buffer, 0, buffer.Length); result.Write(buffer, 0, length); } while (length != 0); gs.Flush(); result.Flush(); } finally { gs.Close(); } } return result; } /// /// 处理请求返回的数据. /// /// 数据源类型 /// 数据源实例 /// 保存数据的流 private void ProcessData(T reader, ref MemoryStream body) { byte[] data = new byte[10240]; int bodyStart = -1;//数据部分起始位置 int readLength = 0; bodyStart = GetHeaders(reader, ref data, ref readLength); if (bodyStart >= 0) { if (this.HttpHeaders.IsChunk) { GetChunkData(reader, ref data, ref bodyStart, ref readLength, ref body); } else { GetBodyData(reader, ref data, bodyStart, readLength, ref body); } } } /// /// 取得返回的http头部内容,并设置相关属性. /// /// 数据源类型 /// 数据源实例 /// 待处理的数据 /// 读取的长度 /// 数据内容的起始位置,返回-1表示未读完头部内容 private int GetHeaders(T reader, ref byte[] data, ref int readLength) { int result = -1; StringBuilder sb = new StringBuilder(1024); do { readLength = this.ReadData(reader, ref data); if (result < 0) { for (int i = 0; i < data.Length; i++) { char c = (char)data[i]; sb.Append(c); if (c == '\n' && string.Concat(sb[sb.Length - 4], sb[sb.Length - 3], sb[sb.Length - 2], sb[sb.Length - 1]).Contains("\r\n\r\n")) { result = i + 1; this.SetThisHeaders(sb.ToString()); break; } } } if (result >= 0) { break; } } while (readLength > 0); return result; } /// /// 取得未分块数据的内容 /// /// 数据源类型 /// 数据源实例 /// 已读取未处理的字节数据 /// 起始位置 /// 读取的长度 /// 保存块数据的流 private void GetBodyData(T reader, ref byte[] data, int startIndex, int readLength, ref MemoryStream body) { int contentTotal = 0; if (startIndex < data.Length) { int count = readLength - startIndex; body.Write(data, startIndex, count); contentTotal += count; } int tlength = 0; do { tlength = this.ReadData(reader, ref data); contentTotal += tlength; body.Write(data, 0, tlength); if (this.HttpHeaders.ContentLength > 0 && contentTotal >= this.HttpHeaders.ContentLength) { break; } } while (tlength > 0); } /// /// 取得分块数据 /// /// 数据源类型 /// Socket实例 /// 已读取未处理的字节数据 /// 起始位置 /// 读取的长度 /// 保存块数据的流 private void GetChunkData(T reader, ref byte[] data, ref int startIndex, ref int readLength, ref MemoryStream body) { int chunkSize = -1;//每个数据块的长度,用于分块数据.当长度为0时,说明读到数据末尾. while (true) { chunkSize = this.GetChunkHead(reader, ref data, ref startIndex, ref readLength); this.GetChunkBody(reader, ref data, ref startIndex, ref readLength, ref body, chunkSize); if (chunkSize <= 0) { break; } } } /// /// 取得分块数据的数据长度 /// /// 数据源类型 /// Socket实例 /// 已读取未处理的字节数据 /// 起始位置 /// 读取的长度 /// 块长度,返回0表示已到末尾. private int GetChunkHead(T reader, ref byte[] data, ref int startIndex, ref int readLength) { int chunkSize = -1; List tChars = new List();//用于临时存储块长度字符 if (startIndex >= data.Length || startIndex >= readLength) { readLength = this.ReadData(reader, ref data); startIndex = 0; } do { for (int i = startIndex; i < readLength; i++) { char c = (char)data[i]; if (c == '\n') { try { chunkSize = Convert.ToInt32(new string(tChars.ToArray()).TrimEnd('\r'), 16); startIndex = i + 1; } catch (Exception e) { throw new Exception("Maybe exists 'chunk-ext' field.", e); } break; } tChars.Add(c); } if (chunkSize >= 0) { break; } startIndex = 0; readLength = this.ReadData(reader, ref data); } while (readLength > 0); return chunkSize; } /// /// 取得分块传回的数据内容 /// /// 数据源类型 /// Socket实例 /// 已读取未处理的字节数据 /// 起始位置 /// 读取的长度 /// 保存块数据的流 /// 块长度 private void GetChunkBody(T reader, ref byte[] data, ref int startIndex, ref int readLength, ref MemoryStream body, int chunkSize) { if (chunkSize <= 0) { return; } int chunkReadLength = 0;//每个数据块已读取长度 if (startIndex >= data.Length || startIndex >= readLength) { readLength = this.ReadData(reader, ref data); startIndex = 0; } do { int owing = chunkSize - chunkReadLength; int count = Math.Min(readLength - startIndex, owing); body.Write(data, startIndex, count); chunkReadLength += count; if (owing <= count) { startIndex += count + 2; break; } startIndex = 0; readLength = this.ReadData(reader, ref data); } while (readLength > 0); } /// /// 从数据源读取数据 /// /// 数据源类型 /// 数据源 /// 用于存储读取的数据 /// 读取的数据长度,无数据为-1 private int ReadData(T reader, ref byte[] data) { int result = -1; if (reader is Socket) { result = (reader as Socket).Receive(data, SocketFlags.None); } else if (reader is SslStream) { result = (reader as SslStream).Read(data, 0, data.Length); } return result; } } public class HttpHeader { /// /// 获取请求回应状态码 /// public string ResponseStatusCode { get; internal set; } /// /// 获取跳转url /// public string Location { get; internal set; } /// /// 获取是否由Gzip压缩 /// public bool IsGzip { get; internal set; } /// /// 获取返回的文档类型 /// public string ContentType { get; internal set; } /// /// 获取内容使用的字符集 /// public string Charset { get; internal set; } /// /// 获取内容长度 /// public long ContentLength { get; internal set; } /// /// 获取是否分块传输 /// public bool IsChunk { get; internal set; } } }