1. 程式人生 > >golang http 伺服器程式設計

golang http 伺服器程式設計

golang http 伺服器程式設計
1. 初識
http 是典型的 C/S 架構,客戶端向服務端傳送請求(request),服務端做出應答(response)。

golang 的標準庫 net/http 提供了 http 程式設計有關的介面,封裝了內部TCP連線和報文解析的複雜瑣碎的細節,使用者只需要和 http.request 和 http.ResponseWriter 兩個物件互動就行。也就是說,我們只要寫一個 handler,請求會通過引數傳遞進來,而它要做的就是根據請求的資料做處理,把結果寫到 Response 中。廢話不多說,來看看 hello world 程式有多簡單吧!

package main

import (
    "io"
    "net/http"
)

type helloHandler struct{}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, world!"))
}

func main() {
    http.Handle("/", &helloHandler{})
    http.ListenAndServe(":12345", nil)
}複製程式碼
執行 go run hello_server.go,我們的伺服器就會監聽在本地的 12345 埠,對所有的請求都會返回 hello, world!:


正如上面程式展示的那樣,我們只要實現的一個 Handler,它的介面原型是(也就是說只要實現了 ServeHTTP 方法的物件都可以作為 Handler):

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}複製程式碼
然後,註冊到對應的路由路徑上就 OK 了。

http.HandleFunc接受兩個引數:第一個引數是字串表示的 url 路徑,第二個引數是該 url 實際的處理物件。

http.ListenAndServe 監聽在某個埠,啟動服務,準備接受客戶端的請求(第二個引數這裡設定為 nil,這裡也不要糾結什麼意思,後面會有講解)。每次客戶端有請求的時候,把請求封裝成 http.Request,呼叫對應的 handler 的 ServeHTTP 方法,然後把操作後的 http.ResponseWriter 解析,返回到客戶端。

2. 封裝
上面的程式碼沒有什麼問題,但是有一個不便:每次寫 Handler 的時候,都要定義一個型別,然後編寫對應的 ServeHTTP 方法,這個步驟對於所有 Handler 都是一樣的。重複的工作總是可以抽象出來,net/http 也正這麼做了,它提供了 http.HandleFunc 方法,允許直接把特定型別的函式作為 handler。上面的程式碼可以改成:

package main

import (
    "io"
    "net/http"
)

func helloHandler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":12345", nil)
}複製程式碼
其實,HandleFunc 只是一個介面卡,

// 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 object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}複製程式碼
自動給 f 函式添加了 HandlerFunc 這個殼,最終呼叫的還是 ServerHTTP,只不過會直接使用 f(w, r)。這樣封裝的好處是:使用者可以專注於業務邏輯的編寫,省去了很多重複的程式碼處理邏輯。如果只是簡單的 Handler,會直接使用函式;如果是需要傳遞更多資訊或者有複雜的操作,會使用上部分的方法。

如果需要我們自己寫的話,是這樣的:

package main

import (
    "io"
    "net/http"
)

func helloHandler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    // 通過 HandlerFunc 把函式轉換成 Handler 介面的實現物件
    hh := http.HandlerFunc(helloHandler)
    http.Handle("/", hh)
    http.ListenAndServe(":12345", nil)
}複製程式碼
3. 預設
大部分的伺服器邏輯都需要使用者編寫對應的 Handler,不過有些 Handler 使用頻繁,因此 net/http 提供了它們的實現。比如負責檔案 hosting 的 FileServer、負責 404 的NotFoundHandler 和 負責重定向的RedirectHandler。下面這個簡單的例子,把當前目錄所有檔案 host 到服務端:

package main

import (
    "net/http"
)

func main() {
    http.ListenAndServe(":12345", http.FileServer(http.Dir(".")))
}複製程式碼
強大吧!只要一行邏輯程式碼就能實現一個簡單的靜態檔案伺服器。從這裡可以看出一件事:http.ListenAndServe 第二個引數就是一個 Handler 函式(請記住這一點,後面有些內容依賴於這個)。

執行這個程式,在瀏覽器中開啟 http://127.0.0.1:12345,可以看到所有的檔案,點選對應的檔案還能看到它的內容。


其他兩個 Handler,這裡就不再舉例子了,讀者可以自行參考文件。

