net/http分析
1. 简介:
http 提供两个函数作为服务器编程开始,它在 addr 地址侦听,收到请求后,回调 Handler 接口方法。ListenAndServeTLS 支持制定证书的 HTTPS 服务。
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
因此,为了剖析net\http包,我们需要从ListenAndServe函数开始,不断向下剖析http包的实现;
2. net\http包原理分析:
我们从ListenAndServe函数定义开始:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
可以看到,ListenAndServe只是利用传入的addr函数,以及handler来初始化实际的服务器server,并利用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
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
BaseContext func(net.Listener) context.Context
ConnContext func(ctx context.Context, c net.Conn) context.Context
inShutdown atomicBool // true when when server is in shutdown
disableKeepAlives int32 // accessed atomically.
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中为Server结构体赋值的两个变量:
- Addr
- Handler
为了了解Handler的用途,我们开始追踪Handler的定义。
Handler:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
那么,我们的Handler实际上是一个接口,它定义了一个名为ServeHTTP的函数。那么所有实现了ServeHTTP函数的变量类型,都可以作为Handler接口的一个变量。那么Handler接口便符合我们在课上讲的单一函数的回调接口使用。我们可以通过以下三种方式来实现一个或是返回一个Handler接口类型变量:
- 函数类型实现该接口
- 函数(闭包)返回该接口
- 数据对象实现该接口
至于net/http中使用了哪一个方式,我们继续分析;
接下来,我们就直接看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)
}
我们忽略所有的错误处理,只看这个代码的实现,可以看到,这里只有两行代码执行了真正的功能:
- net.Listen(“tcp”, addr)
- srv.Serve(ln)
其中net.Listen函数启动了对addr所指向的端口进行监听。net.Listen函数还返回对应的Listener,用于以后的对于链接的建立,关闭。那么我们继续看Server的Serve函数,其代码如下:
func (srv *Server) Serve(l net.Listener) error {
...
baseCtx := context.Background()
...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
...
connCtx := ctx
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx)
}
}
因为原来的代码实在过长,所以我们在这里去除了不必要的部分,并使用…代替;
可以看到,Serve函数在得到监听器之后,就创建了一个循环。并不断等待监听器Accept一个新的访问。当监听器得到一个新的访问的时候,将返回一个rw,rw中保存着request信息。之后调用newConn函数:
func (srv *Server) newConn(rwc net.Conn) *conn {
c := &conn{
server: srv,
rwc: rwc,
}
if debugServerConnections {
c.rwc = newLoggingConn("server", c.rwc)
}
return c
}
即创建一个新的conn类型的变量c,并返回;然后我们利用go程以及相关的上下文,建立一个新的连接以及对应的go程,处理连接。
在c.serve中,我们同样使用了for循环,并在每个循环中利用w, err := c.readRequest(ctx)
获得每个请求的request;然后使用了serverHandler{c.server}.ServeHTTP(w, w.req)
来处理请求。我们可以查看ServeHTTP的实现:
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)
}
当handler为空时,我们使用默认的handler,即DefaultServeMux
来处理请求,否则,我们就使用之前赋予服务器的handler来处理请求。最后一行:
handler.ServeHTTP(rw,req)
才真正实现了对请求的处理。
那么我们的net\http的实现过程即为:
- 调用ListenAndServe,创建Server服务器,并调用它的ListenAndServe函数;
- 调用net.Listen,启动对端口的监听;
- 启动一个循环,利用l.Accept不断准备接收访问;
- 新建一个连接c,并利用go程
go c.serve(connCtx)
完成对该访问任务的处理; - 在c.serve中,我们启动一个for循环来处理对多个请求的访问;
- 使用w, err := c.readRequest(ctx)获得每个请求的request;
- 使用serverHandler{c.server}.ServeHTTP(w, w.req)对请求进行处理;
- serverHandler检查handler是否为空,如果为空就使用DefaultServeMux;否则使用已有的handler;
- handler.ServeHTTP(rw,req)实现对请求的处理;
3. DefaultServeMux实现
找到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
}
type muxEntry struct {
h Handler
pattern string
}
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
可以发现,DefaultServerMuX实际上是ServeMux类型的一个变量。ServeMux变量拥有一个muxEntry的map映射和一个muxEntry的数组es。其中es中的muxEntry按照pattern长度从大到小排列;
那么我们继续看DefaultServeMuX对Handler接口的ServeHTTP函数实现:
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找到满足request的Handler,然后返回给h,由h的ServeHTTP函数对请求进行处理;
mux.Handler:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
...
return mux.handler(host, r.URL.Path)
}
由于Handler原代码太长,我们只选取有意义,且实际,或者说正常情况下被执行的函数mus.handler。其他部分用…代替;那么我们转向mux.handler
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
}
在这里,我们可以看到,mux.handler的实际功能为利用mux.match函数在mux.m和mux.es中找到path所对应的handler,如果没有找到,那么就返回NotFoundHandler。而在match函数中,
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, ""
}
我们先尝试在mux.m中寻找直接和path对应的handler,如果找到,那么就返回这个handler。如果找不到,那么就在mux.es中寻找和path拥有最长公共前缀的handler进行返回。
为了向DefaultServeMuX中添加新的路由规则,我们需要调用以下两个函数:
http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
http.Handle(pattern string, handler func(ResponseWriter, *Request))
就能为DefaultServeMuX添加新的路由规则;
那么DefaultServeMuX的实现如下:
- 使用http.HandleFunc或http.Handle为DefaultServeMuX添加新的路由规则;
- 当http.ListenAndServe没有设置handler的时候,使用DefaultServeMuX作为handler;
- DefaultServeMuX使用match在mux.m和mux.es中找到与path最相匹配的handler作为最终解决对请求的访问的handler。如果找不到,那么使用NotFoundHandler作为请求的处理函数;