寫這篇博客的起源來自於自己最近在學習ASP.NET時對於 PostBack機制的困惑。因為自己在解決困惑地同時,會不斷產生新的疑問,因此博客最后深入到了http 包的格式和Internet所使用的TCP/IP模型,算是來了一堂基礎復習課。但我相信這些基礎的牢固性,會影響到web方向的深入學習,因此整理成文,便於復習,便於探討。
寫博的時候並沒有將http協議包格式等底層的東西調整到最前面寫,因為我覺得既然我是這樣思考的,何不這樣呈現?為了便於描述,我用下圖這棵樹表示寫這篇博文的思路,IsPostBack是表面的引起疑問的葉子,順着這片葉子,可以逐漸追溯到http協議這個樹干。“Post與Get方法”那一塊被安排在樹干上,是因為從這里開始,觸及了Web開發的主體;枝葉上的ASP.NET部分,只是基於這個主體又加入自己的技術的延伸。別的技術比如JSP,Struts等Java方向的技術,也是基於這個主體的另一種技術方向的延伸,因此它們都會像樹枝一樣,從主干上發散開。
博文中有任何覺得不對的地方,歡迎在留言中和我探討。畢竟我也只是剛接觸Web不久的Fresh Man,行文之時,心下惴惴,因此歡迎指出錯誤和討論。
PostBack機制
什么是Postback?IsPostBack的作用是什么?
PostBack機制是ASP.NET特有的機制,為什么說特有,我們從web請求和響應說起。
web的基本原理就是請求和響應。以asp為例,Browser端的HTML文本,以及javascript代碼,運行后向server端發送request,server端的.asp腳本,接受request,處理后發出respond。這種server端script和client端script交互,完成一次次對於用戶操作(提交表單,載入新的URL)的響應。
以一個html文件和asp文件為例:
html代碼:
<!DOCtype html PUBLIC "-//W3C//DTD XhTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>order</title></head> <body> <h2>Form Example</h2> <p> Please input and submit <form method="POST" ACTION="Response.asp"> <p> 姓: <input NAME="fname" SIZE="48"/> <p> 名: <input NAME="lname" SIZE="48"/> <p> 稱呼: <input NAME="title" type=RADIO VALUE="先生"/>先生 <input NAME="title" type=RADIO VALUE="女士"/>女士 <p><input type=SUBMIT VALUE="提交"/><input type=RESET VALUE="清除"/> </form> </body> </html>
Response.asp腳本代碼(VB語言)
<HTML> <HEAD></HEAD> <BODY> <% Title = Request.Form("title") LastName = Request.Form("lname") If Title = "先生" Then Response.Write LastName & "先生" ElseIf Title = "女士" Then Response.Write LastName & "女士" Else Response.Write Request.Form("fname") & " " & LastName End If %> </BODY> </HTML>
可以看到HTML文件的form控件中,"action"屬性指定了form提交后處理它的Server端腳本。
而在ASP.NET系統中,我們沒有Client端和Server端腳本,我們只有aspx文件,而aspx也會被Render為HTML,在Client端通過瀏覽器顯示,因為瀏覽器只能識別HTML標簽。
下面的例子引自Artech的淺談ASP.NET的Postback,這篇文章講解了Postback的實現方式,簡單說來:Render之后的HTML會自動加入一個form,其中用hidden的input來存儲id和事件參數。在__Postback這個函數中,form中的內容會被提交。Artech所給的aspx代碼在Browser端的呈現出來的源碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> Test Page </title> </head> <body> <form name="form1" method="post" action="Default.aspx" id="form1"> <div> <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" /> <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" /> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTA0NDQ2OTE5OWRk281L4eAk7iZT10hzg+BeOyoUWBQ=" /> </div> <script type="text/javascript"> <!-- var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } </script> <div> <span id="LabelMessage" style="color:Red;"></span> </div> <div> <input type="submit" name="Button1" value="Button1" id="Button1" /> <input type="button" name="Button2" value="Button2" onclick="javascript:__doPostBack('Button2','')" id="Button2" /> <input type="button" name="Button3" value="Button3" onclick="javascript:__doPostBack('Button3','')" id="Button3" /> </div> </form> </body> </html>
以上代碼在Artech博文中也可以找到,我把它貼過來,以方便引述。這段代碼是Default.aspx被render到browser端的html源碼,Postback的實現機制就是定義了一個form,這個Form中包含隱藏的input,從而保存需要post的值,那么,向哪里post?Form中"action"屬性指定了post的目標,那這里呢?我們可以看到其值為"Default.aspx",也就是它自己。這和上面那個ASP的例子中是不同的。
我的理解是:所謂Postback,是指在ASP.NET機制中,不是Client端發post請求到server端腳本,在這個機制中,aspx被render到browser后,其Form的post目標依然是這個aspx。Postback由此得名,因為post回來了。。
因此當我們觸發網頁的按鈕時,若此按鈕涉及到后台操作(在后端有C#響應代碼,而非僅僅調用前端javascript函數),aspx頁面便會重新加載,因為Postback觸發了它。(這句話待商榷,我對Postback與page life cycle了解再深刻些后,會再編輯這句話)。
這個機制所需要解決的第一個問題是:當開發人員編寫代碼時,aspx的加載有兩種原因:點擊按鈕觸發post來讓aspx加載;用戶輸入url來加載aspx。對於不同的原因,可能開發人員希望代碼進入不同的處理邏輯。IsPostback這個ASP.NET所給的變量,就是用來給developer確定是否這個網頁是因為postback而加載,而是通過輸入url或者刷新頁面的方式來加載。
最簡單的例子,在Page_Load()方法里經常會 if(!IsPostBack) BindForm();//給表單所有控件賦值的方法。意思是提交后我就不綁定表單了,而是走Click的具體事件方法。
而IsPostBack是何時被賦值的,ASP.NET代碼中是根據什么條件來判斷是否是postback的網頁,這一個樹枝暫時還沒有研究下去,如果能有前輩能在留言中給我一些線索的話,感激不盡 :)
有關於Page_Load()方法,它是ASP.NET中的網頁載入過程中page load事件的默認響應函數,具體請參見:
ASP.NET 頁生命周期概述 以及 [ASP.NET]Page Life Cycle整理
因此Postback的機制本質實現其實是form的post,那么post和get,這些具體是什么?
POST與GET方法
正如之前所說,web實際就是request與respond的交互,那么,這些request與respond,其實就是http包。
對於一個Request,它可能不僅僅是一個要求載入頁面的request,也可能是要求發送一些數據的request,或者要求刪除服務端一些數據的request,如何區分這些request?http給request定義了四個謂詞:POST,GET,PUT,DELETE。從名字就可以看出來他們的功用,分別對應着改,查,增,刪。
而最常使用的是GET與POST。
他們的共同點和區別在哪里?
共同點是:GET POST其實都可以實現向服務器傳送數據。在HTML中,本身表單form的提交就有兩種方式,一種是get的方法,一種是post 的方法。而這兩種方法保存參數的方式是不同的,如果使用Fiddler或其他的http包查看工具,可以發現post方法提交的Form,其參數存在body中,而get方法提交的form,其參數則直接加入到url的后面。
通過包來看兩者之間本質的差別的例子,可以參見淺談 HTTP Method:表單中的 GET 與 POST 有什麼差別?
通過輸入url來訪問頁面,其request都是get。
兩者特性上的差別:form方式因為是將內容放在body中發過去,而get僅僅是發一個get請求過去,內容在url中,因此速度比post快,而安全性則低於post。
GET的response會被cache,而POST的response不會。因為GET的初衷就是獲取內容,因此可以通過cache來存儲不變的內容,而POST則是用來提交內容,其響應基於所提交的內容可能不斷變化。
兩者之間其他差別參見下圖,圖來自[HTTP]Http GET、POST Method
有一篇文章提到了在使用Ajax時,POST會比GET慢數倍,各位有興趣可以看一下:打破沙鍋-AJAX POST比GET效率差?
之前提到既然request和response實際就是HTTP包,那POST與GET這些謂詞又是如何在包中呈現的?
POST與GET報文格式
以Artech的那個例子為例,那個例子如果運行起來,會看到三個按鈕,點擊其中一個,會顯示哪個按鈕的click被fire了:
點擊Button3后,request與response在fiddler中的內容如圖:
在fiddle中,可以看到POST就出現在Request的第一行中。
Response的Header的內容是:HTTP/1.1 200 OK
一個http請求報文的格式如下圖 (圖片來自計算機網絡應用層之HTTP協議)
分為請求行(Request line),首部行(Header line),空行(Brank line)和實體(body),謂詞方法的位置就在請求行的開頭。
HTTP響應respond的報文格式如下:
其中200為狀態碼,表示請求成功。返回內容在實體中呈現,比如之前fiddler的截圖,響應包中的內容就是原先那個HTML的內容,也就是說,瀏覽器收到這個response后,用戶會看到整個頁面被刷新了一下。
我們知道了http的報文格式,那什么是HTTP協議?
HTTP協議
HTTP是HyperText Transfer Protocol即超文本傳輸協議的縮寫,是Web應用層協議之一。
在說這個之前,先提幾個HTTP的特性,這部分引用了Tank的 HTTP協議詳解 中部分內容
HTTP協議是無狀態的:同一個客戶端的這次請求和上次請求是沒有對應關系,對http服務器來說,它並不知道這兩個請求來自同一個客戶端。 為了解決這個問題, Web程序引入了Cookie機制來維護狀態。引入了ViewState來存儲控件內容或者用戶自定義信息。
關於Cookie的介紹,讀寫方式,以及它在ASP.NET form authentication中所起的作用,參見:Fish Li的細說Cookie。
關於ViewState:我的理解是一種在用戶端存儲控件內容和一些開發人員自定義內容的機制。其實現方式是在用戶端的HTML代碼中插入名字為"__ViewState"的hidden input,那些需要存儲的內容,都經過Base64編碼后以字符串的形式存儲在這個hidden input中。因為是以字符串的形式存儲在客戶端的HTML文件中,因此屬於長期儲存,不會自動過期或消失,用戶甚至可以拷貝下來保存。更多內容,參見 [.NET] ASP.NET 狀態管理(State Management):ViewState。
打開一個網頁需要瀏覽器發送很多次Request:
1. 當你在瀏覽器輸入URL http://www.cnblogs.com 的時候,瀏覽器發送一個Request去獲取 http://www.cnblogs.com 的html. 服務器把Response發送回給瀏覽器.
2. 瀏覽器分析Response中的 HTML,發現其中引用了很多其他文件,比如圖片,CSS文件,JS文件。
3. 瀏覽器會自動再次發送Request去獲取圖片,CSS文件,或者JS文件。
4. 等所有的文件都下載成功后。 網頁就被顯示出來了。
由於HTTP是使用TCP作為其運輸協議的,因此http協議也需要連接,也需要三次握手的過程。
TCP連接分為持久連接和非持久連接:
在非持久連接的情況下,服務器在發送響應后,關閉TCP連接。我們定義往返時間RTT為一個小分組從客戶機到服務器再回到客戶所花費的時間。所以RTT包括分組傳播時延、排列時延以及分組處理時延。
若http采用非持久連接時,我們可以估算出完成一次傳輸所消耗的時間:完成了三次握手的前兩部分后,客戶機將三次握手的第三部分(確認)與一個HTTP請求報文結合起來發送到該TCP連接。一旦請求報文到達服務器,服務器向該TCP連接發送HTML文件。從上面的描述,我們可以知道,對於一個非持久連接,請求一個HTTP請求/響應需要的總時間為兩個RTT+服務器傳輸HTML文件的時間,也就是兩個來回所耗的時間加上傳輸時間。
網絡分層結構
我們知道互聯網的框架主要是兩種:OSI的七層框架,和TCP/IP體系結構。而Internet網絡體系結構以TCP/IP為核心。基於TCP/IP的參考模型將協議分成四個層次。
這兩種模型的對應關系如圖,圖片來自網絡互聯參考模型(詳解)
而我們之前應用層的http報文,從最高層往下經歷層層封裝,最后到達最底層轉換為01流。發往目的地,中間會經過交換機和路由器的中轉,解包,確認下一個轉發地址后再次封包轉發。如下圖 (圖片來自網絡互聯參考模型(詳解))