4. 路由
雖然上面的程式碼已經工作,並且能實現很多功能,但是實際開發中,HTTP 介面會有許多的 URL 和對應的 Handler。這裡就要講 net/http 的另外一個重要的概念:ServeMux。Mux 是 multiplexor 的縮寫,就是多路傳輸的意思(請求傳過來,根據某種判斷,分流到後端多個不同的地方)。ServeMux 可以註冊多了 URL 和 handler 的對應關係,並自動把請求轉發到對應的 handler 進行處理。我們還是來看例子吧:

package main

import (
    "io"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello, world!\n")
}

func echoHandler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, r.URL.Path)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", helloHandler)
    mux.HandleFunc("/", echoHandler)

    http.ListenAndServe(":12345", mux)
}複製程式碼
這個伺服器的功能也很簡單:如果在請求的 URL 是 /hello,就返回 hello, world!;否則就返回 URL 的路徑,路徑是從請求物件 http.Requests 中提取的。


這段程式碼和之前的程式碼有兩點區別:

通過 NewServeMux 生成了 ServerMux 結構,URL 和 handler 是通過它註冊的
http.ListenAndServe 方法第二個引數變成了上面的 mux 變數
還記得我們之前說過,http.ListenAndServe 第二個引數應該是 Handler 型別的變數嗎?這裡為什麼能傳過來 ServeMux?嗯,估計你也猜到啦:ServeMux 也是是 Handler 介面的實現,也就是說它實現了 ServeHTTP 方法,我們來看一下:

type ServeMux struct {
        // contains filtered or unexported fields
}

func NewServeMux() *ServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)複製程式碼
哈!果然,這裡的方法我們大都很熟悉,除了 Handler() 返回某個請求的 Handler。Handle 和 HandleFunc 這兩個方法 net/http 也提供了,後面我們會說明它們之間的關係。而 ServeHTTP 就是 ServeMux 的核心處理邏輯:根據傳遞過來的 Request,匹配之前註冊的 URL 和處理函式,找到最匹配的項,進行處理。可以說 ServeMux 是個特殊的 Handler,它負責路由和呼叫其他後端 Handler 的處理方法。

關於ServeMux ,有幾點要說明:

URL 分為兩種,末尾是 /:表示一個子樹,後面可以跟其他子路徑; 末尾不是 /,表示一個葉子,固定的路徑
以/ 結尾的 URL 可以匹配它的任何子路徑,比如 /images 會匹配 /images/cute-cat.jpg
它採用最長匹配原則,如果有多個匹配,一定採用匹配路徑最長的那個進行處理
如果沒有找到任何匹配項,會返回 404 錯誤
ServeMux 也會識別和處理 . 和 ..,正確轉換成對應的 URL 地址
你可能會有疑問?我們之間為什麼沒有使用 ServeMux 就能實現路徑功能?那是因為 net/http 在後臺預設建立使用了 DefaultServeMux。

5. 深入
嗯,上面基本覆蓋了編寫 HTTP 服務端需要的所有內容。這部分就分析一下,它們的原始碼實現,加深理解,以後遇到疑惑也能通過原始碼來定位和解決。

Server
首先來看 http.ListenAndServe():

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}複製程式碼
這個函式其實也是一層封裝,建立了 Server 結構,並呼叫它的 ListenAndServe 方法,那我們就跟進去看看:

// 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
    ......
}

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":http" is used.
func (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)})
}複製程式碼
Server 儲存了執行 HTTP 服務需要的引數,呼叫 net.Listen 監聽在對應的 tcp 埠,tcpKeepAliveListener 設定了 TCP 的 KeepAlive 功能,最後呼叫 srv.Serve()方法開始真正的迴圈邏輯。我們再跟進去看看 Serve 方法:

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each.  The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    // 迴圈邏輯,接受請求並處理
    for {
         // 有新的連線
        rw, e := l.Accept()
        if e != nil {
            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
         // 建立 Conn 連線
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
         // 啟動新的 goroutine 進行處理
        go c.serve()
    }
}複製程式碼
最上面的註釋也說明了這個方法的主要功能:

