## 簡介
HTTP 協議是無狀態的。換句話說,在你的各次請求之間,服務器是不會保留你的 “狀態” 信息。

每一次請求都被認為是一次全新的請求,不同的請求之間并不知道對方的存在.這種” 無狀態性 “使得 HTTP 和互聯網都是 “去中心化” 的,不會輕易被人掌控。 但也是因為這種屬性,使得 web 開發者在開發有狀態的 web 應用時十分的困難。
當我們看看我們熟悉的 web 應用,我們會覺得這些應用大都是有狀態.比如,我們登錄到 Facebook 或者 Twitter ,會看到我們的用戶名顯示在網頁上方,這表示我們的目前狀態是通過了身份驗證。如果我們在頁面上隨便點點(對服務器發起新的請求),我們并不會突然就退出登錄了。 服務器返回的響應頁面里依然有我們的用戶名顯示著,這樣看來這些應用似乎都會維持它們的狀態。
在本章,我們會通過討論一下這是怎么回事,看看 web 開發者常用的實現 “有狀態” 體驗的技術手段。同時,也會討論一些用于高效展示動態頁面信息的技術。會討論以下技術:
* 會話( session )
* Cookies
* 異步 javascript 調用( AJAX )
## 一個有狀態的應用
讓我們來看一個有狀態的應用。當你發起一個請求到`http://www.reddit.com`的時候,主頁是這樣的:

然后輸入你的用戶名和密碼進行登錄:

登錄后,在頁面上方你就會看到你的用戶名,表示你已經成功通過身份驗證。如果你刷新頁面,就會向`http://www.reddit.com`服務器發起一個新的請求,你會看到,頁面還是那個樣子,你的登錄狀態還在。這是怎么回事呢? HTTP 不是一個無狀態協議么?服務器是怎么知道你的用戶名,并動態顯示在頁面上的?哪怕刷新頁面發起新的請求也不影響你的登錄狀態。這種情況非常常見,我們都習以為常了。 這就是你的網絡購物車在你往里加新商品的時候如何保留著你之前的選擇,有時候哪怕過了幾天,你也能看到你購物車里的東西。這就是 Gmail 如何認出你,并在頁面上顯示針對你名字的歡迎信息,所有的現代 web 應用都是這樣工作的。
## 會話 ( session )
顯然,人們可以把這個無狀態的 HTTP 協議通過某種方式保持狀態。在客戶端(一般就是指瀏覽器)的幫助下,HTTP 的行為會讓人覺得它會在客戶端與服務器之間維護一個有狀態的連接,盡管實際并沒有。達到這種效果的一個辦法就是, 服務器在發送響應數據給客戶端的時候帶一個唯一的令牌(英文叫 token,就是一串數)。隨后不論何時客戶端向服務器發起請求的時候都把這個令牌附加在后面,讓服務器能夠辨識這個客戶端。在 web 開發領域我們把這個來回傳遞的令牌叫做會話標識符( session identifier )。
這種在客戶端與服務器之間傳遞`會話 id`的機制,能讓服務器創建一種各次請求之間的持續連接狀態。Web 開發人員利用這種人造的狀態,來構建復雜的應用程序。即使這樣,每一個請求嚴格上來說還是無狀態的,各次請求之間并不知道彼此的存在。
這種人造狀態,會有幾個后果。第一,必須檢查每個請求,查看它是否包含會話標識符。第二,如果請求有會話標識符,也就是有一個會話 id,服務器必須檢查每一個會話 id ,確保這些會話 id 是沒有過期的,也就是服務器需要維護一些關于如何處理會話過期,如何存儲會話數據的規則。第三,服務器要基于這個會話 id 取出這個會話的數據。最后,服務器要根據取出的會話數據重新創建應用程序的狀態( 比如,一個請求對應的 HTML ),然后將其作為響應返回給客戶端。
這就意味著服務器必須非常辛勤的工作,來模擬這個有狀態的用戶體驗。每一個請求都會有一個獨立的響應,哪怕這次的響應跟前一個響應沒有任何區別。舉個例子,如果你登錄到 Facebook 上,服務器會給你一個響應,生成你看到的主頁。這個響應是一個十分復雜的 HTML 頁面。Facebook 的服務器會把頁面上所有照片和留言的贊和評論都組合起來,然后顯示在你的時間線上。生成這樣一個頁面的成本非常高。現在,如果你點了某個照片下面的” 贊 “鏈接,理論上,Facebook 會重新生成整個頁面,它會把你贊過的照片的被贊數加 1,然后把整個 HTML 作為響應返回給你,盡管除了這個贊數以外大部分內容都沒有改變。 慶幸的是,實際中 Facebook 使用 Ajax 代替了全頁面刷新。不然的話,刷新一個頁面會花費很長時間。
服務器使用了很多先進的技術來優化會話和實現安全機制,不過這些話題都超出了本書的范圍,暫且放下。現在我們來聊一個常用的存儲會話信息的方法: 瀏覽器 cookie 。
## Cookies
cookie 就是在一個請求/響應周期內,服務器發送給客戶端(通常就是瀏覽器),并存儲在客戶端的一段數據。Cookies 或者 HTTP cookies,就是存儲在瀏覽器里包含著會話信息的小文件。默認情況下,大部分瀏覽器的 cookies 都是啟用的。當你第一次訪問一個網站的時候,服務器會給你發送會話信息并將其存儲在你本地電腦瀏覽器的 cookie 里。要注意的是真正的會話數據是存在服務器上的。在客戶端發起每一個請求的時候,服務器就會比對客戶端的 cookie 和服務器上的會話數據,用來標識當前的會話。通過這種方法,當你再次訪問同一個網站的時候,服務器就會通過 cookie 和里面的信息來認出你的會話。

