1. 程式人生 > 其它 >net/http原始碼分析之Go原生是如何實現web程式

net/http原始碼分析之Go原生是如何實現web程式

技術標籤: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程式是如何執行的:

  1. 首先我們啟動程式,main函式以呼叫http.HandleFunc開始,它告訴http包使用handler處理所有對web根目錄("/")的請求。
  2. 然後呼叫http.ListenAndServe,指定監聽8080埠。(暫時不必擔心它的第二個引數nil。)此函式將阻塞,直到程式終止。
  3. 所以當在瀏覽器輸入http://localhost:8080/coding時,程式收到此請求並將其路由給給handler函式處理。
  4. 通過變數r獲得請求資訊裡的請求路徑,獲取到路徑名後將去掉第一個字元的字串寫入w輸出到客戶端

handler函式:

  • handler的函式簽名是func(ResponseWriter, *Request),它以http.ResponseWriter和http.Request作為引數。
  • 一個http.ResponseWriter的值w,組裝了HTTP伺服器的響應;通過寫入它,我們將資料傳送到HTTP客戶端。
  • http.Request是一個表示客戶端的HTTP請求的資料結構

二、細說Go如何使Web工作

其實Go使得Web工作主要就是兩步:

  1. 先通過HandleFunc函式在DefaultServeMux中為給定的模式(pattern)註冊處理函式。
  2. 然後通過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