通過Socket進行Http/Https 網頁操作

xuxubaby發表於2013-11-18

轉自:http://blog.csdn.net/rztyfx/article/details/6989715

 

此文章假設讀者已經熟悉在.NET下通過HttpWebRequest/WebClient來操作網頁,

但是由於學藝不精或經驗不夠豐富,仍有很多困惑和疑問?

那麼下面就通過一系列演示來解決其中一些問題。

 

廢話不多,先列舉一些HTTP/HTTPS操作過程經常遇到的問題:

1、HTTP協議頭引數?

     示例:

上圖是瀏覽google時通過IE9.0開發者工具抓到的HTTP資料包,如圖中所示,HTTP協議頭

      存在一些固定的鍵值對;很多人經常搞不清楚這些協議頭到底是否必須要?是否必須和瀏覽器

      提交時抓去到的一模一樣去提交?

      要回答這2個問題,一是需要對HTTP協議有一個簡單瞭解,二是要根據具體應用進行分析;

      如:Accept引數,細心一點就會發現請求頁面時可能為 text/html 請求圖片時就為image/jpeg

            當然根據系統環境,還有Application/xml一類等。 那麼你需要根據需求………         

      又比如:user-Agent引數,很明顯裡面包含的是系統型別與瀏覽器型別,假設你需要偽造!!

      還有如:Accept-Encoding引數,如果大家在使用HttpWebRequest請求網頁時也新增了如上圖的 Accept-Encoding引數,那自己會很杯具的發現,請求回來的內容需要先gzip解壓;該怎麼做你應該知道了!!

      類似上面提交的三種情況,我們需要的就是經驗和靈活應用,作為開發者,我們的優勢是可以換位

      以一個開發者的角度來思考問題:比如我是否會通過Http Header中的Referer引數頭來判斷訪問者

      來路,是否允許它請求;

     

      我的做法就是簡單、簡單、簡單:

StringBuilder bulider = new StringBuilder();
                bulider.AppendLine("POST /user/pass_request HTTP/1.1");
                bulider.AppendLine("Host: www.*.com");
                bulider.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; IE 9.0)");
                bulider.AppendLine("Accept: text");
                bulider.AppendLine("Content-Type: application/x-www-form-urlencoded");
                bulider.AppendLine(string.Format("Content-Length: {0}\r\n", Encoding.Default.GetBytes(strPost).Length));
                bulider.Append(strPost);


需要補充的是Post操作時必須包含Content-Type: application/x-www-form-urlencoded引數;

2、HTTP與HTTPS的區別?

     HTTPS相比HTTP是一種安全可靠的連線,開啟一個HTTPS連線,我們會發現瀏覽器都會有相應的提示,

類似這樣的,可以通過點選其圖示檢視安全狀態和證書;

那麼我們用HttpWebRequest操作HTTPS和HTTP時有什麼區別或者不一定的地方呢?

其實大部分地方都是一樣的,很多網站在伺服器段並沒有做非常嚴格的限制和配置,在做HTTPS操作時甚至不需要新增證書,但是如果遇到必須要使用證書的,那就需要指定HttpWebRequest的Credentials屬性;

關於這一點就不詳解了,有興趣的可以關注下蘇飛的文章http://www.cnblogs.com/sufei/archive/2011/10/22/2221289.html

另外關注HttpWebRequest操作HTTPS的文章網上也較多,大家可自行搜尋!

 

3、Cookies問題?

HttpwebRequest好的一點是我們不需要去關注Cookies,.NET中提供了CookieContainer類來做 Cookies容器,很好的與HttpWebRequest結合,使得我們不必要自己去處理Cookies,當然一些涉及到修改Cookies內容的時候還是有必要的。

例如一年前我分析過拍拍網->財付通的跳轉,它就在Cookies中存放了一個引數導致在跳轉過程中不需要重新登入,而直接從HTTP頁面訪問至HTTPS;

下面在講Socket操作時就需要特別關注下這個Cookies咯!

 

4、速度問題?

    毫無疑問由於HTTP協議是基於TCP/IP的,而HttpWebRequest在封裝過程中的一些處理或多或少的會影響到訪問速度;至於影響多少,我在前段時間做一個國外網站操作的時候簡單對比了下,HttpWebRequest和Socket原生操作的速度相差大概在5倍以上;

   很多時候其實我們並不是很關注速度影響,但是實際應用過程中就會遇到有客戶要求的飛速(當然不排除一些客戶認為執行緒越多速度越快)。

 