我們來看一個真實的案例。用審查器看看 cookies 是如何被創建的。我們要向`http://www.yahoo.com`發起一個請求。要注意的是,如果你的瀏覽器里已經有了 Yahoo 的 cookie ,你可能需要換一個網站。
保持審查器打開(頁面上右鍵,點擊 “查看元素 “),輸入這個網址,然后看看我們的請求頭部:

注意,里面沒有任何有關 cookies 的東西,接下來我們看看響應頭部:

你會看到有個`set-cookie`頭部把 cookie 數據加到響應里。 在首次訪問這個網站的時候這個 cookie 數據會被設置。最后,我們發起一個相同的請求然后再來看看請求頭部:

你會看到有個`cookie`頭部出現了(注意這個是請求頭部,就是說這是要從你的客戶端發送到服務器的)。 里面的內容是上一個響應頭部`set-cookie`的值。這一小段數據,會出現在你每一個發起的請求里,用來唯一標識你 --- 或者說的清楚點, 標識你的客戶端,也就是你的瀏覽器。cookie 是存在瀏覽器里的。現在,就算你關掉瀏覽器,關掉電腦, cookie 里的信息也不會消失的。
現在讓我們回到本章最初的那個例子,關于 Reddit 和其他 web 應用是如何在我們發起的一個又一個請求中記住我們的登錄狀態。記住,每一個請求都是獨立的, 不知道彼此存在的。 那么問題來了,應用程序是如何 “記住” 我們的登錄狀態呢?如果你要跟著做, 保持審查器打開,然后按照下面的步驟來:
1. 點擊 resources 標簽然后訪問`http://www.reddit.com`
2. 把 cookies 那部分展開, 然后點擊`www.reddit.com`,你就能在 value 那一列看到第一次發起請求后服務器返回給我們的 cookie 了:
3. 然后登錄,你應該能看到在最后一行出現了一個唯一的會話 id 。這個會話 id 會存在你瀏覽器的 cookie 里,從此后你每一個到 Reddit.com 的請求都會附上這個會話 id 。
現在每一個請求都會包含這個會話 id ,這樣服務器就能唯一確認你這個客戶端啦。當服務器接收到一個帶有會話 id 的請求,它就會根據這個 id 去找對應的數據,在這個對應的數據里就有服務器"記住"的客戶端的狀態,或者說就是這個會話 id 的狀態。
> #### 會話數據存在哪里?
>
> 一句話:服務器上的某個地方。有時候,存在內存里,其他時候,可能會存在某個持久化存儲介質上,比如數據庫或者鍵 / 值存儲。會話數據存在哪里不是我們現在需要關心的。現在重要的是要理解會話 id 存儲在客戶端,它是訪問存儲在服務器上的會話數據的 “鑰匙”。web 應用就是這樣解決 http 無狀態這個問題的。
還有一點非常重要,在一個會話里發出的會話 id 是唯一的,而且有一個很短的過期時間。對上面的例子來說,在會話過期后你需要重新登錄。如果我們退出登錄, 會話 id 就會消失。

