gin源碼解讀1-net/http的大概流程


gin框架預覽

  • router.Run()的源碼:
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	if engine.isUnsafeTrustedProxies() {
		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
	}

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}

然后看到開始調用的是http.ListenAndServe(address, engine), 這個函數是net/http的函數. 然后請求數據就在net/http開始流轉.

所以, gin源碼閱讀系列就是要弄明白以下幾個問題:

  1. request數據是如何流轉的
  2. gin框架到底扮演了什么角色
  3. 請求從gin流入net/http, 最后又是如何回到gin中
  4. gin的context為何能承擔起來復雜的需求
  5. gin的路由算法
  6. gin的中間件是什么
  7. gin的Engine具體是個什么東西
  8. net/http的requeset, response都提供了哪些有用的東西

request數據是如何流轉的

先不使用gin, 直接使用net/http來處理http請求

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println(request.URL.Path)
		_, _ = writer.Write([]byte(request.URL.Path + "/哈哈哈"))
	})
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalln("start server failed", err)
	}
}

HTTP是如何建立起來的

簡單的說一下http請求是如何建立起來的(需要有基本的網絡基礎,socket編程)

在TCP/IP五層模型下, HTTP位於應用層, 需要有傳輸層來承載HTTP協議. 傳輸層比較常見的協議是TCP,UDP, SCTP等. 由於UDP不可靠, SCTP有自己特殊的運用場景, 所以一般情況下HTTP是由TCP協議承載的(可以使用wireshark抓包然后查看各層協議)

使用TCP協議的話, 就會涉及到TCP是如何建立起來的. 面試中能夠常遇到的名詞三次握手, 四次揮手就是在這里產生的. 具體的建立流程就不在陳述了, 大概流程就是圖中左半邊

所以說, 要想能夠對客戶端http請求進行回應的話, 就首先需要建立起來TCP連接, 也就是socket. 下面要看下net/http是如何建立起來socket?

net/http是如何建立socket的

從圖上可以看出, 不管server代碼如何封裝, 都離不開bind,listen,accept這些函數. 就從上面這個簡單的demo入手查看源碼.

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)

for {
	rw, err := l.Accept()
	if err != nil {
		select {
		case <-srv.getDoneChan():
			return ErrServerClosed
		default:
		}
		if ne, ok := err.(net.Error); ok && ne.Temporary() {
			if tempDelay == 0 {
				tempDelay = 5 * time.Millisecond
			} else {
				tempDelay *= 2
			}
			if max := 1 * time.Second; tempDelay > max {
				tempDelay = max
			}
			srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
			time.Sleep(tempDelay)
			continue
		}
		return err
	}
	connCtx := ctx
	if cc := srv.ConnContext; cc != nil {
		connCtx = cc(connCtx, rw)
		if connCtx == nil {
			panic("ConnContext returned nil")
		}
	}
	tempDelay = 0
	c := srv.newConn(rw)
	c.setState(c.rwc, StateNew, runHooks) // before Serve can return
	go c.serve(connCtx)

}

注冊路由

	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println(request.URL.Path)
		_, _ = writer.Write([]byte(request.URL.Path + "/哈哈哈"))
	})

這段代碼是在注冊一個路由及這個路由的handler到DefaultServeMux中

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}
  • 定義一個函數類型,對一個函數進行轉型成一個類型,然后該類型實現一些方法,從而實現某些接口
func main() {
	_ = http.ListenAndServe(":8080", IndexHandler(Index))
}
type IndexHandler func(w http.ResponseWriter, r *http.Request)
func (i IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	i(w, r)
}

func Index(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.URL.Path)
	_, _ = w.Write([]byte("哈哈哈123456"))
}

可以看到這個路由注冊太過簡單了, 也就給gin, iris, echo等框架留下了擴展的空間, 后面詳細說這個東西

服務監聽及響應

上面路由已經注冊到net/http了, 下面就該如何建立socket了, 以及最后又如何取到已經注冊到的路由, 將正確的響應信息從handler中取出來返回給客戶端
1.
_ = http.ListenAndServe(":8080", nil)
2.

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
// net/http/server.go:L2752-2765
func (srv *Server) ListenAndServe() error {
    // ... 省略代碼
    ln, err := net.Listen("tcp", addr) // <-----看這里listen
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// net/http/server.go:L2805-2853
func (srv *Server) Serve(l net.Listener) error {
    // ... 省略代碼
    for {
        rw, e := l.Accept() // <----- 看這里accept
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) // <--- 看這里
    }
}
// net/http/server.go:L1739-1878
func (c *conn) serve(ctx context.Context) {
    // ... 省略代碼
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.cancelCtx()
    if c.hijacked() {
        return
    }
    w.finishRequest()
    // ... 省略代碼
}
// net/http/server.go:L2733-2742
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    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)
	h.ServeHTTP(w, r)
}

// net/http/server.go:L2352-2362
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) // <--- 看這里
    h.ServeHTTP(w, r)
}

// net/http/server.go:L1963-1965
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

這基本是整個過程的代碼了. 基本上是:

  • ln, err := net.Listen("tcp", addr)做了初試化了socket, bind, listen的操作.
  • rw, e := l.Accept()進行accept, 等待客戶端進行連接
  • go c.serve(ctx) 啟動新的goroutine來處理本次請求. 同時主goroutine繼續等待客戶端連接, 進行高並發操作
  • h, _ := mux.Handler(r) 獲取注冊的路由, 然后拿到這個路由的handler, 然后將處理結果返回給客戶端
    從這里也能夠看出來, net/http基本上提供了全套的服務.

為什么會出現很多go框架

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// Check for exact match first.
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// Check for longest valid match.  mux.es contains all patterns
	// that end in / sorted from longest to shortest.
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

從這段函數可以看出來, 匹配規則過於簡單, 當能匹配到路由的時候就返回其對應的handler, 當不能匹配到時就返回/. 所以net/http的路由匹配無法滿足復雜的需求開發. 所以基本所有的go框架干的最主要的一件事情就是重寫net/http的route

所以我們直接說gin就是一個httprouter也不過分, 當然gin也提供了其他比較主要的功能, 后面會一一介紹

還有一個go框架要實現的東西是http.ResponseWriter

綜述, net/http基本已經提供http服務的70%的功能, 那些號稱賊快的go框架, 基本上都是提供一些功能, 讓我們能夠更好的處理客戶端發來的請求.

參考鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM