c# https請求

  遇到Https網站,c# http請求的時候,老是報SSL鏈接錯誤。後來經搜索,發現有解決方案:html

       .net 2.0  須要引入一個第三方組件:BouncyCastle.dll,這是我寫的一個例子:python

       

        public static string RequestWebServerByTCP(Uri uri, string method, NameValueCollection parameter, string cookie, Encoding encoding)
        {
            try
            {
                StringBuilder RequestHeaders = new StringBuilder();

                RequestHeaders.Append(method + " " + uri.PathAndQuery + " HTTP/1.1\r\n");

                method = method.ToUpper();

                if (method == POSTMETHOD)
                    RequestHeaders.Append("Content-Type:application/x-www-form-urlencoded\r\n");
                RequestHeaders.Append("User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11\r\n");
                RequestHeaders.Append("Cookie:" + cookie + "\r\n");
                RequestHeaders.Append("Accept:*/*\r\n");
                RequestHeaders.Append("Host:" + uri.Host + "\r\n");


                byte[] postdata = null;
                StringBuilder sb = new StringBuilder();

                if (method == GETMETHOD)
                {
                    uri = GetMethodQueryString(uri, parameter, encoding);
                }
                else if (method == POSTMETHOD)
                {
                    if (parameter != null)
                    {
                        foreach (string key in parameter)
                        {
                            sb.Append(string.Format(FORMATSTR1, System.Web.HttpUtility.UrlEncode(key, encoding), System.Web.HttpUtility.UrlEncode(parameter[key], encoding)));
                        }
                    }
                    if (sb.Length != 0)
                    {
                        sb = sb.Remove(sb.Length - 1, 1);
                    }
                    postdata = encoding.GetBytes(sb.ToString());

                    RequestHeaders.Append("Content-Length:" + postdata.Length + "\r\n");
                }

                RequestHeaders.Append("Connection:close\r\n\r\n");
                byte[] req = Encoding.UTF8.GetBytes(RequestHeaders.ToString() + sb.ToString());


                int port = 443;

                MyTlsClient client = new MyTlsClient();
                var protocol = OpenTlsConnection(uri.Host, port, client);

                Stream tlsStream = protocol.Stream;
                tlsStream.Write(req, 0, req.Length);
                tlsStream.Flush();

                StreamReader reader = new StreamReader(tlsStream);

                String line;
                StringBuilder html = new StringBuilder();

                string firstLine = "";

                int i = 0;

                while ((line = reader.ReadLine()) != null)
                {
                    if (i == 0)
                    {
                        firstLine = line;
                        i++;
                    }

                    html.AppendLine(line);

                    if (line.Contains("</html>"))
                    {
                        break;
                    }
                }
                protocol.Close();

                string httpstatusCode = "";

                string[] httpstatus = firstLine.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                if (httpstatus.Length > 2)
                {
                    httpstatusCode = httpstatus[1];
                }
                else
                {
                    //請求無效
                    httpstatusCode = "400";
                }
                return html.ToString();

            }
            catch
            {
                return "";
            }

        }

  請求到的html,爲何須要一行一行讀呢?我在調試的時候發現有個bug,若是一次性讀取的時候,它停不下來,最終報錯,因此我作了一個讀到html末尾的判斷。chrome

       繼承了提供的默認類:c#

  class MyTlsClient : DefaultTlsClient
    {
        public override TlsAuthentication GetAuthentication()
        {
            return new MyTlsAuthentication();
        }
    }

    // Need class to handle certificate auth  
    class MyTlsAuthentication : TlsAuthentication
    {
        public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
        {
            // return client certificate  
            return null;
        }

        public void NotifyServerCertificate(Certificate serverCertificate)
        {
            // validate server certificate  
        }
    }

 

       internal static TlsClientProtocol OpenTlsConnection(string hostname, int port, TlsClient client)
        {
            TcpClient tcp = new TcpClient(hostname, port);

            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream(), secureRandom);
            protocol.Connect(client);
            return protocol;
        }

拼接url參數的方法:瀏覽器

      private static Uri GetMethodQueryString(Uri uri, NameValueCollection parameter, Encoding encoding)
        {
            List<KeyValuePair<string, string>> parameter1 = new List<KeyValuePair<string, string>>();
            foreach (string key in parameter)
            {
                parameter1.Add(new KeyValuePair<string, string>(key, parameter[key]));
            }
            return GetMethodQueryString(uri, parameter1, encoding);
        }

        private static Uri GetMethodQueryString(Uri uri, List<KeyValuePair<string, string>> parameter, Encoding encoding)
        {
            string format = string.Empty;
            UriBuilder uribuilfer = new UriBuilder(uri);

            string QueryString = string.Empty;
            if (string.IsNullOrEmpty(uribuilfer.Query))
            {
                format = FORMATSTR1;
            }
            else
            {
                format = FORMATSTR2;
            }
            QueryString = uribuilfer.Query;
            if (parameter != null)
            {
                foreach (KeyValuePair<string, string> item in parameter)
                {
                    QueryString += string.Format(format, System.Web.HttpUtility.UrlEncode(item.Key, encoding), System.Web.HttpUtility.UrlEncode(item.Value, encoding));
                }
            }
            QueryString = QueryString.TrimEnd(new char[] { '&' });
            QueryString = QueryString.TrimStart(new char[] { '?' });
            uribuilfer.Query = QueryString;
            uri = uribuilfer.Uri;
            return uri;
        }

   注意:List<KeyValuePair<string, string>>NameValueCollection類型的參數有什麼區別呢?它們都包含相同的key,只不過存儲的時候,NameValueCollection會把含有相同Key的值用逗號隔開,存在一塊兒。這樣請求有可能會失敗,拿不到數據。本人所以問題,折騰了好久,用python實現了請求,後來在.net core中實現了一遍,最後終於低下了高傲的頭顱,纔看到傳參時候,有點問題。cookie

       .net  4.0中,只須要添加一句話:ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;app

       .net 4.5中,什麼都不用管。dom

       2.0中連TCP都用上了,不過咱們看到了http請求的本質,把一段具備格式的請求頭+請求數據轉爲二進制發送到主機的某個端口,返回流,經過讀取流,就能夠拿到結果。tcp

       說到這,咱們來看看Request消息格式:ide

       

GET https://www.baidu.com/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: www.baidu.com
Cookie: BAIDUID=C1EFC3A3466AAAEBE74C6F6E7F413FA8:FG=1; BIDUPSID=C1EFC3A3466AAAEBE74C6F6E7F413FA8; PSTM=1525339270; BD_LAST_QID=12260391193367555241

一、請求行,包含請求的方法,url,http協議版本

二、請求頭,接收的格式,瀏覽器代理,cookie等等

三、空行

四、請求體,傳遞數據

 

Response格式:

HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0x9a1ff959000016d0
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Cxy_all: baidu+77e5655ffd82ce31adf5edff251fc585
Date: Thu, 03 May 2018 09:21:10 GMT
Expires: Thu, 03 May 2018 09:21:03 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Set-Cookie: H_PS_PSSID=1428_21080_20719; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Vary: Accept-Encoding
X-Powered-By: HPHP
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

html

一、狀態行

二、消息報頭,content-type,Date,Set-Cookie

三、空行

四、正文

相關文章
相關標籤/搜索