如果你手動刪掉會話 id 也是同樣的效果(在審查器里,右鍵 cookies 然后刪除它),這樣就退出登錄了。
簡單回顧一下,會話數據是由服務器生成并存儲在服務器上,會話 id 以 cookie 的形式發送到客戶端上。我們還看到了 web 應用程序如何充分利用這些來模擬在 web 上的有狀態體驗。
## AJAX
最后,我們來簡單看看 AJAX 和它在 HTTP 請求/響應周期里的作用.AJAX 是”異步 javascript 和 XML “ 的簡稱( Asynchronous JavaScript and XML )。它的主要特點就是允許瀏覽器發送請求和處理響應的時候不用刷新整個頁面。舉個例子,如果你登錄到 Facebook 上,服務器會給你一個響應,生成你看到的主頁。 這個響應是一個十分復雜的 HTML 頁面。Facebook 的服務器會把各種信息組合起來,顯示在你的時間線上。在前面的討論中,我們知道,為每一個請求都重新生成一次頁面的成本是非常高的(記住,你的每一個動作,點個鏈接,提交個表單,都會發起一個新的請求)。
當使用 AJAX 的時候,所有客戶端發送的請求都是異步的,就是說頁面不會刷新。舉個例子,當我們在 google 上搜索的時候:
* 訪問 Google 主頁`http://www.google.com`,然后打開審查器,看 Network 標簽,里面內容是空的。
* 當你開始搜索的時候,你會在 Network 標簽看到請求如潮水般發起。

很明顯發起了很多請求,但是你應該能注意到,頁面沒有整個刷新。 然而這個 Network 標簽的內容讓我們看清: 每敲一個字都會發起一個新的請求,也意味著你每按一下鍵都會觸發一個 AJAX 請求。這些請求的響應會通過一些回調來處理。你可以這樣理解`回調`,就是你把一些邏輯存放在某個函數里,當某個條件被觸發之后再回來執行你前面存放的邏輯。在本例中,當響應返回的時候,回調就會被觸發。你可能已經猜到了,回調函數會用新的搜索結果去更新網頁上的 HTML 。
我們不去深究回調到底是什么樣的或者如何發起一個 AJAX 請求。最主要的一點要記住的是,AJAX 請求就像是普通請求:發送到服務器的請求依然跟普通請求一樣有著一個 HTTP 請求該有的所有組成部分,并且服務器處理 AJAX 請求的方法跟處理普通請求也是一樣的。唯一不同就是,不是通過瀏覽器刷新來處理響應,而通常由客戶端的一些 javascript 代碼來處理。
## 小結
本章我們介紹了一些 web 開發者用來在無狀態的 HTTP 協議上構建有狀態應用的技術。你學習了 cookie 和會話,以及現代 web 應用如何記住客戶端的狀態。也使用審查器了解了 cookies 和會話 id 。最后,了解了 AJAX 在 web 應用里展示動態內容時所扮演的角色。