net/http

web 示例:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello World")
    })
    http.ListenAndServe("localhost:8080", nil)
}
  • 进行逻辑处理
func(w http.ResponseWriter, r *http.Request){ 
    dosomeing
}
  • 调用HandleFunc()注册处理器函数
  • 调用ListenAndServer()监听localhost:8080并提供服务

HandleFunc()与Handle()实现原理

调用层次

image-20220627201713028

http.HandleFunc()http.Handle()的底层实现都依赖于ServerMux.Handle()http.HandleFunc()自己对ServeMux.Handle()进行了封装,封装为ServeMux.HandleFunc() 并定义了自己的数据结构HandlerFunc该结构是函数类型并且实现了Handler 接口。Mux.Handle() 接受一个类型为Handler的参数,所以调用mux.Handle()必须实现Handler接口。http.HandleFunc()通过自己的数据结构HandlerFunc实现了Handler接口所以调用http.HandleFunc()时不用自己去实现Handler接口,直接向http.HandleFUnc()传入pattern stringfunc(w http.ResponseWriter, r *http.Request){}即可;http.Handle()直接调用Mux.Handle() 所以需要调用者自己去定义一个数据结构并且该数据结构要实现Handler接口,在使用该数据结构前对其进行初始化,然后将pattern string 与该数据结构传入http.Handle()

实现

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

当我们调用 http.HandleFunc() 注册处理器时,标准库会使用DefaultServeMux默认的 HTTP 服务器 处理请求。

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry // 处理器键值对map
    es    []muxEntry // slice of entries sorted from longest to shortest.从最长到最短排序的entries切片
    hosts bool       // whether any patterns contain hostnames 是否有任何模式包含主机名
}

type muxEntry struct {
    h       Handler
    pattern string
}

// /DefaultServeMux 是 Serve 使用的默认 ServeMux
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

http.HandleFunc() 在底层实现上与http. Handle()一样,都使用Handle方法。不同的是http.HandleFunc()在调用Handle前需要对handler 进行强制转化为HandlerFuncHandlerFunc实现了ServeHTTP方法,而Handle()需要自己实现ServeHTTP方法。

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    // 强制转换 将handler转换为HandlerFunc
    mux.Handle(pattern, HandlerFunc(handler))
}

HandlerFunc以函数类型func(ResponseWriter, *Request)为底层类型,为HandlerFunc实现了ServeHTTP方法即HandlerFunc实现了Handler接口。Go语言只要某个数据结构实现了接口的方法集就实现了这个接口。

只要你的handler函数签名是:

func (ResponseWriter, *Request)

那么这个handlerhttp.HandlerFunc()就有了一致的函数签名,可以将该handler()函数进行类型转换,转为http.HandlerFunc。而http.HandlerFunc实现了http.Handler这个接口。在http库需要调用你的handler函数来处理http请求时,会调用HandlerFunc()ServeHTTP()函数,可见一个请求的基本调用链是这样的

h = getHandler() => h.ServeHTTP(w, r) => h(w, r)
// 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类型只是为了方便注册函数类型的处理器。我们可以直接声明一个实现了Handler接口的数据结构,然后使用http.Handle()注册该数据结构的示例:

package main

import (
    "fmt"
    "net/http"
)

// func main() {
//     http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
//         fmt.Fprintln(w, "Hello World")
//     })
//     http.ListenAndServe("localhost:8080", nil)
// }

type Hello struct{}

func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello World")
}

func main() {
    // http.Hangle接受两个参数:pattern string, handler Handler
    // 第二参数为handler,我们自己定义一个实现了接口handler的数据结构,在这里需要对该数据结构进行初始化。
    // h := Hello{}
    // http.Handle("/hello", h)
    http.Handle("/hello", Hello{})
    http.ListenAndServe("localhost:8080", nil)
}

为了区分,我们将通过HandleFunc()注册的称为处理函数,通过Handle注册的称为处理器。官方解释为Handle registers the handler for the given pattern in the DefaultServeMux HandleFunc registers the handler function for the given pattern in the DefaultServeMux

Handler响应 HTTP 请求,Handler为处理器函数。

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

Handle方法只接受类型为接口Handler的参数。Handle根据pattern参数在ServeMux(多路复用器)注册处理器函数。ServeMux根据最长匹配原则进行匹配Handlerpattern,所以在注册时需要对pattern进行排序。

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

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
    n := len(es)
    i := sort.Search(n, func(i int) bool {
        return len(es[i].pattern) < len(e.pattern)
    })
    if i == n {
        return append(es, e)
    }
    // we now know that i points at where we want to insert
    es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
    copy(es[i+1:], es[i:])      // Move shorter entries down
    es[i] = e
    return es
}