接受 Listener l 傳遞過來的請求
為每個請求建立 goroutine 進行後臺處理
goroutine 會讀取請求,呼叫 srv.Handler
func (c *conn) serve() {
    origConn := c.rwc // copy it before it's set nil on Close or Hijack

      ...

    for {
        w, err := c.readRequest()
        if c.lr.N != c.server.initialLimitedReaderSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }

         ...

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining.  We could let them all process
        // in parallel even if their responses need to be serialized.
        serverHandler{c.server}.ServeHTTP(w, w.req)

        w.finishRequest()
        if w.closeAfterReply {
            if w.requestBodyLimitHit {
                c.closeWriteAndWait()
            }
            break
        }
        c.setState(c.rwc, StateIdle)
    }
}複製程式碼
看到上面這段程式碼 serverHandler{c.server}.ServeHTTP(w, w.req)這一句了嗎?它會呼叫最早傳遞給 Server 的 Handler 函式:

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)
}複製程式碼
哇!這裡看到 DefaultServeMux 了嗎?如果沒有 handler 為空,就會使用它。handler.ServeHTTP(rw, req),Handler 介面都要實現 ServeHTTP 這個方法,因為這裡就要被呼叫啦。

也就是說,無論如何,最終都會用到 ServeMux,也就是負責 URL 路由的傢伙。前面也已經說過,它的 ServeHTTP 方法就是根據請求的路徑,把它轉交給註冊的 handler 進行處理。這次,我們就在原始碼層面一探究竟。

ServeMux
我們已經知道,ServeMux 會以某種方式儲存 URL 和 Handlers 的對應關係,下面我們就從程式碼層面來解開這個祕密:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry  // 存放路由資訊的字典!\(^o^)/
    hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}複製程式碼
沒錯,資料結構也比較直觀,和我們想象的差不多,路由資訊儲存在字典中,接下來就看看幾個重要的操作:路由資訊是怎麼註冊的?ServeHTTP 方法到底是怎麼做的?路由查詢過程是怎樣的?

// 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 " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

    // 建立 `muxEntry` 並新增到路由字典中
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }

    // 這是一個很有用的小技巧,如果註冊了 `/tree/`, `serveMux` 會自動新增一個 `/tree` 的路徑並重定向到 `/tree/`。當然這個 `/tree` 路徑會被使用者顯示的路由資訊覆蓋。
    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
            // In pattern, at least the last character is a '/', so
            // strings.Index can't be -1.
            path = pattern[strings.Index(pattern, "/"):]
        }
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}
    }
}複製程式碼
路由註冊沒有什麼特殊的地方,很簡單,也符合我們的預期,注意最後一段程式碼對類似 /tree URL 重定向的處理。

// 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)
}複製程式碼
好吧,ServeHTTP 也只是通過 mux.Handler(r) 找到請求對應的 handler,呼叫它的 ServeHTTP 方法,程式碼比較簡單我們就顯示了,它最終會呼叫 mux.match() 方法,我們來看一下它的實現:

// Does path match pattern?
func pathMatch(pattern, path string) bool {
    if len(pattern) == 0 {
        // should not happen
        return false
    }
    n := len(pattern)
    if pattern[n-1] != '/' {
        return pattern == path
    }
    // 匹配的邏輯很簡單,path 前面的字元和 pattern 一樣就是匹配
    return len(path) >= n && path[0:n] == pattern
}

// 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) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
         // 最長匹配的邏輯在這裡
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}複製程式碼
match 會遍歷路由資訊字典,找到所有匹配該路徑最長的那個。路由部分的程式碼解釋就到這裡了,最後回答上面的一個問題:http.HandleFunc 和 ServeMux.HandlerFunc 是什麼關係?

// 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) }

// 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)
}複製程式碼
原來是直接通過 DefaultServeMux 呼叫對應的方法,到這裡上面的一切都串起來了!

Request
最後一部分,要講講 Handler 函式接受的兩個引數:http.Request 和 http.ResponseWriter。

Request 就是封裝好的客戶端請求,包括 URL,method,header 等等所有資訊,以及一些方便使用的方法:

// A Request represents an HTTP request received by a server
// or to be sent by a client.
//
// The field semantics differ slightly between client and server
// usage. In addition to the notes on the fields below, see the
// documentation for Request.Write and RoundTripper.
type Request struct {
    // Method specifies the HTTP method (GET, POST, PUT, etc.).
    // For client requests an empty string means GET.
    Method string

    // URL specifies either the URI being requested (for server
    // requests) or the URL to access (for client requests).
    //
    // For server requests the URL is parsed from the URI
    // supplied on the Request-Line as stored in RequestURI.  For
    // most requests, fields other than Path and RawQuery will be
    // empty. (See RFC 2616, Section 5.1.2)
    //
    // For client requests, the URL's Host specifies the server to
    // connect to, while the Request's Host field optionally
    // specifies the Host header value to send in the HTTP
    // request.
    URL *url.URL

    // The protocol version for incoming requests.
    // Client requests always use HTTP/1.1.
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0

    // A header maps request lines to their values.
    // If the header says
    //
    //    accept-encoding: gzip, deflate
    //    Accept-Language: en-us
    //    Connection: keep-alive
    //
    // then
    //
    //    Header = map[string][]string{
    //        "Accept-Encoding": {"gzip, deflate"},
    //        "Accept-Language": {"en-us"},
    //        "Connection": {"keep-alive"},
    //    }
    //
    // HTTP defines that header names are case-insensitive.
    // The request parser implements this by canonicalizing the
    // name, making the first character and any characters
    // following a hyphen uppercase and the rest lowercase.
    //
    // For client requests certain headers are automatically
    // added and may override values in Header.
    //
    // See the documentation for the Request.Write method.
    Header Header

    // Body is the request's body.
    //
    // For client requests a nil body means the request has no
    // body, such as a GET request. The HTTP Client's Transport
    // is responsible for calling the Close method.
    //
    // For server requests the Request Body is always non-nil
    // but will return EOF immediately when no body is present.
    // The Server will close the request body. The ServeHTTP
    // Handler does not need to.
    Body io.ReadCloser

    // ContentLength records the length of the associated content.
    // The value -1 indicates that the length is unknown.
    // Values >= 0 indicate that the given number of bytes may
    // be read from Body.
    // For client requests, a value of 0 means unknown if Body is not nil.
    ContentLength int64

    // TransferEncoding lists the transfer encodings from outermost to
    // innermost. An empty list denotes the "identity" encoding.
    // TransferEncoding can usually be ignored; chunked encoding is
    // automatically added and removed as necessary when sending and
    // receiving requests.
    TransferEncoding []string

    // Close indicates whether to close the connection after
    // replying to this request (for servers) or after sending
    // the request (for clients).
    Close bool

    // For server requests Host specifies the host on which the
    // URL is sought. Per RFC 2616, this is either the value of
    // the "Host" header or the host name given in the URL itself.
    // It may be of the form "host:port".
    //
    // For client requests Host optionally overrides the Host
    // header to send. If empty, the Request.Write method uses
    // the value of URL.Host.
    Host string

    // Form contains the parsed form data, including both the URL
    // field's query parameters and the POST or PUT form data.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores Form and uses Body instead.
    Form url.Values

    // PostForm contains the parsed form data from POST or PUT
    // body parameters.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores PostForm and uses Body instead.
    PostForm url.Values

    // MultipartForm is the parsed multipart form, including file uploads.
    // This field is only available after ParseMultipartForm is called.
    // The HTTP client ignores MultipartForm and uses Body instead.
    MultipartForm *multipart.Form

    ...

    // RemoteAddr allows HTTP servers and other software to record
    // the network address that sent the request, usually for
    // logging. This field is not filled in by ReadRequest and
    // has no defined format. The HTTP server in this package
    // sets RemoteAddr to an "IP:port" address before invoking a
    // handler.
    // This field is ignored by the HTTP client.
    RemoteAddr string
    ...
}複製程式碼
Handler 需要知道關於請求的任何資訊,都要從這個物件中獲取,一般不會直接修改這個物件(除非你非常清楚自己在做什麼)!

ResponseWriter
ResponseWriter 是一個介面,定義了三個方法:

