深刻剖析Go Web服務器實現原理

1. 前言

對於Go語言來講,只須要短短几行代碼,就能夠實現一個簡單的http server,加上協程的加持,Go實現的http server擁有很是優秀的性能。以下圖所示:web

經過net/http標準庫,咱們能夠啓動一個http服務器,而後讓這個服務器接收請求並返回響應。net/http標準庫還提供了一個鏈接多路複用器(multiplexer)的接口以及一個默認的多路複用器。編程

本文會對Go標準庫net/http服務的實現原理進行較爲深刻的探究,幫助讀者學習網絡編程的設計範式和常看法決問題的思路。本文主要分爲三部分:數組

  • 第一部分簡單介紹一下web服務器的代碼簡單實現。
  • 第二部分是web服務器的實現原理和使用技巧,是本文的重點,筆者會由淺入深的介紹Go語言實現web服務器各個環節所涉及到的概念和實現
  • 第三部分是源碼實現,讀者理解了第二部分的內容,會發現這部份內容不是特別的晦澀難懂。

2. Go Web服務器簡單實現

Go語言建立一個服務器的步驟很是簡單,只要調用ListenAndServe並傳入網絡地址以及負責處理請求的處理器(handler)做爲參數就能夠了。若是網絡地址爲空字符串,那麼服務器默認使用80端口進行網絡鏈接;若是處理器參數爲nil,那麼服務器將使用默認的多路複用器DefaultServeMux。代碼實現:瀏覽器

package main

import "net/http"

func main() {
	http.ListenAndServe("", nil)
}
複製代碼

除了能夠經過ListenAndServe的參數對服務器的網絡地址和處理器進行配置以外,還能夠經過Server結構對服務器進行更詳細的配置,其中包括爲請求讀取操做設置超時時間,爲響應寫入操做設置超時時間、爲Server結構設置錯誤日誌記錄器等。bash

package main

import "net/http"

func main() {
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: nil,
	}
	server.ListenAndServe()
}
複製代碼

Server結構提供了不少的可選配置項:服務器

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
	Addr    string  // TCP address to listen on, ":http" if empty
	Handler Handler // handler to invoke, http.DefaultServeMux if nil
	TLSConfig *tls.Config
	ReadTimeout time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	MaxHeaderBytes int
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
	ConnState func(net.Conn, ConnState)
	ErrorLog *log.Logger
}
複製代碼

3. web服務器的實現原理和使用技巧

前邊咱們啓動了一個web服務器,可是咱們訪問這個服務器地址會獲得:404 page not found 的信息。出現這一問題的緣由是咱們沒有爲服務器編寫任何處理器,服務器的多路複用器在接收到請求以後找不到任何處理器來處理請求,所以它只能返回一個404響應。爲了讓服務器可以產生實際的響應結果,咱們須要編寫處理器。網絡

3.1 處理器和處理器函數

處理器和處理器函數是不一樣的,本節咱們會詳細談談它們的定義。在Go語言中,一個處理器就是一個擁有ServeHTTP方法的接口,這個ServeHTTP方法須要接收兩個參數:第一個參數是一個ResponseWriter接口,而第二個參數則是一個指向Request結構的指針。換句話說,任何接口只要擁有一個ServeHTTP方法,而且該方法帶有如下簽名,那麼它就是一個處理器:架構

ServeHTTP(http.ResponseWriter, *http.Request)
複製代碼

不知道讀者有沒有想過一個問題: ServeHTTP爲何要接受ResponseWriter接口和一個指向Request結構的指針做爲參數呢?接收Request指針的緣由很簡單:爲了讓服務器可以覺察處處理器對Request結構的修改,咱們必需要以傳引用(pass by reference)而不是傳值(pass by value)的方式傳遞Request結構。可是另外一方面,爲何ServeHTTP倒是以傳值的方式接受ResponseWriter呢?難道服務器不須要知道處理器對ResponseWriter所作的修改嗎?對於這個問題,筆者深刻探究了一下net/http庫的源碼,在這裏給出答案:ResonseWriter實際上就是response這個非導出結構的接口,而ResponseWriter在使用response結構時,傳遞的也是指向response 結構的指針,也就是說,ResponseWriter 是以傳引用而是傳值的方式在使用response結構。換句話說:實際上ServeHTTP函數的兩個參數傳遞的都是引用而不是值——雖然ResponseWriter看上去是一個值,但它倒是一個帶有結構指針的接口app