ListebAndServe

调用层次

image-20220628114037105

实现

ListenAndServe 监听 TCP 网络地址 addr,然后调用 handler 来处理传入连接上的请求。 接受的连接被配置为启用 TCP keep-alives。 handler通常为为 nil,在这种情况下使用 DefaultServeMux。 ListenAndServe 总是返回一个非零错误。

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

ListenAndServe创建了一个ServerServer定义了运行 HTTP 服务器的参数。Server 的零值是有效配置。我们可以使用这些字段来调节 Web 服务器的参数。

type Server struct {
    // Addr 可选地指定服务器要监听的 TCP 地址,
    // 以"host:port"的形式。如果为空,则使用":http" (port 80)
    Addr string

    // 要调用的handler,如果为 nil,则为 http.DefaultServeMux
    Handler Handler 

    // 通过ServerTLS和ListenAndServerTLS TLSConfig 可选地提供一个 TLS 配置以供使用
    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

    BaseContext func(net.Listener) context.Context

    ConnContext func(ctx context.Context, c net.Conn) context.Context

    inShutdown atomicBool 

    disableKeepAlives int32     
    nextProtoOnce     sync.Once 
    nextProtoErr      error    

    mu         sync.Mutex
    listeners  map[*net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}
//ListenAndServe 侦听 TCP 网络地址 srv.Addr
//调用handler处理传入连接上的请求。
//接受的连接配置为启用TCP keep-alives。
//如果 srv.Addr 为空,则使用 ":http"。
//ListenAndServe 总是返回一个非零错误。 
//Shutdown 或 Close 后,返回的错误为 ErrServerClosed。
func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    // func net.Listen(network string, address string) (net.Listener, error)
    // 监听本地网络地址上的通告。
    // network为"tcp","tcp4","tcp6","unix" 或 "unixpacket"。
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    // func (srv *Server) Serve(l net.Listener) error
    return srv.Serve(ln)
}

在该方法中,先调用net.Listen()监听端口,将返回的net.Listener作为参数调用Server.Serve()方法

//Serve 接受 Listener l 上的传入连接,为每个连接创建一个新的服务 goroutine。服务 goroutine 读取请求,然后调用 srv.Handler 来回复它们。
//
//仅当侦听器返回 *tls.Conn 连接并且它们在 TLS 中配置为“h2”时才启用 HTTP/2 支持
//Config.NextProtos。
//
//Serve 总是返回一个非 nil 错误并关闭 l。
//Shutdown 或 Close 后,返回的错误为 ErrServerClosed。
func (srv *Server) Serve(l net.Listener) error {
    // 未包装的listener
    if fn := testHookServerServe; fn != nil {
        fn(srv, l) // call hook with unwrapped listener
    }

    origListener := l
    // 幂等
    l = &onceCloseListener{Listener: l}
    defer l.Close()

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    if !srv.trackListener(&l, true) {
        return ErrServerClosed
    }
    defer srv.trackListener(&l, false)

    baseCtx := context.Background()
    if srv.BaseContext != nil {
        baseCtx = srv.BaseContext(origListener)
        if baseCtx == nil {
            panic("BaseContext returned a nil context")
        }
    }

    var tempDelay time.Duration // how long to sleep on accept failure

    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    // 使用一个无限的for循环,不停地调用Listener.Accept()方法接受新连接,开启新 goroutine 处理新连接
    for {
        rw, err := l.Accept()
        if err != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            // 指数退避策略
            // 如果l.Accept()调用返回错误,我们判断该错误是不是临时性地(ne.Temporary())。如果是临时性错误,Sleep一小段时间后重试,每发生一次临时性错误,Sleep的时间翻倍,最多Sleep 1s
            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
        // 获得新连接后,将其封装成一个conn对象
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        go c.serve(connCtx)
    }
}

serve()方法不停地读取客户端发送地请求,创建serverHandler对象调用其ServeHTTP()方法去处理请求,然后做一些清理工作。

//提供新的连接。
func (c *conn) serve(ctx context.Context) {
  for {
    w, err := c.readRequest(ctx)
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.finishRequest()
  }
}

serverHandler只是一个中间的辅助结构

关键代码

