ZYB ARTICLES REPOS

go语言http库详解

调用过程

最简单的http服务器代码实现如下

package main

import (
	"net/http"
)

func main() {
    http.ListenAndServe("", nil)
}

这个段代码会启动一个监听80端口的web服务器,但是无论访问任何地址都会返回404 page not found,因为我们还没添加相应的处理程序。

其中http.ListenAndServe的实现如下:

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

它会创建一个http.Server实例,然后调用它的ListenAndServer接口。如果用户指定了http服务器监听的端口,则用用户指定的,否则监听”:http”也就是”:80”。

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

http.ServerServe会接受每一个客户端的请求,并对每个请求调用serve

func (srv *Server) Serve(l net.Listener) error {
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()

		connCtx := ctx
        if cc := srv.ConnContext; cc != nil {
		    connCtx = cc(connCtx, rw)
        }

        c := srv.newConn(rw)
		go c.serve(connCtx)
	}
}
func (c *conn) serve(ctx context.Context) {
    // ...
	w, err := c.readRequest(ctx)

    serverHandler{c.server}.ServeHTTP(w, w.req)
    // ...
}

serve会通过serverHandler{c.server}的方式给http.Server实例附加一个ServeHTTP接口,并调用它,其实现方法如下:

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

在该函数中会使用http.Server.Handler指定的处理器来接力处理。这个处理器是实现了如下Handler接口的处理器:

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

而如果传入的Handlernil,即最开头程序的http.ListenAndServe("", nil)第二个参数为nil,则将DefaultServeMux作为Handler,因为DefaultServeMuxhttp.ServeMux类型,而http.ServeMux也实现了这个接口

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

http.ServeMux这个多路复用器实现的Handler接口中,它会根据请求里的路径匹配对应的处理器,然后调用处理的Handler接口。

所以DefaultServeMux既是一个多路复用器,也是个处理器。因此最开头提到的程序与下列代码是等价的

package main

import (
	"net/http"
)

func main() {
    server := http.Server {
        Addr: ":80",
        Handler: nil,
    }
    server.ListenAndServe()
}

处理器

所以我们可以为这个程序添加一个HelloHandler处理器

package main

import (
    "fmt"
	"net/http"
)

type AHandler struct {}

func (h AHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler A")
}

func main() {
    ha := AHandler{ }
    server := http.Server {
        Addr: ":80",
        Handler: ha,
    }
    server.ListenAndServe()
}

这样,无论访问任何页面,都会得到Handler A的结果。其实不太实用,那如果我们想针对不同的地址调用不同的处理器呢?

多个处理器

其实可以利用多路复用器,上文提到多路复用器其实也实现了Handler接口,在这个接口的ServeHTTP函数中它会根据请求中的路径匹配到一个对应的处理器,并调用。因此我们只需要创建一个多路复用器,再将不同的Handler注册到多路复用器里,就可以实现根据不同的请求地址调用不同的处理器。

package main

import (
    "fmt"
	"net/http"
)

type AHandler struct {}
type BHandler struct {}

func (h AHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler A")
}
func (h BHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler B")
}

func main() {

    ha := AHandler{ }
    hb := BHandler{ }

    // 创建一个多路复用器
    m := http.NewServeMux()

    // 将处理器注册进复用器
    m.Handle("/a", ha)
    m.Handle("/b", hb)

    server := http.Server {
        Addr: ":80",
        Handler: m, // 因为多路复用器也实现了Handler(处理器)接口
    }
    server.ListenAndServe()
}

这样访问/a和访问/b就会得到Handler AHandler B两个不同的结果,访问其它,则会得到404 page not found。这里不创建多路复用器直接注册也可以,因为http库会默认使用DefaultServeMux

func main() {

    ha := AHandler{ }
    hb := BHandler{ }

    // 将处理器注册进复用器DefaultServeMux里
    http.Handle("/a", ha)
    http.Handle("/b", hb)

    server := http.Server {
        Addr: ":80",
        Handler: nil, // 会调用DefaultServeMux
    }
    server.ListenAndServe()
}

处理器函数

但是我们在实际使用中,使用先定义一个结构体,再对这个结构体实现一个Handler接口,再注册的方式有点繁琐。经常用如下直接注册处理器函数的方式:

package main

import (
    "fmt"
	"net/http"
)


func AHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler A")
}
func BHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler B")
}

func main() {
    m := http.NewServeMux()

    m.HandleFunc("/a", AHandler)
    m.HandleFunc("/b", BHandler)

    server := http.Server {
        Addr: ":80",
        Handler: m,
    }
    server.ListenAndServe()
}

但很明显,AHandlerBHandler并没有实现Handler接口,那么这又是如何做到的呢?

可以看到其注册函数稍稍有些不同,注册处理器的是m.Handle(),注册处理器函数的是m.HandleFunc(),可以接着看一下HandleFunc的实现细节。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

HandleFunc会将处理器函数转换成HandlerFunc类型,HandlerFunc和处理器函数具有同的形式,所以可以强制类型转换。同时http库还为HandlerFunc实现了Handler接口。

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

也就是当调用该Handler接口ServeHTTP的时候就会调用到该函数自身。也就是说多路复用器的HandleFunc函数利用Handle函数封装了一个简易使用的版本。

实现简易的多路复用器

例如,我们想对每次访问页面的请求作日志记录,如果按之前的实现,则要在每个处理器或处理器函数中添加,这样太麻烦,我们可以利用http.ServeMux实现一个自己多路器。 主要是实现Handler接口和HandleHandleFunc两个函数。

package main

import (
    "fmt"
    "log"
	"net/http"
)


func AHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler A")
}
func BHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler B")
}

type CHandler struct {}
func (c CHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Handler C")
}

type Mux struct {
    m *http.ServeMux
}

func (mux *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    mux.m.ServeHTTP(w, r)
}

func doMuxLog(r *http.Request) {
    log.Printf("Request: %s from %s %s\n", r.URL.Path, r.RemoteAddr, r.UserAgent())
}

func (mux *Mux) Handle(pattern string, handler http.Handler) {
    f := func(w http.ResponseWriter, r *http.Request) {
        doMuxLog(r)
        handler.ServeHTTP(w, r)
    }
    logf := http.HandlerFunc(f)
    mux.m.Handle(pattern, logf)
}

func (mux *Mux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
    logf := func(w http.ResponseWriter, r *http.Request) {
        doMuxLog(r)
        handler(w, r)
    }

    mux.m.HandleFunc(pattern, logf)
}

func NewMux() *Mux {
    mux := &Mux {
        m: http.NewServeMux(),
    }

    return mux
}

func main() {
    m := NewMux()

    m.HandleFunc("/a", AHandler)
    m.HandleFunc("/b", BHandler)
    m.Handle("/c", CHandler{})

    server := http.Server {
        Addr: ":80",
        Handler: m,
    }
    server.ListenAndServe()
}

其中HandleHandleFunc也可以按如下方式实现

func (mux *Mux) Handle(pattern string, handler http.Handler) {
    logf := func(http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            doMuxLog(r)
            handler.ServeHTTP(w, r)
        })
    }
    mux.m.Handle(pattern, logf(handler))
}

func (mux *Mux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
    logf := func(http.HandlerFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
            doMuxLog(r)
            handler(w, r)
        }
    }
    mux.m.HandleFunc(pattern, logf(handler))
}