爲了搞清楚另一個問題,咱們不得再也不離題一下。既然ListenAndServe第二個參數是一個處理器,那麼爲什麼它的默認值倒是多路複用器DefaultServeMux呢?原來DefaultServeMux是ServeMux結構的一個實例,ServeMux結構也擁有ServeHTTP方法,而且這個方法的簽名與成爲處理器所須要的簽名徹底一致。因此有告終論:DefaultServeMux不只是一個多路複用器,它仍是一個處理器。不過DefaultServeMux處理器與其餘的處理器不一樣,DefaultServeMux是一個特殊的處理器,它惟一要作的就是根據請求的URL將請求重定向到不一樣的處理器。框架

有了這個結論之後,咱們就能夠自行編寫一個處理器並使用它去代替默認的多路複用器,使服務器可以對客戶端正常響應了,代碼實現:

package main

import (
	"fmt"
	"net/http"
)

type MyHandler struct {}

func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world!")
}

func main() {
	handler := MyHandler{}
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: &handler,
	}
	server.ListenAndServe()
}
複製代碼

如今,咱們再次經過瀏覽器訪問http://localhost:8080/獲得的結果將是:hello world!

可是不管咱們訪問http://localhost:8080/的任何路由獲得的結果始終是hello world!,好比http://localhost:8080/get/list。形成這個問題的緣由很明顯:咱們本身建立了一個處理器並與服務器綁定,替換了原來使用的默認多路複用器(DefaultServeMux),也就是服務器不會再經過URL匹配將請求導向到不一樣的處理器,而是直接使用本身的處理程序處理全部的請求,這並非咱們想要的!咱們繼續使用DefaultServeMux做爲處理器好了,而後經過http.Handle函數將處理器綁定至DefaultServeMux。Handle函數是爲了操做便利而建立的函數,調用它等同於調用了DefaultServeMux的Handle方法。代碼以下:

package main

import (
	"fmt"
	"net/http"
)

type HelloHandler struct {}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello")
}

type WorldHandler struct {}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "world!")
}


func main() {
	server := http.Server{
		Addr:    "127.0.0.1:8080",
	}
	hello := HelloHandler{}
	http.Handle("/hello", &hello)
	world := WorldHandler{}
	http.Handle("/world", &world)

	server.ListenAndServe()
}
複製代碼

知道了什麼是處理器,咱們再來看一下什麼是處理器函數:處理器函數實際上就是與處理器擁有相同行爲的函數,這些函數與ServeHTTP方法擁有相同的簽名。咱們將上邊的處理器方法簡化一下,代碼以下:

package main

import (
	"fmt"
	"net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello")
}

func world(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "world!")
}


func main() {
	server := http.Server{
		Addr:              "127.0.0.1:8080",
	}
	http.HandleFunc("/hello", hello)
	http.HandleFunc("/world", world)

	server.ListenAndServe()
}
複製代碼

處理器函數的實現原理也很簡單:Go語言擁有一種HandlerFunc函數類型,它能夠把一個帶有正確簽名的函數f轉換成一個帶有方法f的Handler。好比說hello函數,程序只須要執行如下代碼:

helloHandler := HandlerFunc(hello)
複製代碼

就能夠把helloHandler設置成爲一個Handler。後邊咱們會經過源碼再來回顧如下HandleFunc的實現原理,咱們如今只須要記住結論就行了:處理器函數只不過是建立處理器的一種便利的方法而已

3.2 ServeMux 和 DefaultServeMux

咱們以前對ServeMux和DefaultServeMux作過介紹,ServeMux是一個HTTP請求多路複用器,它負責接收HTTP請求並根據請求中的URL將請求重定向到正確的處理器,ServeMux也實現了ServeHTTP方法,它也是一個處理器。當ServeMux的ServeHTTP方法接收到一個請求的時候,它會在結構的映射裏面找出與被請求URL最爲匹配的URL,而後調用與之相對應的處理器的ServeHTTP方法,如圖所示:

