原文連結 http://ironxu.com/779
Go Web 基礎概念與代碼閱讀 1. Go 搭建簡單的web 服務
Go 語言裡面提供了一個完善的 net/http 包,通過http 包可以很方便的就搭建起來一個可以啟動並執行Web服務。同時使用這個包能很簡單地對Web的路由,靜態檔案,模版,cookie等進行設定和操作。
$GOPATH/src/github.com/ironxu/go_note/web/basic/server.go 源碼如下:
// http 包建立網頁伺服器package mainimport ( "fmt" "log" "net/http")func sayhelloName(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println("path:", r.URL.Path) fmt.Fprintf(w, "hello go")}func main() { http.HandleFunc("/", sayhelloName) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServer: ", err) }}
go run server.go 即可啟動http 服務,使用瀏覽器開啟 http://localhost:9090 可以查看相應輸出。 2. Go Web 服務講解
本節介紹 Go Web 服務底層實現,包括註冊路由和請求處理 2.1 HTTP 包運行機制
Go 實現Web 服務流程如下 建立Listen Socket, 監聽指定的連接埠, 等待用戶端請求到來。 Listen Socket 接受用戶端的請求, 得到Client Socket, 接下來通過Client Socket與用戶端通訊。 處理用戶端的請求, 首先從Client Socket讀取HTTP請求, 然後交給相應的handler 處理請求, 最後將handler處理完畢的資料, 通過Client Socket寫給用戶端。
其中涉及伺服器端的概念: Request:使用者請求的資訊,用來解析使用者的請求資訊,包括post、get、cookie、url等資訊 Conn:使用者的每次請求連結 Handler:處理請求和產生返回資訊的處理邏輯 Response:伺服器需要反饋給用戶端的資訊 2.2 服務監聽與請求處理過程
Go是通過一個ListenAndServe 監聽服務,底層處理:初始化一個server對象,然後調用 net.Listen("tcp", addr),監控我們設定的連接埠。
監控連接埠之後,調用 srv.Serve(net.Listener) 函數,處理接收用戶端的請求資訊。首先通過Listener 接收請求,其次建立一個Conn,最後單獨開了一個goroutine,把這個請求的資料當做參數扔給這個conn去服務。go c.serve() 使用者的每一次請求都是在一個新的goroutine去服務,相互不影響。
分配相應的函數處理請求: conn 首先會解析 request:c.readRequest(), 然後擷取相應的handler:handler := c.server.Handler,這個是調用函數ListenAndServe 時候的第二個參數,例子傳遞的是nil,也就是為空白,那麼預設擷取handler = DefaultServeMux。DefaultServeMux 是一個路由器,它用來匹配url跳轉到其相應的handle函數
調用 http.HandleFunc("/", sayhelloName) 作用是註冊了請求/的路由規則,將url 和handle 函數註冊到DefaultServeMux 變數,最後調用DefaultServeMux 的ServeHTTP 方法,這個方法內部調用handle 函數。
流程圖如下:
3. Web 服務代碼實現 3.1 路由註冊代碼
1 調用 http.HandleFunc(“/”, sayhelloName) 註冊路由
// /usr/local/go/src/net/http/server.go:2081func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) // DefaultServeMux 類型為 *ServeMux}
2 使用預設 ServeMux
// :2027func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler))}
3 註冊路由策略 DefaultServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() ... mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true } ...}
涉及資料結構
// :1900 ServeMux 預設執行個體是 DefaultServeMuxtype ServeMux struct { mu sync.RWMutex // 鎖,由於請求涉及到並發處理,因此這裡需要一個鎖機制 m map[string]muxEntry // 路由規則,一個string對應一個mux實體,這裡的string就是註冊的路由運算式 hosts bool // 是否在任意的規則中帶有host資訊}type muxEntry struct { explicit bool h Handler // 路由處理器 pattern string // url 匹配正則}type Handler interface { ServeHTTP(ResponseWriter, *Request)}
3.2 服務監聽代碼
1 調用 err := http.ListenAndServe(“:9090”, nil) 監聽連接埠
// /usr/local/go/src/net/http/server.go:2349func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} // handler 為空白 return server.ListenAndServe()}
建立一個 Server 對象,並調用 Server 的 ListenAndServe()
2 監聽TCP連接埠
// :2210func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})}
3 接收請求
// :2256func (srv *Server) Serve(l net.Listener) error { defer l.Close() ... baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr()) for { rw, e := l.Accept() // 1. Listener 接收請求 if e != nil { ... } tempDelay = 0 c := srv.newConn(rw) // 2. 建立 *conn c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) // 3. 新啟一個goroutine,將請求資料做為參數傳給 conn,由這個新的goroutine 來處理這次請求 }}
4 goroutine 處理請求
// Serve a new connection.func (c *conn) serve(ctx context.Context) { ... // HTTP/1.x from here on. c.r = &connReader{r: c.rwc} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) ctx, cancelCtx := context.WithCancel(ctx) defer cancelCtx() for { w, err := c.readRequest(ctx) // 1. 擷取請求資料 ... serverHandler{c.server}.ServeHTTP(w, w.req) // 2. 處理請求 serverHandler, 對應下面第5步 w.cancelCtx() if c.hijacked() { return } w.finishRequest() // 3. 返迴響應結果 if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) }}
5 處理請求
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux // ServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}
5.1 handler.ServeHTTP(rw, req)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) // HandlerFunc, Handler h.ServeHTTP(w, r)}
5.2 執行處理
// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}
涉及的資料類型
type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil ReadTimeout time.Duration // maximum duration before timing out read of the request WriteTimeout time.Duration // maximum duration before timing out write of the response ...}type conn struct { server *Server // server is the server on which the connection arrived. rwc net.Conn // rwc is the underlying network connection. It is usually of type *net.TCPConn or *tls.Conn. remoteAddr string // This is the value of a Handler's (*Request).RemoteAddr. mu sync.Mutex // mu guards hijackedv, use of bufr, (*response).closeNotifyCh. ...}type serverHandler struct { srv *Server}
3.3 Go 代碼的執行流程
調用Http.HandleFunc,按順序做了幾件事: 調用了DefaultServeMux的HandleFunc 調用了DefaultServeMux的Handle 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則
調用http.ListenAndServe(":9090", nil),按順序做了幾件事情: 執行個體化Server 調用Server的ListenAndServe() 調用net.Listen(“tcp”, addr)監聽連接埠 啟動一個for迴圈,在迴圈體中Accept請求 對每個請求執行個體化一個Conn,並且開啟一個goroutine為這個請求進行服務go c.serve() 讀取每個請求的內容w, err := c.readRequest() 判斷handler是否為空白,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux 調用handler的ServeHttp 在這個例子中,下面就進入到DefaultServeMux.ServeHttp 根據request選擇handler,並且進入到這個handler的ServeHTTP mux.handler(r).ServeHTTP(w, r) 選擇handler:
A 判斷是否有路由能滿足這個request(迴圈遍曆ServerMux的muxEntry)
B 如果有路由滿足,調用這個路由handler的ServeHttp
C 如果沒有路由滿足,調用NotFoundHandler的ServeHttp 4. 自訂路由實現
定義的類型實現ServeHTTP 方法,即可實現自訂路由
package mainimport ( "fmt" "log" "net/http")type MyMux struct {}func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sayhelloName(w, r) return } http.NotFound(w, r) return}func sayhelloName(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println("path:", r.URL.Path) fmt.Fprintf(w, "hello go")}func main() { mux := &MyMux{} err := http.ListenAndServe(":9090", mux) if err != nil { log.Fatal("ListenAndServer: ", err) }}
參考 3.2 Go搭建一個Web伺服器 3.3 Go如何使得Web工作 3.4 Go的http包詳解
可以關注我的微博瞭解更多資訊: @剛剛小碼農