文章目錄
- 一、概述
-
- 1. Web工作方式:
- 2. HTTP協定
- 3. 位址(URL)
- 二、HTTP封包解析:
-
- 1. 請求封包格式:
- 2.響應封包格式:
- 三、Go語言HTTP程式設計:
-
- 1. 簡單的服務端:
- 2. 簡單的用戶端:
一、概述
1. Web工作方式:
對于普通的上網過程, 系統其實是這樣做的:
浏覽器本身是一個用戶端, 當輸入URL的時候, 首先浏覽器會去請求DNS伺服器, 通過DNS擷取相應的域名對應的IP, 然後通過IP位址找到IP對應的伺服器後, 要求建立TCP連接配接, 等浏覽器發送完
HTTP Request(請求)
包後, 伺服器接收到請求包之後才開始處理請求包, 伺服器調用自身服務, 傳回
HTTP Response(響應)
包; 用戶端收到來自伺服器的響應後開始渲染這個
Response
包裡的主體
(body)
, 等收到全部的内容随後斷開與該伺服器之間的TCP連接配接;
DNS域名伺服器(Domain Name Server)是進行域名(domain name)和與之相對應的IP位址轉換的伺服器; DNS中儲存了一張域名解析表;解析消息的域名;
一個Web伺服器也被稱為HTTP伺服器, 它通過HTTP(HyperText Transfer Protocol 超文本傳輸協定)協定與用戶端通信; 這個用戶端通常指的是Web浏覽器(其實手機端用戶端内部也是浏覽器實作的);
Web伺服器的工作原理可以簡單地歸納為:
- 客戶機通過
協定建立到伺服器的TCP連接配接;TCP/IP
- 用戶端向伺服器發送HTTP協定請求包, 請求伺服器裡的資源文檔;
- 伺服器向客戶機發送HTTP協定應答包, 如果請求的資源包含有動态語言的内容, 那麼伺服器會調用動态語言的解釋引擎負責處理“動态内容”, 并将處理得到的資料傳回給用戶端;
- 客戶機與伺服器斷開; 由用戶端解釋HTML文檔, 在用戶端螢幕上渲染圖形結果;
2. HTTP協定
超文本傳輸協定(HTTP, HyperText Transfer Protocol)是網際網路上應用最為廣泛的一種網絡協定, 它詳細規定了浏覽器和網際網路伺服器之間互相通信的規則, 通過網際網路傳送網際網路文檔的資料傳送協定;
HTTP協定通常承載于TCP協定之上, 有時也承載于TLS或SSL協定層之上, 這個時候, 就成了HTTPS; 如下圖所示:
3. 位址(URL)
URL全稱為
Unique Resource Location
, 用來表示網絡資源, 可以了解為網絡檔案路徑;
基本URL的結構包含模式(協定)、伺服器名稱(IP位址)、路徑和檔案名; 常見的協定/模式如
http、https、ftp
等; 伺服器的名稱或IP位址後面有時還跟一個冒号和一個端口号; 再後面是到達這個檔案的路徑和檔案本身的名稱; 如:
http://localhost[":"port][abs_path]
http://192.168.31.1/html/index
https://pan.baidu.com/
URL的長度有限制, 不同的伺服器的限制值不太相同, 但是不能無限長;
二、HTTP封包解析:
1. 請求封包格式:
- 請求行:
- 請求行由方法字段、URL 字段 和HTTP 協定版本字段 3個部分組成, 他們之間使用空格隔開;
- 請求頭部
-
請求頭部為請求封包添加了一些附加資訊, 由名/值對組成, 每行一對, 名和值之間使用冒号分隔; 請求頭部通知伺服器有關于用戶端請求的資訊, 典型的請求頭有:
|請求頭 |含義|
|-|-|
|User-Agent |請求的浏覽器類型|
|Accept| 用戶端可識别的響應内容類型清單, 星号“ * ”用于按範圍将類型分組, 用“ / ”訓示可接受全部類型, 用“ type/* ”訓示可接受
type
類型的所有子類型|
|Accept-Language |用戶端可接受的自然語言|
|Accept-Encoding| 用戶端可接受的編碼壓縮格式
|Accept-Charset |可接受的應答的字元集|
|Host |請求的主機名, 允許多個域名同處一個IP 位址, 即虛拟主機|
|connection |連接配接方式(close或keepalive)|
|Cookie| 存儲于用戶端擴充字段,向同一域名的服務端發送屬于該域的cookie|
-
- 空行
- 最後一個請求頭之後是一個空行, 發送回車符和換行符, 通知伺服器以下不再有請求頭;
- 請求包體
- 請求包體不在
方法中使用, 而在GET
方法中使用;POST
方法适用于需要客戶填寫表單的場合了; 與請求包體相關的最常使用的是包體類型POST
和包體長度Content-Type
;Content-Length
- 請求包體不在
2.響應封包格式:
http的成功響應封包:
http的失敗響應封包:
HTTP 響應封包由
狀态行、響應頭部、空行、響應包體
4個部分組成, 如下圖所示:
- 狀态行:
- 狀态行由
3個部分組成, 他們之間使用空格隔開;HTTP 協定版本字段、狀态碼和狀态碼的描述文本
- 狀态碼: 狀态碼由三位數字組成, 第一位數字表示響應的類型, 常用的狀态碼有五大類如下所示:
- 狀态行由
狀态碼 | 含義 |
---|---|
1xx | 表示伺服器已接收了用戶端請求,用戶端可繼續發送請求 |
2xx | 表示伺服器已成功接收到請求并進行處理 |
3xx | 表示伺服器要求用戶端重定向 |
4xx | 表示用戶端的請求有非法内容 |
5xx | 表示伺服器未能正常處理用戶端的請求而出現意外錯誤 |
常見的狀态碼舉例:
狀态碼 | 含義 |
---|---|
200 OK | 用戶端請求成功 |
400 Bad Request | 請求封包有文法錯誤 |
401 Unauthorized | 未授權 |
403 Forbidden | 伺服器拒絕服務 |
404 Not Found | 請求的資源不存在 |
500 Internal Server Error | 伺服器内部錯誤 |
503 Server Unavailable | 伺服器臨時不能處理用戶端請求 |
- 響應頭部
- 響應頭可能包括:
響應頭 | 含義 |
---|---|
Location | Location響應報頭域用于重定向接受者到一個新的位置 |
Server | Server 響應報頭域包含了伺服器用來處理請求的軟體資訊及其版本 |
Vary | 訓示不可緩存的請求頭清單 |
Connection | 連接配接方式 |
- 空行:
- 最後一個響應頭部之後是一個空行, 發送回車符和換行符, 通知伺服器以下不再有響應頭部;
- 響應包體:
- 伺服器傳回給用戶端的文本資訊;
三、Go語言HTTP程式設計:
Go語言标準庫内建提供了
net/http
包, 涵蓋了HTTP用戶端和服務端的具體實作; 使用
net/http
包, 可以很友善地編寫HTTP用戶端或服務端的程式:
1. 簡單的服務端:
package main
import (
"fmt"
"net/http"
)
//服務端編寫的業務邏輯處理程式 —— 回調函數
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("method = ", r.Method) //請求方法
fmt.Println("URL = ", r.URL) // 浏覽器發送請求檔案路徑
fmt.Println("header = ", r.Header) // 請求頭
fmt.Println("body = ", r.Body) // 請求包體
fmt.Println(r.RemoteAddr, "連接配接成功") //用戶端網絡位址
w.Write([]byte("hello http")) //給用戶端回複資料
}
func main() {
http.HandleFunc("/hello", myHandler) // 注冊處理函數
//該方法用于在指定的 TCP 網絡位址 addr 進行監聽,然後調用服務端處理程式來處理傳入的連接配接請求。
//該方法有兩個參數:第一個參數 addr 即監聽位址;第二個參數表示服務端處理程式,通常為nil
//當參2為nil時,服務端調用 http.DefaultServeMux 進行處理
http.ListenAndServe("127.0.0.1:8000", nil)
}
浏覽器輸入url位址:
127.0.0.1:8000/hello
回調函數
myHandler
的函數原型固定;
func myHandler(w http.ResponseWriter, r *http.Request)
有兩個參數:
-
和w http.ResponseWriter
;r *http.Request
- w用來“給用戶端回發資料”, 它是一個interface:
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
- r 用來“接收用戶端發送的資料”; 浏覽器發送給伺服器的http請求包的内容可以借助r來檢視, 它對應一個結構體:
type Request struct {
Method string // 浏覽器請求方法 GET、POST…
URL *url.URL // 浏覽器請求的通路路徑
……
Header Header // 請求頭部
Body io.ReadCloser // 請求包體
RemoteAddr string // 浏覽器位址
……
ctx context.Context
}
檢視一下結構體成員:
fmt.Println("Method = ", r.Method)
fmt.Println("URL = ", r.URL)
fmt.Println("Header = ", r.Header)
fmt.Println("Body = ", r.Body)
fmt.Println(r.RemoteAddr, "連接配接成功")
2. 簡單的用戶端:
用戶端通路web伺服器資料, 主要使用
func Get(url string) (resp *Response, err error)
函數來完成; 讀到的響應封包資料被儲存在
Response
結構體中:
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Proto string // e.g. "HTTP/1.0"
……
Header Header
Body io.ReadCloser
……
}
伺服器發送的響應包體被儲存在Body中; 可以使用它提供的Read方法來擷取資料内容; 儲存至切片緩沖區中, 拼接成一個完整的字元串來檢視;
結束的時候, 需要調用Body中的
Close()
方法關閉io;
package main
import (
"net/http"
"fmt"
)
func main() {
// 使用Get方法擷取伺服器響應包資料
//resp, err := http.Get("http://www.baidu.com")
resp, err := http.Get("http://127.0.0.1:8000/hello")
if err != nil {
fmt.Println("Get err:", err)
return
}
defer resp.Body.Close()
// 擷取伺服器端讀到的資料
fmt.Println("Status = ", resp.Status) // 狀态
fmt.Println("StatusCode = ", resp.StatusCode) // 狀态碼
fmt.Println("Header = ", resp.Header) // 響應頭部
fmt.Println("Body = ", resp.Body) // 響應包體
buf := make([]byte, 4096) // 定義切片緩沖區,存讀到的内容
var result string
// 擷取伺服器發送的資料包内容
for {
n, err := resp.Body.Read(buf) // 讀body中的内容。
if n == 0 {
fmt.Println("Body.Read err:", err)
break
}
result += string(buf[:n]) // 累加讀到的資料内容
}
// 列印從body中讀到的所有内容
fmt.Println("result = ", result)
}