由於ServeMux是一個結構而不是一個接口,因此DefaultServeMux並非ServeMux的實現。DefaultServeMux其實是ServeMux的一個實例,而且全部引入了net/http標準庫的程序均可以使用這個實例。當用戶沒有爲Server結構指定處理器時,服務器就會使用DefaultServeMux做爲ServeMux的默認實例。

上邊的例子中,請求URL /hello完美的匹配了多路複用器綁定的URL,可是若是咱們使用瀏覽器訪問/hello/china呢?那麼服務器會返回什麼響應呢?

這個問題的答案跟咱們綁定的URL方法相關:匹配不成功的URL會根據URL的層級逐層降低,直到最終落到根URL上。當瀏覽器訪問/hello/china時,會返回什麼結果呢?不少人會認爲由於咱們的程序已經綁定了/hello,因此響應結果應該是hello。相信試驗事後的讀者可能會有所失望,服務器並無這樣去作,由於程序在綁定url使用的是/hello而不是/hello/。若是被綁定的url不是以/結尾,那麼它只能會與徹底相同的url匹配;可是若是被綁定的url以/結尾,那麼即便請求的url只有前綴部分與被綁定的url相同,ServeMux也會認爲這兩個url是匹配的。(留心的讀者可能會發現,這和Nginx的策略是相同的)

ServeMux的一個缺陷是沒法使用變量實現URL模式匹配,好比/get/post/123,咱們的本意是得到id爲123的帖子內容,可是咱們使用ServeMux並不能實現這樣的功能。因此纔有了業界著名的HttpRouter包的出現。

3.3 優雅的實現Web框架中間件

咱們上邊瞭解瞭如何Go語言如何實現一個服務器,知道了不少概念和原理,這很枯燥,本節咱們來實戰一個例子:優雅的實現Web框架中間件。

中間件是什麼,在這裏無需多言,利用中間件技術能夠將業務代碼和非業務代碼解耦。咱們來看一下咱們要實現中間件的用法:

r := router.NewRouter()
r.Use(timeMiddleware)
r.Add("/", http.HandlerFunc(hello))
r.Run(":8080")
複製代碼

相信沒有比這更簡潔、更靈活的中間件了,實現它卻很是簡單:

package router

import "net/http"

type middleware func(http.Handler) http.Handler

type Router struct {
	middlewareChain [] middleware
	mux map[string] http.Handler
}

func NewRouter() *Router{
	return &Router{
		[]middleware{},
		make(map[string] http.Handler),
	}
}

func (r *Router) Use(m middleware) {
	r.middlewareChain = append(r.middlewareChain)
}

func (r *Router) Add(route string, h http.Handler) {
	var mergeHandler = h
	for i := len(r.middlewareChain) - 1; i >= 0; i-- {
		mergeHandler = r.middlewareChain[i](mergeHandler)
	}
	r.mux[route] = mergeHandler
}

func (r *Router) Run(addr string) {
	for route, handler := range r.mux {
		http.Handle(route, handler)
	}
	http.ListenAndServe(addr, nil)
}
複製代碼

能夠看到咱們代碼實現的核心邏輯也只有40行的代碼,首先定義一個路由結構體,其內容一個是中間件的調用鏈條,咱們用slice存放,另一個是實現了路由及其handler的map:

type Router struct {
	middlewareChain [] middleware
	mux map[string] http.Handler
}
複製代碼

咱們爲路由結構體定義了三個方法,Use、Add、Run。Use的實現只是簡單的向中間件鏈條中添加了一個元素而已,使用append方法,middlerware並非隨便寫的,咱們也定義了一個結構體,接收一個http.Handler,返回值也是一個http.Handler,這樣咱們才能在最後使用handle將路由url成功註冊處處理器上:

type middleware func(http.Handler) http.Handler
...
func (r *Router) Use(m middleware) {
	r.middlewareChain = append(r.middlewareChain)
}
複製代碼

