大家好 我是寸鐵👊
總結了一篇Go Web服務器(go net/http) 處理Get、Post請求的文章?
喜歡的小伙伴可以點點關注 💝
前言
go http
請求如何編寫簡單的函數去拿到前端的請求(Get和Post) 服務器(后端)接收到請求后,又是怎么處理請求,再把響應返回給客戶端?
操作步驟:
Step1:注冊監聽和服務的端口
一開始,我們需要注冊監聽和服務的端口
我們需要調用go中提供的net/http
這個函數包的ListenAndServe
函數
方式一:
http.ListenAndServe(":8080", nil)
注意:這里:
號要帶上,不能只寫8080
(踩坑經歷)
等價于
http.ListenAndServe("127.0.0.1:8080", nil)
方式二:
考慮到很多時候,可能并不是直接訪問本機127.0.0.1:8080
端口,這里可以寫具體地址:端口號
(其實就是一個Socket)
如:
http.ListenAndServe("192.168.0.1:3306", nil)
在知道怎么操作后,我們來了解一下底層源碼具體是怎么實現?了解后,便于更清楚的知道整體的流程!
ListenAndServe()
函數有兩個參數,當前監聽的端口號和事件處理器Handler。
事件處理器的Handler接口定義如下:
type Handler interface {ServeHTTP(ResponseWriter, *Request)}
Handler
接口:Handler
是一個接口類型,聲明了一個方法ServeHTTP
。這個接口規定了任何實現了ServeHTTP
方法的類型都可以被視為一個 HTTP 請求處理器。
ServeHTTP
方法:ServeHTTP
方法有兩個參數,分別是ResponseWriter
和*Request
。
ResponseWriter
接口用于構建HTTP 響應
。它提供了一系列方法,允許你設置響應的狀態碼、頭部信息以及響應主體內容。*Request
類型表示 HTTP 請求。它包含了所有關于客戶端請求
的信息,比如請求方法、請求頭、URL 等。PS:實現
Handle
接口不用寫ServeHttp
方法,會比較方便。后面的代碼都是用的封裝好的
handler
處理器,幫我們寫好ResponseWriter
和*Request
,只需專注業務邏輯即可。后續分析這兩個參數的包含的具體信息
只要實現了這個接口,就可以實現自己的handler
處理器。Go語言在net/http
包中已經實現了這個接口的公共方法:
type HandlerFunc func(ResponseWriter, *Request)?// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)}
如果ListenAndServe()
傳入的第一個參數地址為空,則服務器在啟動后默認使用http://localhost:8080
地址進行訪問;
如果這個函數傳入的第二個參數為nil
,則服務器在啟動后將使用默認的多路復用器DefaultServeMux
。
再來分析一下內層的server
代碼
server結構體
server := &Server{Addr: addr, Handler: handler}
這是一種原生的寫法,我們也可以自己根據這段代碼來對server
進行更詳細的定義。如下:
s := &http.Server{Addr: ":8081", //地址Handler: myHandler,//處理器ReadTimeout: 20 * time.Second, //讀的超時設置 WriteTimeout: 20 * time.Second, //寫的超時設置MaxHeaderBytes: 1 << 10, //傳輸的最大頭部字節}
net/http.Server
是 HTTP
服務器的主要結構體,用于控制 HTTP
服務器的行為。
其結構體定義為:
type Server struct { //服務器監聽的地址和端口號,格式為 "host:port",例如 "127.0.0.1:8080"Addr string //HTTP 請求的處理器。對于每個收到的請求,服務器會將其路由到對應的處理器進行處理。通常使用 http.NewServeMux() 方法創建一個默認的多路復用器,并將其作為處理器。如果沒有設置該字段,則使用 DefaultServeMuxHandler Handler //一個布爾值,用于指示是否禁用 OPTIONS 方法的默認實現。如果該值為 true,則在收到 OPTIONS 請求時,服務器不會自動返回 Allow 頭部,而是交給用戶自行處理。默認為 false,即啟用 OPTIONS 方法的默認實現DisableGeneralOptionsHandler bool //HTTPS 服務器的 TLS 配置,用于控制 HTTPS 服務器的加密方式、證書、密鑰等安全相關的參數TLSConfig *tls.Config //HTTP 請求的讀取超時時間。如果服務器在該時間內沒有讀取到完整的請求,就會關閉連接。該字段為 time.Duration 類型,默認為 0,表示沒有超時限制ReadTimeout time.Duration //HTTP 請求頭部讀取超時時間。如果服務器在該時間內沒有完成頭部讀取,就會關閉連接。該字段為 time.Duration 類型,默認為 0,表示沒有超時限制ReadHeaderTimeout time.Duration //HTTP 響應的寫入超時時間。如果服務器在該時間內沒有完成對響應的寫入操作,就會關閉連接。該字段為 time.Duration 類型,默認為 0,表示沒有超時限制WriteTimeout time.Duration //HTTP 連接的空閑超時時間。如果服務器在該時間內沒有收到客戶端的請求,就會關閉連接。該字段為 time.Duration 類型,默認為 0,表示沒有超時限制IdleTimeout time.Duration //HTTP 請求頭部的最大大小。如果請求頭部的大小超過該值,服務器就會關閉連接。該字段為 int 類型,默認為 1 << 20(1MB)MaxHeaderBytes intTLSNextProto map[string]func(*Server, *tls.Conn, Handler) //連接狀態變化的回調函數,用于處理連接的打開、關閉等事件ConnState func(net.Conn, ConnState) //錯誤日志的輸出目標。如果該字段為 nil,則使用 log.New(os.Stderr, "", log.LstdFlags) 創建一個默認的日志輸出目標ErrorLog *log.Logger //所有 HTTP 請求的基礎上下文。當處理器函數被調用時,會將請求的上下文從基礎上下文派生出來。默認為 context.Background()。BaseContext func(net.Listener) context.Context //連接上下文的回調函數,用于創建連接上下文。每個連接上下文都與一個客戶端連接相關聯。如果未設置該字段,則每個連接的上下文都是 BaseContext 的副本ConnContext func(ctx context.Context, c net.Conn) context.Context //標志變量,用于表示服務器是否正在關閉。該變量在執行 Shutdown 方法時被設置為 true,用于避免新的連接被接受inShutdown atomic.Bool //標志變量,用于控制服務器是否支持 HTTP keep-alive。如果該變量為 true,則服務器在每次響應完成后都會關閉連接,即不支持 keep-alive。如果該變量為 false,則服務器會根據請求頭部中的 Connection 字段來決定是否支持 keep-alive。該變量在執行 Shutdown 方法時被設置為 true,用于關閉正在進行的disableKeepAlives atomic.Bool // 一個 sync.Once 類型的值,用于確保在多線程環境下,NextProtoOnce 方法只被調用一次。NextProtoOnce 方法用于設置 Server.NextProto 字段nextProtoOnce sync.Once // error 類型的值,用于記錄 NextProto 方法的調用結果。該值在多個 goroutine 之間共享,用于檢測 NextProto 方法是否成功nextProtoErr error //互斥鎖,用于保護 Server 結構體的字段。因為 Server 結構體可能被多個 goroutine 并發訪問,所以需要使用互斥鎖來確保它們的安全性mu sync.Mutex //存儲 HTTP 或 HTTPS 監聽器的列表。每個監聽器都是一個 net.Listener 接口類型的實例,用于接收客戶端請求。當調用 Server.ListenAndServe() 或 Server.ListenAndServeTLS() 方法時,會為每個監聽地址創建一個對應的監聽器,并將其添加到該列表中listeners map[*net.Listener]struct{} //表示當前處于活動狀態的客戶端連接的數量。該字段只是一個計數器,并不保證一定準確。該字段用于判斷服務器是否處于繁忙狀態,以及是否需要動態調整服務器的工作負載等activeConn map[*conn]struct{} //在服務器關閉時執行的回調函數列表。當服務器調用 Server.Shutdown() 方法時,會依次執行該列表中的每個回調函數,并等待它們全部執行完畢。該字段可以用于在服務器關閉時釋放資源、保存數據等操作onShutdown []func() //表示所有監聽器的組。該字段包含一個讀寫互斥鎖 sync.RWMutex 和一個映射表 map[interface{}]struct{}。在監聽器啟動時,會將監聽器地址作為鍵添加到映射表中。該字段主要用于實現優雅地關閉服務器。在服務器關閉時,會遍歷所有監聽器,逐個關閉它們,并等待所有連接關閉。如果在等待連接關閉時,有新的連接進來,服務器會先將新連接添加到 activeConn 字段中,并等待所有連接關閉后再退出。這樣可以保證服務器在關閉過程中,不會丟失任何連接listenerGroup sync.WaitGroup }
Step2.1: 調用HandleFunc
函數處理get請求
HandleFunc
函數
在上面的代碼的基礎上,開始調用常用的處理請求的函數HandleFunc
函數
在調用前,不妨先了解一下HandleFunc
函數的作用是什么?
HandleFunc
函數的作用是創建一個處理器
并將其注冊到指定的路徑上
。這個處理器會調用提供的函數
來處理請求
。這種方式非常方便,因為你可以直接使用一個函數來處理請求,而不必實現完整的http.Handler
接口。
有了上面的基礎后,編寫一個案例摸清HandleFunc
函數。
測試案例
package mainimport ("fmt""net/http"
)func helloHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "Hello, World!")
}func main() {http.ListenAndServe(":8080", nil)http.HandleFunc("/hello", helloHandler)
}
輸出結果如下:
分析一下這段代碼
在這個例子中
helloHandler
函數處理/hello
路徑上的請求。通過http.HandleFunc
將這個函數helloHandler
注冊到指定的路徑,然后使用http.ListenAndServe
啟動 HTTP 服務器。當有請求訪問
/hello
路徑時,將調用helloHandler
函數來處理請求。
有了HandleFunc
函數的基礎后,下面正式進入處理客戶端發送過來的get
請求
處理客戶端的get請求
運行效果及逐行分析
代碼
package mainimport ("fmt""io""net/http"
)func main() {//get請求http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {query := request.URL.Query() //接收到客戶端發送的get請求的URLfmt.Print(request.URL.Path) //后端顯示打印很多相關的內容//io包下提供的寫方法,將服務端get到的id和name以字符串的形式寫到客戶端顯示io.WriteString(writer, "query:"+query.Get("id")+query.Get("name"))})http.ListenAndServe(":8080", nil)}
運行上面這段代碼后,我們來看一下運行的結果,從結果去不斷梳理整個處理的過程。
運行結果如下
分析運行結果
逐行逐行詳解:
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request)
- 這一行代碼使用
http.HandleFunc
函數將一個匿名函數注冊到根路徑("/")
上,也就是處理默認的請求。這個匿名函數接受兩個參數,writer
是用于寫入響應的http.ResponseWriter
接口,而request
是表示客戶端請求的http.Request
結構。query := request.URL.Query()
- 通過
request.URL.Query()
獲取請求的 URL 中的查詢參數
,并將其存儲在query
變量中。查詢參數通常是在 URL 中以?
后面的鍵值對形式出現的,例如:http://localhost:8080/?id=123&name=John
。fmt.Print(request.URL.Path)
- 在控制臺上查看請求的路徑信息
io.WriteString(writer, "query:"+query.Get("id")+query.Get("name"))
- 使用
io.WriteString
將包含查詢參數的字符串寫入響應
。它會將 “query:” 后面連接著id
和name
參數的值。總結:
當有請求訪問根路徑
("/")
時,打印請求的路徑并返回包含查詢參數的字符串響應。
request的api的具體信息
在演示了基本的get
請求結果后,服務端往客戶端返回的響應(寫一句話)
下面開始在控制臺輸出request
其他相關的具體信息
先來看一下可以調用request
的哪些具體屬性(字段)
request
可調出的屬性比較多,每個屬性中對應的api方法也比較多。
這里演示常用的api,需要獲取request的具體api方法可以直接查。
演示結果如下:
demo
package mainimport ("fmt""io""net/http"
)func main() {//get請求http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {query := request.URL.Query()//設置request請求頭部的字段 如設置name為hellofmt.Println("request.URL.Path內容:", request.URL.Path) //可以打印很多相關的內容fmt.Println("request.URL.Host內容:", request.URL.Host) //可以打印很多相關的內容fmt.Println("request.URL.Fragment內容:", request.URL.Fragment)fmt.Println("request.URL.Scheme內容:", request.URL.Scheme)fmt.Println("request.URL.User內容:", request.URL.User)fmt.Println("request.Header內容:", request.Header)fmt.Println("request.Body內容:", request.Body)fmt.Println("request.ContentLength內容為:", request.ContentLength)fmt.Println("request.Method內容為:", request.Method)fmt.Println("request.Close內容為:", request.Close)io.WriteString(writer, "query:"+query.Get("id")+query.Get("name"))})http.ListenAndServe(":8080", nil)}
處理器writer和request的具體信息
在前面的應用基礎上,下面正式分析處理器的writer
和request
的來源和相關信息
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request)
- 先看
writer http.ResponseWriter
http.ResponseWriter
是一個接口類型:
// net/http/server.go
type ResponseWriter interface {Header() HeaderWrite([]byte) (int, error)WriteHeader(statusCode int)
}
用于向客戶端發送響應,實現了
ResponseWriter
接口的類型顯然也實現了io.Writer
接口。所以在處理函數index
中,可以調用fmt.Fprintln()
向ResponseWriter
寫入響應信息。這也很好的解釋了前面代碼調用
io.writeString
方法將返回的響應信息寫入實現了ResponseWriter
接口的對象writer
,再調用方法返回響應信息。io.WriteString(writer, "query:"+query.Get("id")+query.Get("name"))
代碼剖析
定義了一個接口 ResponseWriter,該接口規定了用于寫入 HTTP 響應的方法。
接口中的每個方法如下:
Header() Header:
Header() 方法返回一個 Header 類型的值。Header 是一個映射(map),用于表示 HTTP 響應頭。響應頭包括一系列的鍵值對,每個鍵值對表示一個響應頭字段和它的值。
Write([]byte) (int, error):
Write 方法接受一個字節切片([]byte)作為參數,并返回寫入的字節數和可能的錯誤。這個方法用于將字節數據寫入 HTTP 響應體。
WriteHeader(statusCode int):
WriteHeader 方法接受一個整數參數 statusCode,用于設置 HTTP 響應的狀態碼。HTTP 狀態碼是一個三位數的代碼,用于表示服務器對請求的處理結果。例如,
200
表示成功,404
表示資源未找到,500
表示服務器內部錯誤等。編寫一個 HTTP 處理器時,會接收到一個實現了 ResponseWriter 接口的對象
writer
,可以通過該對象來設置響應頭、寫入響應體以及設置狀態碼等信息。
補充:
在 Go 中,通常會使用http.ResponseWriter
類型的變量來表示實現了 ResponseWriter
接口的對象。例如,在你的 HTTP 處理器函數中,如下:
func MyHandler(writer http.ResponseWriter, r *http.Request) {// 使用 writer來設置響應頭、寫入響應體等
}
這樣,便可以通過 writer
對象來操作 HTTP 響應。
- 再看
request
的具體信息
net/http
中的 Request
結構體表示一個HTTP
請求,包含請求方法
、URL
、請求頭
、請求體
等信息。信息主要在文件 net/http/request.go中。
結構體中字段的信息:
type Request struct {Method string //HTTP請求方法,如GET、POST等URL *url.URL//HTTP請求的URL地址,是一個指向url.URL類型的指針。Proto string //HTTP協議版本,如"HTTP/1.0"或者"HTTP/1.1"ProtoMajor int //HTTP協議的主版本號,整數類型。如1ProtoMinor int //HTTP協議的次版本號,整數類型。如0Header Header //HTTP請求頭信息,是一個http.Header類型的映射,用于存儲HTTP請求頭。Body io.ReadCloser //HTTP請求體,是一個io.ReadCloser類型的接口,表示一個可讀可關閉的數據流。GetBody func() (io.ReadCloser, error) //HTTP請求體獲取函數ContentLength int64 //HTTP請求體的長度,整數類型。TransferEncoding []string //HTTP傳輸編碼,如"chunked"等。Close bool //表示在請求結束后是否關閉連接。Host string //HTTP請求的主機名或IP地址,字符串類型。Form url.Values //HTTP請求的表單數據,是一個url.Values類型的映射,用于存儲表單字段和對應的值。PostForm url.Values //HTTP POST請求的表單數據,同樣是一個url.Values類型的映射。MultipartForm *multipart.Form //HTTP請求的multipart表單數據,是一個multipart.Form類型的結構體。Trailer Header //HTTP Trailer頭信息,是一個http.Header類型的映射,用于存儲Trailer頭部字段和對應的值。RemoteAddr string //請求客戶端的地址。RequestURI string //請求的URI,包括查詢字符串。TLS *tls.ConnectionState //如果請求是使用TLS加密的,則該字段存儲TLS連接的狀態信息。Cancel <-chan struct{} //一個只讀通道,用于在請求被取消時發送信號。Response *Response //一個指向http.Response類型的指針,表示HTTP響應信息。ctx context.Context //一個context.Context類型的上下文,用于控制請求的超時和取消。}
Step2.2: 調用HandleFunc
函數處理post請求
http.HandleFunc("/user/add", func(writer http.ResponseWriter, request *http.Request) {// ...
})
這里使用
http.HandleFunc
函數注冊了一個處理 “/user/add” 路徑的回調函數。當有請求訪問 “/user/add” 路徑時,Go 將調用這個函數來處理請求。這是注冊的回調函數的簽名,它接收兩個參數,一個是
http.ResponseWriter
,用于構建 HTTP 響應,另一個是http.Request
,包含了客戶端的 HTTP 請求信息。
var params map[string]string //創建map
decoder := json.NewDecoder(request.Body)
//調用NewDecoder() 創建body的json解碼器
decoder.Decode(¶ms)
//json解碼成map后存儲到params變量
這段代碼使用 Go 標準庫的
encoding/json
包創建了一個 JSON 解碼器decoder
,然后將 HTTP 請求的主體(body)中的 JSON 數據解碼到params
變量中。params
是一個map[string]string
類型,用于存儲 JSON 解析后的鍵值對。其實就是將
json
轉換為map
io.WriteString(writer, "postjson:"+params["name"])
最后,通過
io.WriteString
向http.ResponseWriter
寫入響應。這個響應是一個字符串,包含了 “postjson:” 和從 JSON 中提取的名為 “name” 的字段的值。
總結:
代碼用于處理 POST 請求,解析請求主體中的 JSON
數據,并返回一個字符串響應,其中包含從 JSON
中提取的 "name"
字段的值。
運行結果如下:
http.HandleFunc("/user/del", func(writer http.ResponseWriter, request *http.Request)
這里使用
http.HandleFunc
函數注冊了一個處理 “/user/del” 路徑的回調函數。當有請求訪問 “/user/del” 路徑時,Go 將調用這個函數來處理請求。
接著是注冊的回調函數的簽名,它接收兩個參數,一個是
http.ResponseWriter
,用于構建 HTTP 響應,另一個是http.Request
,包含了客戶端的 HTTP 請求信息。
request.ParseForm()
這行代碼調用了
ParseForm
方法,用于解析請求的表單數據(包括 URL 中的查詢參數和請求體中的表單數據)。這是因為后面的代碼使用了request.Form.Get("name")
來獲取表單中名為 “name” 的字段的值。
io.WriteString(writer, "form:"+request.Form.Get("name"))
最后,通過
io.WriteString
向http.ResponseWriter
寫入響應。這個響應是一個字符串,包含了 “form:” 和從表單中提取的名為 “name” 的字段的值。
總結
其實就是用于處理·"/user/del"
路徑的請求,解析表單數據,并返回一個字符串響應,其中包含從表單中提取的 "name"
字段的值。
運行結果如下:
demo
package mainimport ("encoding/json""io""net/http"
)func main() {//Post請求//添加用戶 post client - server post entype postman JSON client ajax//客戶端向服務端發送請求http.HandleFunc("/user/add", func(writer http.ResponseWriter, request *http.Request) {//POST DATA ENTYPE JSONvar params map[string]stringdecoder := json.NewDecoder(request.Body)decoder.Decode(¶ms)//傳入地址 將傳入的json轉換為對應的map格式io.WriteString(writer, "postjson:"+params["name"]) //通過map獲取對應的值})http.HandleFunc("/user/del", func(writer http.ResponseWriter, request *http.Request) {request.ParseForm() //不能直接使用Get 需要轉換一下io.WriteString(writer, "form:"+request.Form.Get("name"))})http.ListenAndServe(":8080", nil)}