簡單談了下以上4個問題,其實還很是很片面,鑑於個人表達能力有限,有些東西還需要大家在實踐中去認識瞭解;下面就來著重看下通過Socket操作HTTP/HTTPS;

 

前面我們已經知道了簡單的HTTP協議,也知道HTTP是基於TCP/IP協議的,對於有網路經驗的同學,我們就可以直接寫Socket提交HTTP協議,這一步相對比較簡單,我們直接看一下程式碼就OK了:

static byte[] InternalSocketHttp(IPEndPoint endpoint,
           HttpArgs args,
           HttpMethod method)
        {
            using (Socket sK = new Socket(AddressFamily.InterNetwork,
                        SocketType.Stream,
                        ProtocolType.Tcp))
            {
                try
                {
                    sK.Connect(endpoint);
                    if (sK.Connected)
                    {
                        byte[] buff = ParseHttpArgs(method, args);
                        if (sK.Send(buff) > 0)
                        {
                            return ParseResponse(endpoint,sK,args);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            return null;
        }


 

其中有2個比較重要的函式一個是ParseHttpArgs(),另外一個是ParseResponse();

先看第一個函式:

static byte[] ParseHttpArgs(HttpMethod method, HttpArgs args)
        {
            StringBuilder bulider = new StringBuilder();
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("POST {0} HTTP/1.1",
                    args.Url));
                bulider.AppendLine("Content-Type: application/x-www-form-urlencoded");
            }
            else
            {
                bulider.AppendLine(string.Format("GET {0} HTTP/1.1",
                args.Url));
            }
            bulider.AppendLine(string.Format("Host: {0}",
                args.Host));
            bulider.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; IE 9.0)");
            if (!string.IsNullOrEmpty(args.Referer))
                bulider.AppendLine(string.Format("Referer: {0}",
                    args.Referer));
            bulider.AppendLine("Connection: keep-alive");
            bulider.AppendLine(string.Format("Accept: {0}",
                args.Accept));
            bulider.AppendLine(string.Format("Cookie: {0}",
                args.Cookie));
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("Content-Length: {0}\r\n",
                   Encoding.Default.GetBytes(args.Body).Length));
                bulider.Append(args.Body);
            }
            else
            {
                bulider.Append("\r\n");
            }
            string header = bulider.ToString();
            return Encoding.Default.GetBytes(header);
        }


 

 

通過上面的程式碼,很清晰的我們就能看到ParseHttpArgs其實就是將HttpArgs的一些屬性填充為HTTP協議,並返回其二進位制內容用於Socket提交,其中值得注意的一點就是在HTTP協議頭完畢後實際上需要一個空行,這一點有疑惑的同學請看HTTP協議詳解:http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html

 

至於ParseResponse函式我們暫時先不講解,其大致過程是先讀取Socket ,讀取出Http返回包的協議頭,

然後根據不同的協議頭進行下一步處理;

 

-----------------------到此為止,簡單的利用Socket進行HTTP操作的例子已經有了,無非就是填充協議,提交資料,解析返回資料;

 

下面我們來了解下HTTP返回協議頭:

HTTP/1.1 200   HTTP/1.1 404 大家都比較熟悉,一個是成功 一個是404無法訪問,我們需要關注的是 HTTP/1.1 302 ,對於302的解釋大家可以google下.

而我們的任務是需要處理302,在瀏覽器操作時遇到301 302之類的協議時,一般瀏覽器會自動幫我們進行跳轉,而我們使用HttpWebRequest操作時也可以通過指定AllowAutoRedirect屬性來響應重定向;

 

那麼在Socket提交時,可就沒有那麼智慧了,這時候就需要我們自己處理302,否則你會發現你請求的結果和你的預期不一樣;

 

 

if (header.StartsWith("HTTP/1.1 302"))
                {
                    int start = header
                        .ToUpper().IndexOf("LOCATION");
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        args.Url = sArry[0].Remove(0, 10);
                        return Get(endpoint, args, certificates);  //注意:302協議需要重定向
                    }
                }


 

上面這段程式碼時前面提到的ParseResponse函式的一部分,根據302協議描述,我們知道它有一個Location引數,這個引數內容就是需要重定向的地址,當我們判斷到302操作時就需要重新提交;

 