type serverHandler struct {
    srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    handler.ServeHTTP(rw, req)
}
//serverHandler 委托给服务器的 Handler 或
//DefaultServeMux 并且还处理“OPTIONS *”请求。
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 从Server对象中获取Handler,这个Handler就是调用http.ListenAndServe()时传入的第二个参数
    handler := sh.srv.Handler
    
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }

    if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
        var allowQuerySemicolonsInUse int32
        req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
            atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
        }))
        defer func() {
            if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
                sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
            }
        }()
    }

    handler.ServeHTTP(rw, req)
}
//ServeHTTP 将请求分派给其模式与请求 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
    }
    // mux.Handler(r)通过请求的路径信息查找处理器
    h, _ := mux.Handler(r)
    // 调用处理器的ServeHTTP()方法处理请求
    h.ServeHTTP(w, r)
}

Handler()通过URL信息查找handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // 剥离端口
    // stripHostPort 返回 h,不带任何尾随 ":<port>"
      host := stripHostPort(r.Host)
    return mux.handler(host, r.URL.Path)
}

handlerHandler的主要实现:

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
}

在给定路径字符串的handler map上查找handler,最长匹配模式。

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.
    // 检查最长的有效匹配。 mux.es 包含所有以 /结尾的模式,从最长到最短排序。
    // 只要注册了/根路径处理,所有未匹配到的路径最终都会交给/路径处理
    // 所以在注册handler时需要对pattern进行排序从最长到最短
    for _, e := range mux.es {
        // func strings.HasPrefix(s string, prefix string) bool
        // HasPrefix 测试字符串 s 是否以prefix开头。
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

ServeMux

示例

http.HandleFunc()/http.Handle()都是将处理器/函数注册到ServeMux的默认对象DefaultServeMux上。使用DefaultServeMux不可控。

一来Server参数都使用了默认值,二来第三方库也可能使用这个默认对象注册一些处理,容易冲突。更严重的是,我们在不知情中调用http.ListenAndServe()开启 Web 服务,那么第三方库注册的处理逻辑就可以通过网络访问到,有极大的安全隐患。所以,除非在示例程序中,否则建议不要使用默认对象。

我们可以使用http.NewServeMux()创建一个新的ServeMux对象,然后创建http.Server对象定制参数,用ServeMux对象初始化ServerHandler字段,最后调用Server.ListenAndServe()方法开启 Web 服务:

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", index)
  mux.Handle("/hello", Hello(struct{}))
    
  server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  20 * time.Second,
    WriteTimeout: 20 * time.Second,
  }
  server.ListenAndServe()
}

img

img

实现

NewServeMux()分配并返回一个新的ServeMux

// func new(Type) *Type
//新的内置函数分配内存。第一个参数是一个类型, 不是一个值,返回的值是一个指向新的指针 分配了该类型的零值。
func NewServeMux() *ServeMux { return new(ServeMux) }

ServeMux实现了ServeHTTP方法即ServeMux实现了Handler,故在设置server时可以将ServeMux当作HAndler传入。Server.Handler = nilDefaultServeMux,Server.Handler = mux时使用调用者定义的多路复用器。

中间件(middleware)

中间件对请求进行一些预处理或后处理。它位于Go web服务器和处理器函数之间。

image-20220628142302418

任何方法实现了ServeHTTP,即是一个合法的http.Handler

使用中间件可以剥离非业务逻辑。

中间件通过函数闭包实现。

中间件通过包装handler再返回一个handler

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)
        logger.Println(timeElapsed)
    })
}

func main() {
    http.Handle("/", timeMiddleware(http.HandlerFunc(hello)))
    err := http.ListenAndServe(":8080", nil)
    ...
}
customizedHandler = logger(timeout(ratelimit(helloHandler)))

函数链执行过程中的上下文:

image-20220628135013469

链接中间件

customizedHandler = logger(timeout(ratelimit(helloHandler)))

写法太繁琐,我们可以进行简化。

r = NewRouter()
r.Use(logger)
r.Use(timeout)
r.Use(ratelimit)
r.Add("/", helloHandler)

可以使用Use()来增加或删除中间件。

实现
type middleware func(http.Handler) http.Handler

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

func NewRouter() *Router{
    return &Router{}
}

// 接受中间件切片
func (r *Router) Use(m middleware) {
    r.middlewareChain = append(r.middlewareChain, m)
}

// 运行m1(m2(m3(m4(m5(handler)))))
func (r *Router) Add(route string, h http.Handler) {
    var mergedHandler = h
    
    // for 循环将从中间件的 Slice中循环遍历中间件,并将其自身包裹在传递给函数的 mergedHandler周围。
    for i := len(r.middlewareChain) - 1; i >= 0; i-- {
        mergedHandler = r.middlewareChain[i](mergedHandler)
    }

    r.mux[route] = mergedHandler
}
参考:
Go 每日一库之 net/http(基础和中间件)
go高级编程

zhaobuqi
1 声望1 粉丝