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;
}
}
}