這裡有一個問題插播下:HTTP協議是一種短連線,而我們一般做網路通訊做Socket操作時,大多數是長連線操作,那麼這裡我們遇到302 或者 發起一個請求,這個過程是也應該按照HTTP協議的要求進行短連線,即 連線伺服器-> 發起一個HTTP請求->收到一個HTTP請求->斷開伺服器連線。(所以細心的同學就會發現之前的示例中的using語句以及此處的 Get(*,*,*)方法)

 

OK,簡單的瞭解了302如何處理,還有重要的一點就是Cookies,很多同學都知道Cookies也是HTTP協議的一個引數,在用Socket提交時也需要指定Cookies,這一點很好理解,服務端會根據Cookies來判斷頁面跳轉之間的狀態,那麼假設你需要你的提交能被服務端正確判斷到,那你必須提交Cookies讓服務端知道就是你;

 

知道了這一點,那我們一些同學在新增Cookies的時候就犯難了,抓包的時候發現Cookies裡面類似SessionID的一段隨機字串不知道哪裡來的,oh my god,我用瀏覽器瀏覽的時候會有這個值,那用Socket的時候怎麼辦呢,其實在.NET中有一個類SessionIDManager (System.Web.SessionState下)可以幫助我們,

 

sessionID = sessionIDManager.CreateSessionID(null);
bulider.AppendLine(string.Format("Cookie: Language=en-US;ASP.NET_SessionId={0}",
                    sessionID));


 

這下應該明白了吧;

 

說了這麼多,我們還是來看下ParseResponse函式吧(注意:這只是一個簡單示例,並不一定完全正確,不完善,請酌情使用)

private static byte[] ParseResponse(IPEndPoint endpoint,
             Socket sK,
             HttpArgs args)
        {
            //嘗試10秒時間讀取協議頭
            CancellationTokenSource source = new CancellationTokenSource();
            Task<string> myTask = Task.Factory.StartNew<string>(
                new Func<object, string>(ReadHeaderProcess),
                sK,
                source.Token);
            if (myTask.Wait(10000))
            {
                string header = myTask.Result;
                if (header.StartsWith("HTTP/1.1 302"))
                {
                    int start = header
                        .ToUpper().IndexOf("LOCATION");
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        args.Url = sArry[0].Remove(0, 10);
                        return Get(endpoint, args);  //注意:302協議需要重定向
                    }
                }
                else if (header.StartsWith("HTTP/1.1 200"))  //繼續讀取內容
                {
                    int start = header
                           .ToUpper().IndexOf("CONTENT-LENGTH");
                    int content_length = 0;
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        content_length = Convert.ToInt32(sArry[0].Split(':')[1]);
                        if (content_length > 0)
                        {
                            byte[] bytes = new byte[content_length];
                            if (sK.Receive(bytes) > 0)
                            {
                                return bytes;
                            }
                        }
                    }
                    else
                    {
                        //不存在Content-Length協議頭
                        return ParseResponse(sK);
                    }
                }
                else
                {
                    return Encoding.Default.GetBytes(header);
                }
            }
            else
            {
                source.Cancel();  //超時的話,別忘記取消任務哦
            }
            return null;
        }


 

 

解析下上面這段程式碼:

1)非同步讀取返回的協議頭;設定超時時間!!!!!

2)解析協議頭 ,200  / 302  /404 等!!!!

    示例:

/// <summary>
        ///  讀取協議頭
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        static string ReadHeaderProcess(object args)
        {
            Socket sK = (Socket)args;
            StringBuilder bulider = new StringBuilder();
            while (true)
            {
                byte[] buff = new byte[1];
                int read = sK.Receive(buff, SocketFlags.None);
                if (read > 0)
                {
                    bulider.Append((char)buff[0]);
                }
                string temp = bulider.ToString();
                if (temp.Contains("\r\n\r\n"))
                {
                    break;
                }
            }
            return bulider.ToString();
        }


 

 

3)根據不同返回型別做不同操作!!!!

在返回協議中沒有判斷到Content-Length引數時通過ParseResponse(sK)方法去解析內容,這裡需要說明的是這個函式並不完全正確,通過迴圈讀取 判斷直到讀取到</html> 就認為結束,所以很有可能產生死迴圈,其程式碼如下:

 

