TOC

Golang HTTP Server

最简模式

package main

import (
    "fmt"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}

func main() {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":8080", nil)
}

重点是记住这两点:

  1. func hello(w http.ResponseWriter, req *http.Request)
  2. http.HandleFunc 注册一个函数到指定路径上

ListenAndServe 流程分析

https://github.com/golang/go/blob/master/src/net/http/server.go#L3240
https://github.com/golang/go/blob/master/src/net/http/server.go#L2976

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

主要的逻辑:

  1. net.Listen("tcp", addr) -> net.Listener
  2. for 循环:
  3. net.Listener.Accept() -> net.Conn
  4. http.Server.newConn(conn) -> http.Conn
  5. go http.Conn.serve(ctx)
  6. goroutine 中又是一个 for 循环:
  7. http.Conn.readRequest(ctx) -> http.response
  8. serverHandler{c.server}.ServeHTTP(w, w.req)
  9. 最后这个 serverHandler.ServeHTTP 就比较关键了:
    优先采用 http.Server 上的 Handler;
    如果没有,则用默认的 http.DefaultServeMux 实现简单的 URL 路由。
    这也是为什么 ListenAndServe 传了个 nil 进来,srv.Handlernil 就用 DefaultServeMux
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
}

func (mux *ServeMux) match(path string) (h Handler, pattern string)
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool)
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

DefaultServeMux

// ==================================================================
// 封装 handler,添加到路由表 =========================================
// ==================================================================
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    // https://github.com/golang/go/blob/master/src/net/http/server.go#L2505
    // func (mux *ServeMux) Handle(pattern string, handler Handler)
    // 把 handler 注册到 mux.m (map[string]muxEntry) 上
    // muxEntry{h: handler, pattern: pattern}
    mux.Handle(pattern, HandlerFunc(handler))
}

// 封装普通函数为 Handler
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// ==================================================================
// 路由匹配 =========================================================
// ==================================================================
ServeHTTP -> Handler -> handler -> match
          -> redirectToPathSlash -> shouldRedirectRLocked -> 跳转(http.RedirectHandler)

默认路由规则:

  1. 优先完整匹配
  2. URL 前缀匹配,最长的路由规则优先

另一种方式 (http.Handle)

不用 http.HandleFunc,而是传入一个 Handler(实现 ServeHTTP 方法的结构体)。

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.Handle("/", &HelloHandler{"hello world\n"})

    http.ListenAndServe(":8080", nil)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

http.Handlehttp.HandleFunc 都只是给默认路由(DefaultServeMux)上注册一个规则,知道这一点就可以了。

重点是 mux.HandleHandler 接口。

还可以这样

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct {
    content string
}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, h.content)
}

func main() {
    http.ListenAndServe(":8080", &HelloHandler{"hello world\n"})
}

知道背后的流程就豁然开朗了。

当然,这样也是可以的:

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello world\n")
}
func main() {
    http.ListenAndServe(":8080", http.HandlerFunc(hello))
}

整理总结

  • http.Conn
  • http.Server
  • 到处都是 Hander(接口),路由(mux)也是 Handler
  • func(w http.ResponseWriter, req *http.Request)

实现正则路由

GitHub 上一搜索 golang mux golang router 就有,这是几个可以参考的方案:

  1. gorilla/mux shield.io
  2. julienschmidt/httprouter shield.io
  3. go-chi/chi shield.io
  4. celrenheit/lion shield.io
  5. beego/mux shield.io

我自己实现一个简单的路由机制,参考 Django:

<name> OR <converter:name>

其中:converter 内置支持 str, int, slug, uuid, path, 可以自定义,默认是 str; name 可以是合法的 Go 变量名。


中间件

通过 Handler 套娃实现中间件:

type LogMiddleware struct {
    handler Handler
}
func (this LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    this.handler.ServeHTTP(w, r)
    fmt.Printf("%s %s %v\n", r.Method, r.URL.RequestURI(), time.Since(start))
}
mux := ...
http.ListenAndServe(":8000", LogMiddleware{mux})

系统内置 Handler:

grep -E "func.+ServeHTTP" src/net/http -R | grep -Fv "_test.go"
src/net/http/cgi/host.go:func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/fs.go:func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/httputil/reverseproxy.go:func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/pprof/pprof.go:func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
src/net/http/server.go:func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/server.go:func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (h initALPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/triv.go:func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
src/net/http/triv.go:func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {

有待分析。

优雅重启


参考资料与拓展阅读

如果你有魔法,你可以看到一个评论框~