Add方法就是使用middleware對處理器函數進行包裝,須要注意的是代碼中middleware數組遍歷順序與用戶但願的調用順序是「相反」的,這個應該不難理解。

func (r *Router) Add(route string, h http.Handler) {
	var mergeHandler = h
	for i := len(r.middlewareChain) - 1; i >= 0; i-- {
		mergeHandler = r.middlewareChain[i](mergeHandler)
	}
	r.mux[route] = mergeHandler
}
複製代碼

最後是Run函數,所作的工做就是將咱們定義的路由解析,也就是將url綁定到指定的處理器上,咱們默認只開發了addr可配置,使用DefaultServeMux:

func (r *Router) Run(addr string) {
	for route, handler := range r.mux {
		http.Handle(route, handler)
	}
	http.ListenAndServe(addr, nil)
}
複製代碼

接下來咱們就能夠在main函數中使用它了:

......
func hello(wr http.ResponseWriter, r *http.Request) {
	wr.Write([]byte("hello"))
}

func timeMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
		timeStart := time.Now()
		//next handler
		next.ServeHTTP(wr, r)

		timeElapsed := time.Since(timeStart)

		log.Println(timeElapsed)
	})
}


func main() {
	r := router.NewRouter()
	r.Use(timeMiddleware)
	r.Add("/", http.HandlerFunc(hello))
	r.Run(":8080")
}
複製代碼

咱們定義了一個timeMiddleware,用來統計接口的耗時,timeMiddleware符合middleware結構體的定義,注意在返回的匿名函數內調用了next.ServeHTTP(wr, r)方法。路由的Add方法中使用HandlerFunc對hello處理器函數作了封裝,使其轉化爲了處理器。

這樣咱們一個優雅的web中間件就實現了,咱們能夠拓展這個中間件的功能:好比在Run函數中添加更多的控制參數。給指定的處理器函數添加指定的中間件等等,這些有趣而實用的功能就交給感興趣的讀者完成吧。

4. Go Web服務器實現源碼解析

學習軟件設計的思想和深刻理解架構理論最好的途徑是閱讀源碼,本節將帶領你們一步步閱讀Go Web服務器的實現邏輯,相信你們能收穫不少。

4.1 路由註冊:http.HandleFunc 和 http.Handle源碼

咱們前邊看到,http.HandleFunc和http.Handle方法都是用於註冊路由,二者的區別在於第二個參數,前者是一個具備 func(w http.ResponseWriter, r *http.Requests) 簽名的函數,然後者是一個結構體,該結構體實現了 func(w http.ResponseWriter, r *http.Requests) 簽名的方法。

http.HandleFunc 和 http.Handle源碼實現以下:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}
...
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}
複製代碼
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
複製代碼

能夠看到最終這兩個函數都是由多路複用器調用Handle方法完成路由的註冊。這裏咱們遇到了兩種類型的對象:ServeMux 和 Handler,咱們先來講Handler:

Handler是一個接口:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
複製代碼

Handler 接聲明瞭名爲 ServeHTTP 的函數簽名,也就是說任何結構只要實現了這個 ServeHTTP 方法,那麼這個結構體就是一個 Handler 對象。其實go的 http 服務都是基於 Handler 進行處理,而 Handler 對象的 ServeHTTP 方法也正是用以處理 request 並構建 response 的核心邏輯所在。

在上邊的代碼中,咱們看到有這麼一段邏輯:

mux.Handle(pattern, HandlerFunc(handler))
複製代碼

這裏的HandlerFunc是個什麼呢?函數、結構體......,答案還要從源碼中尋找:

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
複製代碼

讀到這裏,讀者應該明白了,HandlerFunc實際上是一個類型,只不過是具備func(ResponseWriter, *Request)函數簽名的類型。HandlerFunc(handler)只不過是將handler方法作了類型轉化。而且這種類型本身實現了ServeHTTP函數,也就是說這個類型的函數其實就是一個 Handler 類型的對象。利用這種類型轉換,咱們能夠將一個 handler 函數轉換爲一個 Handler 對象,而不須要定義一個結構體,再讓這個結構實現 ServeHTTP 方法。讀者能夠認真體會一下這種設計思想。