/// <summary>
        /// 注意:此函式可能產生死迴圈
        /// </summary>
        /// <param name="ssl"></param>
        /// <returns></returns>
        static byte[] ParseResponse(Socket sK)
        {
            ArrayList array = new ArrayList();
            StringBuilder bulider = new StringBuilder();
            int length = 0;
            while (true)
            {
                byte[] buff = new byte[1024];
                int len = sK.Receive(buff);
                if (len > 0)
                {
                    length += len;
                    byte[] reads = new byte[len];
                    Array.Copy(buff, 0, reads, 0, len);
                    array.Add(reads);
                    bulider.Append(Encoding.Default.GetString(reads));
                }
                string temp = bulider.ToString();
                if (temp.ToUpper().Contains("</HTML>"))
                {
                    break;
                }
            }
            byte[] bytes = new byte[length];
            int index = 0;
            for (int i = 0; i < array.Count; i++)
            {
                byte[] temp = (byte[])array[i];
                Array.Copy(temp, 0, bytes,
                    index, temp.Length);
                index += temp.Length;
            }
            return bytes;
        }


 

 

OK,OK,又說了一大堆關於Socket操作HTTP的東東,其中提到了Cookies 提到了302 提到了HTTP協議,也基本對應與文章開頭提到的幾個問題;下面我們還要繼續關注下Socket如何操作HTTPS;

 

其實使用Socket操作HTTPS時與HTTP還是有一些不同的,首先證書載入無疑,還有一點就是連線,一般HTTP伺服器埠80,而HTTPS服務埠是443,

如果有人妄圖通過Socket提交 適用HTTPS協議的加密資料的話,那我沒話說,您牛! 我這裡需要講解的是通過SslStream 載入證書來完成Socket下對HTTPS的操作;

 

來看下示例程式碼:

 

