我們平時瀏覽網頁的時候,會打開瀏覽器,輸入網址后按下回車鍵,然后就會顯示出你想要瀏覽的內容。在這個看似簡單的用戶行為背后,到底隱藏了些什么呢?
對于普通的上網過程,系統其實是這樣做的:瀏覽器本身是一個客戶端,當你輸入URL的時候,首先瀏覽器會去請求DNS服務器,通過DNS獲取相應的域名對應的IP,然后通過IP地址找到IP對應的服務器后,要求建立TCP連接,等瀏覽器發送完HTTP Request(請求)包后,服務器接收到請求包之后才開始處理請求包,服務器調用自身服務,返回HTTP Response(響應)包;客戶端收到來自服務器的響應后開始渲染這個Response包里的主體(body),等收到全部的內容隨后斷開與該服務器之間的TCP連接。
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.web2.png?raw=true)
圖3.1 用戶訪問一個Web站點的過程
一個Web服務器也被稱為HTTP服務器,它通過HTTP協議與客戶端通信。這個客戶端通常指的是Web瀏覽器(其實手機端客戶端內部也是瀏覽器實現的)。
Web服務器的工作原理可以簡單地歸納為:
* 客戶機通過TCP/IP協議建立到服務器的TCP連接
* 客戶端向服務器發送HTTP協議請求包,請求服務器里的資源文檔
* 服務器向客戶機發送HTTP協議應答包,如果請求的資源包含有動態語言的內容,那么服務器會調用動態語言的解釋引擎負責處理“動態內容”,并將處理得到的數據返回給客戶端
* 客戶機與服務器斷開。由客戶端解釋HTML文檔,在客戶端屏幕上渲染圖形結果
一個簡單的HTTP事務就是這樣實現的,看起來很復雜,原理其實是挺簡單的。需要注意的是客戶機與服務器之間的通信是非持久連接的,也就是當服務器發送了應答后就與客戶機斷開連接,等待下一次請求。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#url和dns解析)URL和DNS解析
我們瀏覽網頁都是通過URL訪問的,那么URL到底是怎么樣的呢?
URL(Uniform Resource Locator)是“統一資源定位符”的英文縮寫,用于描述一個網絡上的資源, 基本格式如下
~~~
scheme://host[:port#]/path/.../[?query-string][#anchor]
scheme 指定低層使用的協議(例如:http, https, ftp)
host HTTP服務器的IP地址或者域名
port# HTTP服務器的默認端口是80,這種情況下端口號可以省略。如果使用了別的端口,必須指明,例如 http://www.cnblogs.com:8080/
path 訪問資源的路徑
query-string 發送給http服務器的數據
anchor 錨
~~~
DNS(Domain Name System)是“域名系統”的英文縮寫,是一種組織成域層次結構的計算機和網絡服務命名系統,它用于TCP/IP網絡,它從事將主機名或域名轉換為實際IP地址的工作。DNS就是這樣的一位“翻譯官”,它的基本工作原理可用下圖來表示。
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.dns_hierachy.png?raw=true)
圖3.2 DNS工作原理
更詳細的DNS解析的過程如下,這個過程有助于我們理解DNS的工作模式
1. 在瀏覽器中輸入www.qq.com域名,操作系統會先檢查自己本地的hosts文件是否有這個網址映射關系,如果有,就先調用這個IP地址映射,完成域名解析。
2. 如果hosts里沒有這個域名的映射,則查找本地DNS解析器緩存,是否有這個網址映射關系,如果有,直接返回,完成域名解析。
3. 如果hosts與本地DNS解析器緩存都沒有相應的網址映射關系,首先會找TCP/IP參數中設置的首選DNS服務器,在此我們叫它本地DNS服務器,此服務器收到查詢時,如果要查詢的域名,包含在本地配置區域資源中,則返回解析結果給客戶機,完成域名解析,此解析具有權威性。
4. 如果要查詢的域名,不由本地DNS服務器區域解析,但該服務器已緩存了此網址映射關系,則調用這個IP地址映射,完成域名解析,此解析不具有權威性。
5. 如果本地DNS服務器本地區域文件與緩存解析都失效,則根據本地DNS服務器的設置(是否設置轉發器)進行查詢,如果未用轉發模式,本地DNS就把請求發至 “根DNS服務器”,“根DNS服務器”收到請求后會判斷這個域名(.com)是誰來授權管理,并會返回一個負責該頂級域名服務器的一個IP。本地DNS服務器收到IP信息后,將會聯系負責.com域的這臺服務器。這臺負責.com域的服務器收到請求后,如果自己無法解析,它就會找一個管理.com域的下一級DNS服務器地址(qq.com)給本地DNS服務器。當本地DNS服務器收到這個地址后,就會找qq.com域服務器,重復上面的動作,進行查詢,直至找到www.qq.com主機。
6. 如果用的是轉發模式,此DNS服務器就會把請求轉發至上一級DNS服務器,由上一級服務器進行解析,上一級服務器如果不能解析,或找根DNS或把轉請求轉至上上級,以此循環。不管是本地DNS服務器用是是轉發,還是根提示,最后都是把結果返回給本地DNS服務器,由此DNS服務器再返回給客戶機。
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.dns_inquery.png?raw=true)
圖3.3 DNS解析的整個流程
> 所謂?`遞歸查詢過程`?就是 “查詢的遞交者” 更替, 而?`迭代查詢過程`?則是 “查詢的遞交者”不變。
>
> 舉個例子來說,你想知道某個一起上法律課的女孩的電話,并且你偷偷拍了她的照片,回到寢室告訴一個很仗義的哥們兒,這個哥們兒二話沒說,拍著胸脯告訴你,甭急,我替你查(此處完成了一次遞歸查詢,即,問詢者的角色更替)。然后他拿著照片問了學院大四學長,學長告訴他,這姑娘是xx系的;然后這哥們兒馬不停蹄又問了xx系的辦公室主任助理同學,助理同學說是xx系yy班的,然后很仗義的哥們兒去xx系yy班的班長那里取到了該女孩兒電話。(此處完成若干次迭代查詢,即,問詢者角色不變,但反復更替問詢對象)最后,他把號碼交到了你手里。完成整個查詢過程。
通過上面的步驟,我們最后獲取的是IP地址,也就是瀏覽器最后發起請求的時候是基于IP來和服務器做信息交互的。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#http協議詳解)HTTP協議詳解
HTTP協議是Web工作的核心,所以要了解清楚Web的工作方式就需要詳細的了解清楚HTTP是怎么樣工作的。
HTTP是一種讓Web服務器與瀏覽器(客戶端)通過Internet發送與接收數據的協議,它建立在TCP協議之上,一般采用TCP的80端口。它是一個請求、響應協議--客戶端發出一個請求,服務器響應這個請求。在HTTP中,客戶端總是通過建立一個連接與發送一個HTTP請求來發起一個事務。服務器不能主動去與客戶端聯系,也不能給客戶端發出一個回調連接。客戶端與服務器端都可以提前中斷一個連接。例如,當瀏覽器下載一個文件時,你可以通過點擊“停止”鍵來中斷文件的下載,關閉與服務器的HTTP連接。
HTTP協議是無狀態的,同一個客戶端的這次請求和上次請求是沒有對應關系,對HTTP服務器來說,它并不知道這兩個請求是否來自同一個客戶端。為了解決這個問題, Web程序引入了Cookie機制來維護連接的可持續狀態。
> HTTP協議是建立在TCP協議之上的,因此TCP攻擊一樣會影響HTTP的通訊,例如比較常見的一些攻擊:SYN Flood是當前最流行的DoS(拒絕服務攻擊)與DdoS(分布式拒絕服務攻擊)的方式之一,這是一種利用TCP協議缺陷,發送大量偽造的TCP連接請求,從而使得被攻擊方資源耗盡(CPU滿負荷或內存不足)的攻擊方式。
### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#http請求包瀏覽器信息)HTTP請求包(瀏覽器信息)
我們先來看看Request包的結構, Request包分為3部分,第一部分叫Request line(請求行), 第二部分叫Request header(請求頭),第三部分是body(主體)。header和body之間有個空行,請求包的例子所示:
~~~
GET /domains/example/ HTTP/1.1 //請求行: 請求方法 請求URI HTTP協議/協議版本
Host:www.iana.org //服務端的主機名
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //瀏覽器信息
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客戶端能接收的mine
Accept-Encoding:gzip,deflate,sdch //是否支持流壓縮
Accept-Charset:UTF-8,*;q=0.5 //客戶端字符編碼集
//空行,用于分割請求頭和消息體
//消息體,請求資源參數,例如POST傳遞的參數
~~~
HTTP協議定義了很多與服務器交互的請求方法,最基本的有4種,分別是GET,POST,PUT,DELETE。一個URL地址用于描述一個網絡上的資源,而HTTP中的GET, POST, PUT, DELETE就對應著對這個資源的查,改,增,刪4個操作。我們最常見的就是GET和POST了。GET一般用于獲取/查詢資源信息,而POST一般用于更新資源信息。
通過fiddler抓包可以看到如下請求信息:
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.http.png?raw=true)
圖3.4 fiddler抓取的GET信息
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.httpPOST.png?raw=true)
圖3.5 fiddler抓取的POST信息
我們看看GET和POST的區別:
1. 我們可以看到GET請求消息體為空,POST請求帶有消息體。
2. GET提交的數據會放在URL之后,以`?`分割URL和傳輸數據,參數之間以`&`相連,如`EditPosts.aspx?name=test1&id=123456`。POST方法是把提交的數據放在HTTP包的body中。
3. GET提交的數據大小有限制(因為瀏覽器對URL的長度有限制),而POST方法提交的數據沒有限制。
4. GET方式提交數據,會帶來安全問題,比如一個登錄頁面,通過GET方式提交數據時,用戶名和密碼將出現在URL上,如果頁面可以被緩存或者其他人可以訪問這臺機器,就可以從歷史記錄獲得該用戶的賬號和密碼。
### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#http響應包服務器信息)HTTP響應包(服務器信息)
我們再來看看HTTP的response包,他的結構如下:
~~~
HTTP/1.1 200 OK //狀態行
Server: nginx/1.0.8 //服務器使用的WEB軟件名及版本
Date:Date: Tue, 30 Oct 2012 04:14:25 GMT //發送時間
Content-Type: text/html //服務器發送信息的類型
Transfer-Encoding: chunked //表示發送HTTP包是分段發的
Connection: keep-alive //保持連接狀態
Content-Length: 90 //主體內容長度
//空行 用來分割消息頭和主體
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... //消息體
~~~
Response包中的第一行叫做狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成。
狀態碼用來告訴HTTP客戶端,HTTP服務器是否產生了預期的Response。HTTP/1.1協議中定義了5類狀態碼, 狀態碼由三位數字組成,第一個數字定義了響應的類別
* 1XX 提示信息 - 表示請求已被成功接收,繼續處理
* 2XX 成功 - 表示請求已被成功接收,理解,接受
* 3XX 重定向 - 要完成請求必須進行更進一步的處理
* 4XX 客戶端錯誤 - 請求有語法錯誤或請求無法實現
* 5XX 服務器端錯誤 - 服務器未能實現合法的請求
我們看下面這個圖展示了詳細的返回信息,左邊可以看到有很多的資源返回碼,200是常用的,表示正常信息,302表示跳轉。response header里面展示了詳細的信息。
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.response.png?raw=true)
圖3.6 訪問一次網站的全部請求信息
### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#http協議是無狀態的和connection-keep-alive的區別)HTTP協議是無狀態的和Connection: keep-alive的區別
無狀態是指協議對于事務處理沒有記憶能力,服務器不知道客戶端是什么狀態。從另一方面講,打開一個服務器上的網頁和你之前打開這個服務器上的網頁之間沒有任何聯系。
HTTP是一個無狀態的面向連接的協議,無狀態不代表HTTP不能保持TCP連接,更不能代表HTTP使用的是UDP協議(面對無連接)。
從HTTP/1.1起,默認都開啟了Keep-Alive保持連接特性,簡單地說,當一個網頁打開完成后,客戶端和服務器之間用于傳輸HTTP數據的TCP連接不會關閉,如果客戶端再次訪問這個服務器上的網頁,會繼續使用這一條已經建立的TCP連接。
Keep-Alive不會永久保持連接,它有一個保持時間,可以在不同服務器軟件(如Apache)中設置這個時間。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.1.md#請求實例)請求實例
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.1.web.png?raw=true)
圖3.7 一次請求的request和response
上面這張圖我們可以了解到整個的通訊過程,同時細心的讀者是否注意到了一點,一個URL請求但是左邊欄里面為什么會有那么多的資源請求(這些都是靜態文件,go對于靜態文件有專門的處理方式)。
這個就是瀏覽器的一個功能,第一次請求url,服務器端返回的是html頁面,然后瀏覽器開始渲染HTML:當解析到HTML DOM里面的圖片連接,css腳本和js腳本的鏈接,瀏覽器就會自動發起一個請求靜態資源的HTTP請求,獲取相對應的靜態資源,然后瀏覽器就會渲染出來,最終將所有資源整合、渲染,完整展現在我們面前的屏幕上。
> 網頁優化方面有一項措施是減少HTTP請求次數,就是把盡量多的css和js資源合并在一起,目的是盡量減少網頁請求靜態資源的次數,提高網頁加載速度,同時減緩服務器的壓力。
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料