4.2 路由解析: ServeMux和 DefaultServeMux源碼實現

前邊咱們說到,Go語言中的路由基於多路複用器實現,多路複用器基本結構是:ServeMux。ServeMux是一個結構體,咱們先來看一下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
}
複製代碼

ServeMux中的m是一個map,key是路由表達式,value是muxEntry結構體,muxEntry結構體的內容是路由表達式和handler(處理器)。

咱們前邊說過ServeMux也實現了ServeHTTP方法:

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}
複製代碼

代碼很簡單,不作過多解釋,咱們直到了什麼是handler和ServeMux,再回到原來的代碼邏輯:

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler)
}
複製代碼

咱們知道:DefaultServeMux是Go語言默認提供的多路複用器(Multiplexer)。當咱們沒有建立自定義的 Multiplexer ,則會自動使用一個默認的 Multiplexer 。它是ServeMux的一個實例。咱們就來看一下ServeMux的Handle方法都作了些什麼工做吧,咱們前邊其實也說過,它將url映射到了相關的處理器。源碼以下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
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
	}
}
複製代碼

咱們發現:ServeMux 的 map[string]muxEntry 增長給定的路由匹配規則;還將路由表達式以 '/' 結尾對應的 muxEntry 對象加入到 []muxEntry 中,按照路由表達式長度排序。這就是咱們上邊舉例子中爲何/hello/註冊和/hello是不同的了,降級的匹配規則就是這樣實現的

4.3 開啓監聽服務ListenAndServe源碼實現

咱們前邊代碼中屢次使用到ListenAndServe,咱們來看一下它的實現:

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(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
複製代碼

這裏先建立了一個 Server 對象,傳入了地址和 handler 參數,而後調用 Server 對象 ListenAndServe() 方法。這是http調用ListenAndServe的方式:

http.ListenAndServe(":8080", nil)
複製代碼

若是是nil,就使用默認的DefaultServeMux,它也是一個ServeMux實例。Server結構體咱們前邊提到過,再來看一下:

type Server struct {
	Addr    string  // TCP address to listen on, ":http" if empty
	Handler Handler // handler to invoke, http.DefaultServeMux if nil
	TLSConfig *tls.Config
	ReadTimeout time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	MaxHeaderBytes int
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
	ErrorLog *log.Logger

	disableKeepAlives int32     // accessed atomically.
	inShutdown        int32     // accessed atomically (non-zero means we're in Shutdown) nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used mu sync.Mutex listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} doneChan chan struct{} onShutdown []func() } 複製代碼

前邊一部分是可變配置參數咱們前邊提過了,後邊一部分首字母都是小寫的,咱們沒法對其進行配置。

在 Server 的 ListenAndServe 方法中,會初始化監聽地址 Addr ,同時調用 Listen 方法設置監聽。最後將監聽的TCP對象傳入 Serve 方法:

func (srv *Server) Serve(l net.Listener) error {
 ...

 baseCtx := context.Background() // base is always background, per Issue 16220
 ctx := context.WithValue(baseCtx, ServerContextKey, srv)
 for {
  rw, e := l.Accept() // 等待新的鏈接創建

  ...

  c := srv.newConn(rw)
  c.setState(c.rwc, StateNew) // before Serve can return
  go c.serve(ctx) // 建立新的協程處理請求
 }
}
複製代碼

這裏隱去了一些細節,以便了解 Serve 方法的主要邏輯。首先建立一個上下文對象,而後調用 Listener 的 Accept() 等待新的鏈接創建;一旦有新的鏈接創建,則調用 Server 的 newConn() 建立新的鏈接對象,並將鏈接的狀態標誌爲 StateNew ,而後開啓一個新的 goroutine 處理鏈接請求。

conn的Serve方法依然很長,不過咱們從中能學習到不少東西:

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())

    for {
		w, err := c.readRequest(ctx)
		if c.r.remain != c.server.initialReadLimitSize() {
			// If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } ...... serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) c.curReq.Store((*response)(nil)) ...... c.rwc.SetReadDeadline(time.Time{}) } } 複製代碼

當一個鏈接創建以後,該鏈接中全部的請求都將在這個協程中進行處理,直到鏈接被關閉。在 serve() 方法中會循環調用 readRequest() 方法讀取下一個請求進行處理。

c.setState(c.rwc, StateIdle)  //將鏈接狀態置爲空閒
c.curReq.Store((*response)(nil)) //將當前請求置爲空
複製代碼

其中最關鍵的邏輯就是一行代碼:

serverHandler{c.server}.ServeHTTP(w, w.req)
複製代碼

咱們繼續在源碼中查看serverHandler:

// serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server } 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) } 複製代碼

在 serverHandler 的 ServeHTTP() 方法裏的 sh.srv.Handler 其實就是咱們最初在 http.ListenAndServe() 中傳入的 Handler 對象,也就是咱們自定義的 ServeMux 對象。若是該 Handler 對象爲 nil ,則會使用默認的 DefaultServeMux 。最後調用 ServeMux 的 ServeHTTP() 方法匹配當前路由對應的 handler 方法。

接下來就是ServeMux的handler方法:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	// CONNECT requests are not canonicalized.
	if r.Method == "CONNECT" {
		// If r.URL.Path is /tree and its handler is not registered,
		// the /tree -> /tree/ redirect applies to CONNECT requests
		// but the path canonicalization does not.
		if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
		}

		return mux.handler(r.Host, r.URL.Path)
	}

	// All other requests have any port stripped and path cleaned
	// before passing to mux.handler.
	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	// If the given path is /tree and its handler is not registered,
	// redirect for /tree/.
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	if path != r.URL.Path {
		_, pattern = mux.handler(host, path)
		url := *r.URL
		url.Path = path
		return RedirectHandler(url.String(), StatusMovedPermanently), pattern
	}

	return mux.handler(host, r.URL.Path)
}
......
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// Host-specific pattern takes precedence over generic ones
	if mux.hosts {
		h, pattern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}
......
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
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, ""
}
複製代碼