static byte[] InternalSslSocketHttp(IPEndPoint endpoint,
            X509CertificateCollection certificates,
            HttpArgs args,
            HttpMethod method)
        {
            TcpClient tcp = new TcpClient();
            try
            {
                tcp.Connect(endpoint);
                if (tcp.Connected)
                {
                    using (SslStream ssl = new SslStream(tcp.GetStream(),
                        false,
                        new RemoteCertificateValidationCallback(ValidateServerCertificate),
                        null))
                    {
                        ssl.AuthenticateAsClient("ServerName",
                            certificates,
                            SslProtocols.Tls,
                            false);
                        if (ssl.IsAuthenticated)
                        {
                            byte[] buff = ParseHttpArgs(method, args);  //生成協議包
                            ssl.Write(buff);
                            ssl.Flush();
                            return ParseSslResponse(endpoint, ssl, args, certificates);
 
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return null;
        }


 

 

怎麼樣,是否是似曾相識,跟Socket操作HTTP時結構很相似唉~~  ,不同的是這裡使用TcpClient,

這裡有個引數是X509CertificateCollection ,

其實這一切都只為了SslStream,有了SslStream 我想大家也都明白了,剩下的事情是差不多的了。

 

提一點關於ValidateServerCertificate這個函式,有過證書操作經驗的同學應該不陌生了,大部分情況下,驗證客戶端證書也好,服務端證書也好,我們經常是直接返回一個 true~~  (我不知道為啥, 但是我做上一個WCF應用的時候也是這樣乾的) 所以在有遇到證書檢驗的時候,大家不妨也直接來個return true試試先;

 

至於剩下的程式碼我就不詳細說咯:貼一下我自己用到的HttpHelper,其中一些程式碼是剛寫的,有錯誤的地方還請大家海涵,有需要的就直接copy下去,自己用的時候自己除錯吧!

 

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
/************************************************************************/
/* Author:huliang
 * Email:huliang@yahoo.cn
 * QQ:12658501
 * 說明:轉載請註明出處
/************************************************************************/
 
namespace iGame
{
    class HttpArgs
    {
        public string Url { get; set; }
        public string Host { get; set; }
        public string Accept { get; set; }
        public string Referer { get; set; }
        public string Cookie { get; set; }
        public string Body { get; set; }
    }
 
    static class HttpHelper
    {
        /// <summary>
        /// 提交方法
        /// </summary>
        enum HttpMethod
        {
            GET,
            POST
        }
 
        #region HttpWebRequest & HttpWebResponse
 
        /// <summary>
        /// Get方法
        /// </summary>
        /// <param name="geturl">請求地址</param>
        /// <param name="cookieser">Cookies儲存器</param>
        /// <returns>請求返回的Stream</returns>
        public static string Get(string url,
            CookieContainer cookies,
            Encoding encoding)
        {
            return InternalHttp(HttpMethod.GET, url, null, cookies, encoding);
        }
 
        public static Stream Get(string url,
            CookieContainer cookies)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.UserAgent = "Mozilla/5.0 (Windows NT 6.1;MSIE 6.0;)";
            request.CookieContainer = cookies;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            return response.GetResponseStream();
        }
 
        /// <summary>
        /// Post方法
        /// </summary>
        /// <param name="posturl">請求地址</param>
        /// <param name="bytes">Post資料</param>
        /// <param name="cookieser">Cllkies儲存器</param>
        /// <returns>請求返回的流</returns>
        public static string Post(string url,
            byte[] bytes,
            CookieContainer cookies,
            Encoding encoding)
        {
            return InternalHttp(HttpMethod.POST, url, bytes, cookies, encoding);
        }
 
        /// <summary>
        /// Http操作
        /// </summary>
        /// <param name="method">請求方式</param>
        /// <param name="url">請求地址</param>
        /// <param name="bytes">提交資料</param>
        /// <param name="cookieser">Cookies儲存器</param>
        /// <returns>請求結果</returns>
        static string InternalHttp(HttpMethod method,
            string url,
            byte[] bytes,
            CookieContainer cookies,
            Encoding encoding)
        {
            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException("訪問url不能為空");
            if (method == HttpMethod.POST)
            {
                if (bytes == null)
                    throw new ArgumentNullException("提交的post資料不能為空");
            }
            if (cookies == null)
                throw new ArgumentNullException("Cookies儲存器不能為空");
            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = method.ToString();
                request.UserAgent = "Mozilla/5.0 (Windows NT 6.1;MSIE 9.0;)";
                request.CookieContainer = cookies;
                if (method == HttpMethod.POST)
                {
                    request.ContentType = "application/x-www-form-urlencoded";
                    request.ContentLength = bytes.Length;
                    using (Stream stream = request.GetRequestStream())
                    {
                        stream.Write(bytes, 0, bytes.Length);
                        stream.Flush();
                        stream.Close();
                    }
                }
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    using (StreamReader reader = new StreamReader(response.GetResponseStream(), encoding))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }
 
        #endregion
 
        #region Ssl Socket
 
        static bool ValidateServerCertificate(
                 object sender,
                 X509Certificate certificate,
                 X509Chain chain,
                 SslPolicyErrors sslPolicyErrors)
        {
            /*
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;
            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
            return false;
            */
            return true;
        }
 
        public static byte[] Get(IPEndPoint endpoint, HttpArgs args, X509CertificateCollection certificates)
        {
            return InternalSslSocketHttp(endpoint, certificates, args, HttpMethod.GET);
        }
 
        public static byte[] Post(IPEndPoint endpoint,
            HttpArgs args,
            X509CertificateCollection certificates)
        {
            return InternalSslSocketHttp(endpoint, certificates, args, HttpMethod.POST);
        }
 
        static byte[] InternalSslSocketHttp(IPEndPoint endpoint,
            X509CertificateCollection certificates,
            HttpArgs args,
            HttpMethod method)
        {
            TcpClient tcp = new TcpClient();
            try
            {
                tcp.Connect(endpoint);
                if (tcp.Connected)
                {
                    using (SslStream ssl = new SslStream(tcp.GetStream(),
                        false,
                        new RemoteCertificateValidationCallback(ValidateServerCertificate),
                        null))
                    {
                        ssl.AuthenticateAsClient("ServerName",
                            certificates,
                            SslProtocols.Tls,
                            false);
                        if (ssl.IsAuthenticated)
                        {
                            byte[] buff = ParseHttpArgs(method, args);  //生成協議包
                            ssl.Write(buff);
                            ssl.Flush();
                            return ParseSslResponse(endpoint, ssl, args, certificates);
 
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return null;
        }
 
        /// <summary>
        /// 解析 Ssl Response
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="ssl"></param>
        /// <param name="args"></param>
        /// <param name="certificates"></param>
        /// <returns></returns>
        private static byte[] ParseSslResponse(IPEndPoint endpoint,
            SslStream ssl,
            HttpArgs args,
            X509CertificateCollection certificates)
        {
            //嘗試10秒時間讀取協議頭
            CancellationTokenSource source = new CancellationTokenSource();
            Task<string> myTask = Task.Factory.StartNew<string>(
                new Func<object, string>(ReadSslHeaderProcess),
                ssl,
                source.Token);
            if (myTask.Wait(10000))
            {
                string header = myTask.Result;
                if (header.StartsWith("HTTP/1.1 302"))
                {
                    int start = header
                        .ToUpper().IndexOf("LOCATION");
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        args.Url = sArry[0].Remove(0, 10);
                        return Get(endpoint, args, certificates);  //注意:302協議需要重定向
                    }
                }
                else if (header.StartsWith("HTTP/1.1 200"))  //繼續讀取內容
                {
                    int start = header
                           .ToUpper().IndexOf("CONTENT-LENGTH");
                    int content_length = 0;
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        content_length = Convert.ToInt32(sArry[0].Split(':')[1]);
                        if (content_length > 0)
                        {
                            byte[] bytes = new byte[content_length];
                            if (ssl.Read(bytes, 0, bytes.Length) > 0)
                            {
                                return bytes;
                            }
                        }
                    }
                    else
                    {
                        //不存在Content-Length協議頭
                        return ParseSslResponse(ssl);
                    }
                }
                else
                {
                    return Encoding.Default.GetBytes(header);
                }
            }
            else
            {
                source.Cancel();  //超時的話,別忘記取消任務哦
            }
            return null;
        }
 
        /// <summary>
        ///  讀取協議頭
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        static string ReadSslHeaderProcess(object args)
        {
            SslStream ssl = (SslStream)args;
            StringBuilder bulider = new StringBuilder();
            while (true)
            {
                int read = ssl.ReadByte();
                if (read != -1)
                {
                    byte b = (byte)read;
                    bulider.Append((char)b);
                }
                string temp = bulider.ToString();
                if (temp.Contains("\r\n\r\n"))
                {
                    break;
                }
            }
            return bulider.ToString();
        }
 
        /// <summary>
        /// 注意:此函式可能產生死迴圈
        /// </summary>
        /// <param name="ssl"></param>
        /// <returns></returns>
        static byte[] ParseSslResponse(SslStream ssl)
        {
            //沒有指定協議頭,嘗試讀取至</html>
            ArrayList array = new ArrayList();
            StringBuilder bulider = new StringBuilder();
            int length = 0;
            while (true)
            {
                byte[] buff = new byte[1024];
                int len = ssl.Read(buff, 0, buff.Length);
                if (len > 0)
                {
                    length += len;
                    byte[] reads = new byte[len];
                    Array.Copy(buff, 0, reads, 0, len);
                    array.Add(reads);
                    bulider.Append(Encoding.Default.GetString(reads));
                }
                string temp = bulider.ToString();
                if (temp.ToUpper().Contains("</HTML>"))
                {
                    break;
                }
            }
            byte[] bytes = new byte[length];
            int index = 0;
            for (int i = 0; i < array.Count; i++)
            {
                byte[] temp = (byte[])array[i];
                Array.Copy(temp, 0, bytes,
                    index, temp.Length);
                index += temp.Length;
            }
            return bytes;
        }
 
        #endregion
 
        #region Socket
 
        public static byte[] Get(IPEndPoint endpoint,
            HttpArgs args)
        {
            return InternalSocketHttp(endpoint, args, HttpMethod.GET);
        }
 
        public static byte[] Post(IPEndPoint endpoint,
            HttpArgs args)
        {
            return InternalSocketHttp(endpoint, args, HttpMethod.POST);
        }
 
        static byte[] InternalSocketHttp(IPEndPoint endpoint,
           HttpArgs args,
           HttpMethod method)
        {
            using (Socket sK = new Socket(AddressFamily.InterNetwork,
                        SocketType.Stream,
                        ProtocolType.Tcp))
            {
                try
                {
                    sK.Connect(endpoint);
                    if (sK.Connected)
                    {
                        byte[] buff = ParseHttpArgs(method, args);
                        if (sK.Send(buff) > 0)
                        {
                            return ParseResponse(endpoint,sK,args);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            return null;
        }
 
        private static byte[] ParseResponse(IPEndPoint endpoint,
             Socket sK,
             HttpArgs args)
        {
            //嘗試10秒時間讀取協議頭
            CancellationTokenSource source = new CancellationTokenSource();
            Task<string> myTask = Task.Factory.StartNew<string>(
                new Func<object, string>(ReadHeaderProcess),
                sK,
                source.Token);
            if (myTask.Wait(10000))
            {
                string header = myTask.Result;
                if (header.StartsWith("HTTP/1.1 302"))
                {
                    int start = header
                        .ToUpper().IndexOf("LOCATION");
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        args.Url = sArry[0].Remove(0, 10);
                        return Get(endpoint, args);  //注意:302協議需要重定向
                    }
                }
                else if (header.StartsWith("HTTP/1.1 200"))  //繼續讀取內容
                {
                    int start = header
                           .ToUpper().IndexOf("CONTENT-LENGTH");
                    int content_length = 0;
                    if (start > 0)
                    {
                        string temp = header.Substring(start, header.Length - start);
                        string[] sArry = Regex.Split(temp, "\r\n");
                        content_length = Convert.ToInt32(sArry[0].Split(':')[1]);
                        if (content_length > 0)
                        {
                            byte[] bytes = new byte[content_length];
                            if (sK.Receive(bytes) > 0)
                            {
                                return bytes;
                            }
                        }
                    }
                    else
                    {
                        //不存在Content-Length協議頭
                        return ParseResponse(sK);
                    }
                }
                else
                {
                    return Encoding.Default.GetBytes(header);
                }
            }
            else
            {
                source.Cancel();  //超時的話,別忘記取消任務哦
            }
            return null;
        }
 
        /// <summary>
        ///  讀取協議頭
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        static string ReadHeaderProcess(object args)
        {
            Socket sK = (Socket)args;
            StringBuilder bulider = new StringBuilder();
            while (true)
            {
                byte[] buff = new byte[1];
                int read = sK.Receive(buff, SocketFlags.None);
                if (read > 0)
                {
                    bulider.Append((char)buff[0]);
                }
                string temp = bulider.ToString();
                if (temp.Contains("\r\n\r\n"))
                {
                    break;
                }
            }
            return bulider.ToString();
        }
 
        /// <summary>
        /// 注意:此函式可能產生死迴圈
        /// </summary>
        /// <param name="ssl"></param>
        /// <returns></returns>
        static byte[] ParseResponse(Socket sK)
        {
            ArrayList array = new ArrayList();
            StringBuilder bulider = new StringBuilder();
            int length = 0;
            while (true)
            {
                byte[] buff = new byte[1024];
                int len = sK.Receive(buff);
                if (len > 0)
                {
                    length += len;
                    byte[] reads = new byte[len];
                    Array.Copy(buff, 0, reads, 0, len);
                    array.Add(reads);
                    bulider.Append(Encoding.Default.GetString(reads));
                }
                string temp = bulider.ToString();
                if (temp.ToUpper().Contains("</HTML>"))
                {
                    break;
                }
            }
            byte[] bytes = new byte[length];
            int index = 0;
            for (int i = 0; i < array.Count; i++)
            {
                byte[] temp = (byte[])array[i];
                Array.Copy(temp, 0, bytes,
                    index, temp.Length);
                index += temp.Length;
            }
            return bytes;
        }
        #endregion
 
        #region  Helper
 
        static byte[] ParseHttpArgs(HttpMethod method, HttpArgs args)
        {
            StringBuilder bulider = new StringBuilder();
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("POST {0} HTTP/1.1",
                    args.Url));
                bulider.AppendLine("Content-Type: application/x-www-form-urlencoded");
            }
            else
            {
                bulider.AppendLine(string.Format("GET {0} HTTP/1.1",
                args.Url));
            }
            bulider.AppendLine(string.Format("Host: {0}",
                args.Host));
            bulider.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; IE 9.0)");
            if (!string.IsNullOrEmpty(args.Referer))
                bulider.AppendLine(string.Format("Referer: {0}",
                    args.Referer));
            bulider.AppendLine("Connection: keep-alive");
            bulider.AppendLine(string.Format("Accept: {0}",
                args.Accept));
            bulider.AppendLine(string.Format("Cookie: {0}",
                args.Cookie));
            if (method.Equals(HttpMethod.POST))
            {
                bulider.AppendLine(string.Format("Content-Length: {0}\r\n",
                   Encoding.Default.GetBytes(args.Body).Length));
                bulider.Append(args.Body);
            }
            else
            {
                bulider.Append("\r\n");
            }
            string header = bulider.ToString();
            return Encoding.Default.GetBytes(header);
        }
 
        #endregion
    }
}


 

 

 

相關文章