net/http原始碼分析之Go原生是如何實現web程式
阿新 • • 發佈:2021-01-08
技術標籤:Go Web
一、Go原生程式碼庫實現一個簡單的web程式
首先來瀏覽一下以下使用Go內建的net/http包實現的一個簡單的web例項:
示例程式碼:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//r.URL.Path是請求URL的路徑部分
//其尾隨的[1:]意思是“建立Path中第一個字元到結尾的子片段”(這將從路徑名中刪除前導“ /”)。
//將format字串寫入w
fmt.Fprintf(w, "Hello world, I love %s!", r.URL.Path[1:])
}
func main() {
//設定路由
http.HandleFunc("/", handler)
//設定監聽的埠
http.ListenAndServe(":8080", nil)
}
執行上面程式,開啟http://localhost:8080/coding可以見到如下效果:
先來大概解釋一下這個web程式是如何執行的:
- 首先我們啟動程式,main函式以呼叫http.HandleFunc開始,它告訴http包使用handler處理所有對web根目錄("/")的請求。
- 然後呼叫http.ListenAndServe,指定監聽8080埠。(暫時不必擔心它的第二個引數nil。)此函式將阻塞,直到程式終止。
- 所以當在瀏覽器輸入http://localhost:8080/coding時,程式收到此請求並將其路由給給handler函式處理。
- 通過變數r獲得請求資訊裡的請求路徑,獲取到路徑名後將去掉第一個字元的字串寫入w輸出到客戶端
handler函式:
- handler的函式簽名是
func(ResponseWriter, *Request)
,它以http.ResponseWriter和http.Request作為引數。 - 一個http.ResponseWriter的值w,組裝了HTTP伺服器的響應;通過寫入它,我們將資料傳送到HTTP客戶端。
- http.Request是一個表示客戶端的HTTP請求的資料結構
二、細說Go如何使Web工作
其實Go使得Web工作主要就是兩步:
- 先通過HandleFunc函式在DefaultServeMux中為給定的模式(pattern)註冊處理函式。
- 然後通過ListenAndServe監聽TCP埠8080,然後呼叫Serve和handler來處理傳入連線的請求。handler通常為nil,在這種情況下使用DefaultServeMux中匹配的handler。
下面通過剖析原始碼來了解Go是如何實現上面的兩步
1、HandleFunc函式為DefaultServeMux註冊handler
DefaultServeMux&ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
...
type muxEntry struct {
h Handler
pattern string
}
...
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
由net/http.server.go裡的原始碼可知,DefaultServeMux
是一個指向ServeMux
的指標變數。
而ServeMux
是一個HTTP請求多路複用器。它將每個傳入請求的URL與一組已註冊的模式(ServerMux中的m)進行匹配,並呼叫與URL最匹配的模式的處理程式。
HandleFunc函式:
HandleFunc在DefaultServeMux中為給定的模式(pattern)註冊處理函式(handler)。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
//為給定的模式(pattern)註冊處理函式(handler)。
DefaultServeMux.HandleFunc(pattern, handler)
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
...
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
...
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
- 由net/http.server.go裡的原始碼可知HandlerFunc是一個實現了Handler介面的型別
- 而ServeMux的Handle方法是為pattern註冊Handler。
ServeMux的Handler方法
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
//為pattern註冊Handler
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
...
}
2、ListenAndServe監聽埠、處理接收的請求
ListenAndServe監聽TCP的地址addr,然後呼叫Serve和handler來處理傳入連線的請求。處理程式通常為nil,在這種情況下使用DefaultServeMux。
http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
- 利用Server的ListenAndServe監聽埠和處理請求
Server的ListenAndServe方法
func (srv *Server) ListenAndServe() error {
...
addr := srv.Addr
...
ln, err := net.Listen("tcp", addr)
...
return srv.Serve(ln)
}
- 通過net.Listen監聽tcp埠,返回偵聽器ln,然後呼叫Server的Serve方法接收偵聽器接收的請求並處理。
Server的Serve方法
在偵聽器l上接收傳入的連線,為每個連線建立一個新的goroutine。這些goroutines讀取請求,處理請求。
func (srv *Server) Serve(l net.Listener) error {
...
baseCtx := context.Background()
...
//ServerContextKey是一個全域性變數,它等於&contextKey{"http-server"}
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
...
connCtx := ctx
...
c := srv.newConn(rw)
...
go c.serve(connCtx)
}
}
conn的serve方法
func (c *conn) serve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
...
req := w.req
...
//重點方法
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
serverHandler結構體
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux //handler為nil則使用預設的DefaultServeMux
}
...
handler.ServeHTTP(rw, req)
}
然後呼叫ServeMux的ServeHTTP方法,方法內部會根據其匹配模式返回符合請求URL的handler,然後呼叫handler的實現的ServeHTTP介面的方法處理請求
本文參考了
go官方文件給出的Writing Web Applications