123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765 |
- 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
- {
- /// <summary>
- /// 获取或设置请求与回应的超时时间,默认3秒.
- /// </summary>
- public int TimeOut
- {
- get;
- set;
- }
- /// <summary>
- /// 获取或设置请求cookie
- /// </summary>
- public List<string> Cookies
- {
- get;
- set;
- }
- /// <summary>
- /// 获取请求返回的 HTTP 头部内容
- /// </summary>
- public HttpHeader HttpHeaders
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取或设置错误信息分隔符
- /// </summary>
- private string ErrorMessageSeparate;
- public HttpWebSocket()
- {
- this.TimeOut = 3;
- this.Cookies = new List<string>();
- this.ErrorMessageSeparate = ";;";
- this.HttpHeaders = new HttpHeader();
- }
- /// <summary>
- /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式
- /// </summary>
- /// <param name="url">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <returns>返回图像</returns>
- 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;
- }
- /// <summary>
- /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式
- /// </summary>
- /// <param name="url">请求绝对地址</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <returns>返回 html 内容,如果发生异常将返回上次http状态码及异常信息</returns>
- public string GetHtmlUseSocket(string url, string postData = null)
- {
- return this.GetHtmlUseSocket(url, null, postData);
- }
- /// <summary>
- /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式
- /// </summary>
- /// <param name="url">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <returns>返回 html 内容,如果发生异常将返回上次http状态码及异常信息</returns>
- 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;
- }
- /// <summary>
- /// get或post方式请求一个 http 或 https 地址.
- /// </summary>
- /// <param name="url">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <returns>返回的已解压的数据内容</returns>
- 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;
- }
- /// <summary>
- /// get或post方式请求一个 http 地址.
- /// </summary>
- /// <param name="uri">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <param name="headText">输出包含头部内容的StringBuilder</param>
- /// <returns>返回未解压的数据流</returns>
- 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;
- }
- /// <summary>
- /// get或post方式请求一个 https 地址.
- /// </summary>
- /// <param name="uri">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <param name="headText">输出包含头部内容的StringBuilder</param>
- /// <returns>返回未解压的数据流</returns>
- 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;
- }
- /// <summary>
- /// 返回请求的头部内容
- /// </summary>
- /// <param name="uri">请求绝对地址</param>
- /// <param name="referer">请求来源地址,可为空</param>
- /// <param name="postData">post请求参数. 设置空值为get方式请求</param>
- /// <returns>请求头部数据</returns>
- 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);
- ;
- }
- /// <summary>
- /// 设置此类的字段
- /// </summary>
- /// <param name="headText">头部文本</param>
- 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>();
- 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;
- }
- }
- }
- /// <summary>
- /// 解压数据流
- /// </summary>
- /// <param name="data">数据流, 压缩或未压缩的.</param>
- /// <returns>返回解压缩的数据流</returns>
- 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;
- }
- /// <summary>
- /// 处理请求返回的数据.
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">数据源实例</param>
- /// <param name="body">保存数据的流</param>
- private void ProcessData<T>(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);
- }
- }
- }
- /// <summary>
- /// 取得返回的http头部内容,并设置相关属性.
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">数据源实例</param>
- /// <param name="data">待处理的数据</param>
- /// <param name="readLength">读取的长度</param>
- /// <returns>数据内容的起始位置,返回-1表示未读完头部内容</returns>
- private int GetHeaders<T>(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;
- }
- /// <summary>
- /// 取得未分块数据的内容
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">数据源实例</param>
- /// <param name="data">已读取未处理的字节数据</param>
- /// <param name="startIndex">起始位置</param>
- /// <param name="readLength">读取的长度</param>
- /// <param name="body">保存块数据的流</param>
- private void GetBodyData<T>(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);
- }
- /// <summary>
- /// 取得分块数据
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">Socket实例</param>
- /// <param name="data">已读取未处理的字节数据</param>
- /// <param name="startIndex">起始位置</param>
- /// <param name="readLength">读取的长度</param>
- /// <param name="body">保存块数据的流</param>
- private void GetChunkData<T>(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;
- }
- }
- }
- /// <summary>
- /// 取得分块数据的数据长度
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">Socket实例</param>
- /// <param name="data">已读取未处理的字节数据</param>
- /// <param name="startIndex">起始位置</param>
- /// <param name="readLength">读取的长度</param>
- /// <returns>块长度,返回0表示已到末尾.</returns>
- private int GetChunkHead<T>(T reader, ref byte[] data, ref int startIndex, ref int readLength)
- {
- int chunkSize = -1;
- List<char> tChars = new List<char>();//用于临时存储块长度字符
- 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;
- }
- /// <summary>
- /// 取得分块传回的数据内容
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">Socket实例</param>
- /// <param name="data">已读取未处理的字节数据</param>
- /// <param name="startIndex">起始位置</param>
- /// <param name="readLength">读取的长度</param>
- /// <param name="body">保存块数据的流</param>
- /// <param name="chunkSize">块长度</param>
- private void GetChunkBody<T>(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);
- }
- /// <summary>
- /// 从数据源读取数据
- /// </summary>
- /// <typeparam name="T">数据源类型</typeparam>
- /// <param name="reader">数据源</param>
- /// <param name="data">用于存储读取的数据</param>
- /// <returns>读取的数据长度,无数据为-1</returns>
- private int ReadData<T>(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
- {
- /// <summary>
- /// 获取请求回应状态码
- /// </summary>
- public string ResponseStatusCode
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取跳转url
- /// </summary>
- public string Location
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取是否由Gzip压缩
- /// </summary>
- public bool IsGzip
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取返回的文档类型
- /// </summary>
- public string ContentType
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取内容使用的字符集
- /// </summary>
- public string Charset
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取内容长度
- /// </summary>
- public long ContentLength
- {
- get;
- internal set;
- }
- /// <summary>
- /// 获取是否分块传输
- /// </summary>
- public bool IsChunk
- {
- get;
- internal set;
- }
- }
- }
|