引言
最近有朋友問如何用winform模擬post請求,然后登錄網站,稍微想了一下,大致就是對http報文的相關信息的封裝,然后請求網站登錄地址的樣子。發現自己的博客中對這部分只是也沒總結,就借着這股風,總結一下http報文的相關知識吧。
HTTP定義
超文本傳輸協議 (HTTP-Hypertext transfer protocol) 是一種詳細規定了瀏覽器和萬維網服務器之間互相通信的規則,通過因特網傳送萬維網文檔的數據傳送協議。
這里對http的具體內容就不再介紹了,主要分析http報文信息。
http報文分為:請求報文和響應報文。
HTTP請求報文
一個Http請求報文由請求行(request line)、請求頭部(header)、空行和請求數據4個部分組成,請求報文個格式如下:
Post請求
弄一個簡單的登錄頁面,使用ajax發送post請求,在IE下瀏覽,F12分析一下它的請求報文:
頁面代碼:

1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>wolfy信息系統登錄</title> 6 <script type="text/javascript" src="Scripts/jquery-1.11.0.js"></script> 7 <script type="text/javascript"> 8 $(function () { 9 $("#btnLogin").click(function () { 10 var name = $("#txtUserName").val(); 11 var pwd = $("#txtPwd").val(); 12 $.ajax({ 13 url: "Ashx/Login.ashx", 14 data: "name=" + name + "&pwd=" + pwd, 15 type: "POST", 16 dataType: "text", 17 success: function (msg) { 18 if (msg=="1") { 19 $("#divMsg").html("登錄成功"); 20 } else { 21 $("#divMsg").html("登錄失敗"); 22 } 23 } 24 25 26 }); 27 }); 28 }); 29 </script> 30 </head> 31 <body> 32 <div style="text-align:center;"> 33 <table> 34 <tr> 35 <td>用戶名:</td> 36 <td><input type="text" id="txtUserName" name="name" value="admin" /></td> 37 </tr> 38 <tr> 39 <td>密碼:</td> 40 <td><input type="password" id="txtPwd" name="name" value="admin" /></td> 41 </tr> 42 <tr> 43 <td colspan="2"><input type="button" id="btnLogin" name="name" value="登錄" /></td> 44 </tr> 45 </table> 46 <div id="divMsg"></div> 47 </div> 48 </body> 49 </html>
一般處理頁代碼:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace Wolfy.LoginWeb.Ashx 7 { 8 /// <summary> 9 /// Login 的摘要說明 10 /// </summary> 11 public class Login : IHttpHandler 12 { 13 14 public void ProcessRequest(HttpContext context) 15 { 16 context.Response.ContentType = "text/plain"; 17 //接收用戶名和密碼 18 string name = context.Request.Form["name"]; 19 string pwd = context.Request.Form["pwd"]; 20 if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(pwd)) 21 { 22 context.Response.Write("2"); 23 } 24 else 25 { 26 if (name == "admin" && pwd == "admin") 27 { 28 //登錄成功記入cookie 29 context.Response.Cookies["n"].Value = name; 30 context.Response.Cookies["n"].Expires = DateTime.Now.AddDays(7); 31 context.Response.Cookies["p"].Value = pwd; 32 context.Response.Cookies["p"].Expires = DateTime.Now.AddDays(7); 33 context.Response.Write("1"); 34 } 35 else 36 { 37 context.Response.Write("2"); 38 } 39 } 40 } 41 42 public bool IsReusable 43 { 44 get 45 { 46 return false; 47 } 48 } 49 } 50 }
Accept:瀏覽器可接受的MIME類型。
Accept-Charset:瀏覽器可接受的字符集。
Accept-Encoding:瀏覽器能夠進行解碼的數據編碼方式,比如gzip。Servlet能夠向支持gzip的瀏覽器返回經gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時間。
Accept-Language:瀏覽器所希望的語言種類,當服務器能夠提供一種以上的語言版本時要用到。
Authorization:授權信息,通常出現在對服務器發送的WWW - Authenticate頭的應答中。
Connection:表示是否需要持久連接。如果Servlet看到這里的值為“Keep - Alive”,或者看到請求使用的是HTTP 1.1(HTTP 1.1默認進行持久連接),它就可以利用持久連接的優點,當頁面包含多個元素時(例如Applet,圖片),顯著地減少下載所需要的時間。要實現這一點,Servlet需要在應答中發送一個 Content-Length頭,最簡單的實現方法是:先把內容寫入ByteArrayOutputStream,然后在正式寫出內容之前計算它的大小。
Content-Length:表示請求消息正文的長度。
Cookie:這是最重要的請求頭信息之一。
From:請求發送者的email地址,由一些特殊的Web客戶程序使用,瀏覽器不會用到它。
Host:初始URL中的主機和端口。
If-Modified-Since:只有當所請求的內容在指定的日期之后又經過修改才返回它,否則返回304“Not Modified”應答。
Pragma:指定“no-cache”值表示服務器必須返回一個刷新后的文檔,即使它是代理服務器而且已經有了頁面的本地拷貝。
Referer:包含一個URL,用戶從該URL代表的頁面出發訪問當前請求的頁面。
User-Agent:瀏覽器類型,如果Servlet返回的內容與瀏覽器類型有關則該值非常有用。
UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE瀏覽器所發送的非標准的請求頭,表示屏幕大小、顏色深度、操作系統和CPU類型。
請求正文
從這里可以發現,請求正文就是我們要向服務器post提交的數據。
Get請求
將ajax的請求方式換成"GET",那么get方式請求報文是怎樣的呢?
從上圖可以看出post和get請求報文的區別,post提交的數據是在請求正文中,而get提交的數據是在url中。
Http響應報文
從上圖可以看出,響應報文和請求報文非常相似,包括:狀態行、消息報文、響應正文。
在響應報文中第一行中用狀態信息代替了請求信息,狀態行(status)通過提供一個狀態嗎來說明所請求的資源情況。
狀態行的格式為:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態碼;Reason-Phrase表示狀態碼的文本描述。狀態碼由三位數字組成,第一個數字定義了響應的類別,且有5種可能取值:
-
- 1xx:指示信息——表示請求已接收,繼續處理。
- 2xx:成功——表示請求已被成功接收、理解、接受。比如200
- 3xx:重定向——要完成請求必須進行更進一步的操作。
- 4xx:客戶端錯誤——請求有語法錯誤或請求無法實現。
- 5xx:服務端錯誤——服務器未能實現合法的請求。
常見的狀態碼:
-
- 200 OK:客戶端請求成功。
- 400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解。
- 401 Unauthorized:請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用。
- 403 Forbidden:服務器收到請求,但是拒絕提供服務。
- 404 Not Found:請求資源不存在。
- 500 Internal Server Error:服務器發生不可預期的錯誤。
- 503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能回復正常。
GET和Post的區別
GET方式,請求的數據會在URL之后(就是將數據放置在http請求<request-line>中),以問號分割URL和傳輸數據,多個參數使用&連接,如果數據是英文字母/數字,原樣發送,如果是空格,轉換為+,如果是中文或其他字符,則直接將字符串用Base64加密,在url中最常見的:%E4%BD%A0%E5%A5%BD,這種東東%XX中的XX為該符號以16進制表示的ASCII。
Post方式,把提交的數據防止在http的包體<request-body>中。上文請求正文中的數據就是實際傳輸的數據。
因此,get提交的數據會在地址欄中顯示出來,而post不會。
GET:不同瀏覽器和服務器對URL長度有限制。例如IE對url的限制是2083字節。其他瀏覽器如Netscape、FireFox等,理論上沒有長度限制,其限制取決於操作系統的支持。
因此對於get提交時,傳輸數據就會收到url長度限制。
POST:由於不是通過url傳值,理論上是不受限的。但實際各個web服務器會規定對post提交數據大小進行限制。Apache、iis6都有各自的配置。
(以上參考文章:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/28/2612910.html)
get方式和post方式比較在安全性上較低,所以比較隱私性的東東一般采用post方式提交。誰也不願意將自己用戶名和密碼在url中顯示出來吧?
winform登錄模擬post方式登錄
上面參考網絡資源對http請求與響應報文又學習了一下,現在模擬post方式登錄。get方式大致相似,不再贅述。
post類
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.IO.Compression; 5 using System.Linq; 6 using System.Net; 7 using System.Text; 8 using System.Threading.Tasks; 9 10 namespace Wolfy.LoginTest 11 { 12 public class Post 13 { 14 /// <summary> 15 /// 獲得post請求后響應的數據 16 /// </summary> 17 /// <param name="postUrl">請求地址</param> 18 /// <param name="referUrl">請求引用地址</param> 19 /// <param name="data">請求帶的數據</param> 20 /// <returns>響應內容</returns> 21 public string PostLogin(string postUrl, string referUrl, string data) 22 { 23 string result = ""; 24 try 25 { 26 //命名空間System.Net下的HttpWebRequest類 27 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(postUrl); 28 //參照瀏覽器的請求報文 封裝需要的參數 這里參照ie9 29 //瀏覽器可接受的MIME類型 30 request.Accept = "text/plain, */*; q=0.01"; 31 //包含一個URL,用戶從該URL代表的頁面出發訪問當前請求的頁面 32 request.Referer = referUrl; 33 //瀏覽器類型,如果Servlet返回的內容與瀏覽器類型有關則該值非常有用 34 request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)"; 35 request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; 36 //請求方式 37 request.Method = "POST"; 38 //是否保持常連接 39 request.KeepAlive = false; 40 request.Headers.Add("Accept-Encoding", "gzip, deflate"); 41 //表示請求消息正文的長度 42 request.ContentLength = data.Length; 43 44 Stream postStream = request.GetRequestStream(); 45 byte[] postData = Encoding.UTF8.GetBytes(data); 46 //將傳輸的數據,請求正文寫入請求流 47 postStream.Write(postData, 0, postData.Length); 48 postStream.Dispose(); 49 //響應 50 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 51 //判斷響應的信息是否為壓縮信息 若為壓縮信息解壓后返回 52 if (response.ContentEncoding == "gzip") 53 { 54 MemoryStream ms = new MemoryStream(); 55 GZipStream zip = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress); 56 byte[] buffer = new byte[1024]; 57 int l = zip.Read(buffer, 0, buffer.Length); 58 while (l > 0) 59 { 60 ms.Write(buffer, 0, l); 61 l = zip.Read(buffer, 0, buffer.Length); 62 } 63 ms.Dispose(); 64 zip.Dispose(); 65 result = Encoding.UTF8.GetString(ms.ToArray()); 66 } 67 return result; 68 } 69 catch (Exception) 70 { 71 72 throw; 73 } 74 } 75 } 76 }
請求中相關屬性的值,你可以在瀏覽器,F12中,將對應的值,復制粘貼就可以,這東西不需要記。這里沒有考慮cookie的,驗證碼的情況。

測試結果:根據返回值是1(成功),2(失敗)
源碼下,猛戳這里:鏈接:http://pan.baidu.com/s/1pJ4lFBX 密碼:p6hk