Header():返回一個 Header 物件,可以通過它的 Set() 方法設定頭部,注意最終返回的頭部資訊可能和你寫進去的不完全相同,因為後續處理還可能修改頭部的值(比如設定 Content-Length、Content-type 等操作)
Write(): 寫 response 的主體部分,比如 html 或者 json 的內容就是放到這裡的
WriteHeader():設定 status code,如果沒有呼叫這個函式,預設設定為 http.StatusOK, 就是 200 狀態碼
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface {
    // Header returns the header map that will be sent by WriteHeader.
    // Changing the header after a call to WriteHeader (or Write) has
    // no effect.
    Header() Header

    // Write writes the data to the connection as part of an HTTP reply.
    // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
    // before writing the data.  If the Header does not contain a
    // Content-Type line, Write adds a Content-Type set to the result of passing
    // the initial 512 bytes of written data to DetectContentType.
    Write([]byte) (int, error)

    // WriteHeader sends an HTTP response header with status code.
    // If WriteHeader is not called explicitly, the first call to Write
    // will trigger an implicit WriteHeader(http.StatusOK).
    // Thus explicit calls to WriteHeader are mainly used to
    // send error codes.
    WriteHeader(int)
}複製程式碼
實際上傳遞給 Handler 的物件是:

// A response represents the server side of an HTTP response.
type response struct {
    conn          *conn
    req           *Request // request for this response
    wroteHeader   bool     // reply header has been (logically) written
    wroteContinue bool     // 100 Continue response was written

    w  *bufio.Writer // buffers output in chunks to chunkWriter
    cw chunkWriter
    sw *switchWriter // of the bufio.Writer, for return to putBufioWriter

    // handlerHeader is the Header that Handlers get access to,
    // which may be retained and mutated even after WriteHeader.
    // handlerHeader is copied into cw.header at WriteHeader
    // time, and privately mutated thereafter.
    handlerHeader Header
    ...
    status        int   // status code passed to WriteHeader
    ...
}複製程式碼
它當然實現了上面提到的三個方法,具體程式碼就不放到這裡了,感興趣的可以自己去看。

6. 擴充套件
雖然 net/http 提供的各種功能已經滿足基本需求了,但是很多時候還不夠方便,比如:

不支援 URL 匹配,所有的路徑必須完全匹配,不能捕獲 URL 中的變數,不夠靈活
不支援 HTTP 方法匹配
不支援擴充套件和巢狀,URL 處理都在都一個 ServeMux 變數中
雖然這些都可以自己手動去碼,但實在很不方便。這部分看看有哪些三方的包,都提供了哪些額外的功能。

alice
alice 的功能很簡單——把多個 handler 串聯起來,有請求過來的時候,逐個通過這個 handler 進行處理。

alice.New(Middleware1, Middleware2, Middleware3).Then(App)複製程式碼
Gorilla Mux
Gorilla 提供了很多網路有關的元件, Mux 就是其中一個,負責 HTTP 的路由功能。這個元件彌補了上面提到的 ServeMux 的一些缺陷,支援的功能有:

更多的匹配型別:HTTP 方法、query 欄位、URL host 等
支援正則表示式作為 URL path 的一部分,也支援變數提取功能
支援子路由,也就是路由的巢狀,SubRouter 可以實現路由資訊的傳遞
並且和 ServeMux 完全相容
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)複製程式碼
httprouter
httprouter 和 mux 一樣,也是擴充套件了自帶 ServeMux 功能的路由庫。它的主要特點是速度快、記憶體使用少、可擴充套件性高(使用 radix tree 資料結構進行路由匹配,路由項很多的時候速度也很快)。

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
    "log"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}複製程式碼
negroni
http middleware 庫,支援巢狀的中介軟體,能夠和其他路由庫相容。同時它也自帶了不少 middleware 可以使用,比如Recovery、Logger、Static。

router := mux.NewRouter()
router.HandleFunc("/", HomeHandler)

n := negroni.New(Middleware1, Middleware2)
// Or use a middleware with the Use() function
n.Use(Middleware3)
// router goes last
n.UseHandler(router)

http.ListenAndServe(":3001", n)複製程式碼
7. 參考
這篇文章參考了以下資料:

golang net/http 官方文件
net/http 原始碼
A Recap of Request Handling in Go
Not Another Go/Golang net/http Tutorial
the http handlerfunc wrapper technique in golang
why do all golang url routers suck