ServeMux的Handler方法就是根據url調用指定handler方法,handler方法的做用是調用match匹配路由。在 match 方法裏咱們看到以前提到的 map[string]muxEntry 和 []muxEntry 。這個方法裏首先會利用進行精確匹配,在 map[string]muxEntry 中查找是否有對應的路由規則存在;若是沒有匹配的路由規則,則會進行近似匹配。

對於相似 /path1/path2/path3 這樣的路由,若是不能找到精確匹配的路由規則,那麼則會去匹配和當前路由最接近的已註冊的父路由,因此若是路由 /path1/path2/ 已註冊,那麼該路由會被匹配,不然繼續匹配父路由,直到根路由 / 。

ServeMux的Handler方法中找到要執行的handler以後,就調用handler的serveHTTP方法,至此,你們應該熟悉Go Web服務器的整個實現過程了。

5.小結

Go語言實現Web服務器的設計仍是至關精巧的,簡單總結以下:

  • 一個方法只要實現了ServeHTTP的接口,它就是一個處理器。
ServeHTTP(http.ResponseWriter, *http.Request)
複製代碼
  • 處理器是處理用戶邏輯的核心所在,Go語言提供了handleFunc方法,將帶有func(http.ResponseWriter, *http.Request)函數簽名(處理器函數)的方法都能轉化爲處理器,從而方便讀者實現業務邏輯。

  • Go提供了一個默認的多路複用器DefaultServeMux,它是ServeMux的一個實例,一樣實現了ServeHTTP方法,做用是將url綁定到具體的處理器。

咱們還簡單實現了一個web中間件,幫助讀者瞭解了Go設計服務器接口的靈活性和可拓展性。

文章的最後咱們還簡單分析了一下Go Web服務器實現的源碼,學習了一下網絡編程的設計思想。但願本文能給讀者帶來一些收穫,爲從此學習Go語言在Web方面的編程打下基礎。

相關文章
相關標籤/搜索