一、Hello World
使用net/http
编写一个简单的web服务器, 定义了一个UserHandler
的处理函数,通过HandleFunc
来将路由和handler
进行绑定,最后通过ListenAndServe启动web服务,后面我将handler统称为视图函数
package mainimport "net/http"func UserHandler(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World!"))
}func main() {http.HandleFunc("/", UserHandler)http.ListenAndServe(":8080", nil)
}
二、http.HandlFunc 源码解析
HandleFunc
接受两个参数,第一个是路由地址,第二个是一个视图函数,该函数接受两个参数 ResponseWriter 和 *Request ,前者是用来处理http请求的响应,后者是用来处理http请求的参数。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)
}
DefaultServeMux
是一个结构体变量,真实的结构体是ServeMux
。
var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMuxtype ServeMux struct {mu sync.RWMutexm map[string]muxEntryes []muxEntry // slice of entries sorted from longest to shortest.hosts bool // whether any patterns contain hostnames
}type muxEntry struct {h Handlerpattern string
}type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
ServeMux
有4个字段
- 第一个字段是读写锁,
- 第二个字段是一个map,
key
其实是路由地址,val
是一个muxEntry
结构体,该结构体由路由地址
和Handler接口
组成。 - 第三个字段是
muxEntry
切片,这个字段是用来处理存储路由地址是/结尾
的,比如/abc/d/
,并且将最长的pattern从长到短进行排序,后面的代码会做解释。
ServeMux (DefaultServeMux)
定义了一个HandleFunc
方法,该方法就是将用户的路由地址和视图函数继续传递给Handle方法,并判断视图函数是否为空。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {if handler == nil {panic("http: nil handler")}mux.Handle(pattern, HandlerFunc(handler))
}
mux.Handle(pattern, HandlerFunc(handler))
中HandlerFunc() 是将用户传递的视图函数转为 HandlerFunc
类型, 并不是函数调用。
type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}
这里为什么要将视图函数转为HandlerFunc
呢?在上面的代码中muxEntry
结构体存储一个Handler
接口,这个接口的定义了一个ServerHTTP
方法,你会发现HandlerFunc
恰好实现一个ServeHTTP
方法,所以HandlerFunc
相当于实现了Handler
接口,这样用户定义的视图函数就可以赋值给muxEntry结构体的h字段了。HandlerFunc 类型
是一种适配器,允许将普通函数用作 HTTP 处理程序。
我们接着往下看mux.Handle
内部,给ServeMux
结构体初始化,并将视图函数和路由地址加载到m
字段和es
字段中。
func (mux *ServeMux) Handle(pattern string, handler Handler) {// 校验路由地址是否已经定义过了。if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}// 初始化 m 字段if mux.m == nil {mux.m = make(map[string]muxEntry)}// 构造muxEntry结构体,并将视图函数和路由地址传入。e := muxEntry{h: handler, pattern: pattern}mux.m[pattern] = e// 如果路由地址最后是以 / 结尾,则将mux.es切片进行排序后重新赋值给mux.esif pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}if pattern[0] != '/' {mux.hosts = true}
}
appendSorted
函数内部主要是将路由地址从长到短进行排序后返回[]muxEntry
,并重新赋值给ServeMux
结构体的es
字段。
以上就是HandlFunc的源码,还是挺简单的,主要做的事情就是将用户定义的路由地址和视图函数填充到ServeMux结构体
三、http.ListenAndServe 源码解析
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}
ListenAndServe
接受一个地址和一个Handler
接口,然后实例化Server
结构体,然后调用Server
的ListenAndServe
方法。Server
结构体由很多字段,包含TSL配置,读取超时时间,响应超时时间等。
type Server struct {Addr stringHandler Handler // handler to invoke, http.DefaultServeMux if nilTLSConfig *tls.ConfigReadTimeout time.DurationReadHeaderTimeout time.DurationWriteTimeout time.Duration......
}
ListenAndServe
对用户传递的addr
进行校验,然后使用net.Listen
创建监听对象,并传入Serve
方法。
Serve
方法中我去掉了一些代码,保留了主要部分,baseCtx
主要用来保存整个请求生命周期的上下文,
这里有一个指数退避策略的用法。如果l.Accept()
调用返回错误,我们判断该错误是不是临时性地(ne.Temporary())
。如果是临时性错误,Sleep一小段时间后重试,每发生一次临时性错误,Sleep的时间翻倍,最多Sleep 1s。获得新连接后,将其封装成一个conn对象(srv.newConn(rw))
,创建一个 goroutine 运行其serve()
方法。省略无关逻辑的代码如下:
func (srv *Server) Serve(l net.Listener) error {ctx := context.WithValue(baseCtx, ServerContextKey, srv)var tempDelay time.Duration // how long to sleep on accept failurefor {rw, err := l.Accept()if err != nil {if srv.shuttingDown() {return ErrServerClosed}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 := ctxtempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can returngo c.serve(connCtx)}
}
serve()
方法其实就是不停地读取客户端发送地请求,创建serverHandler
对象调用其ServeHTTP()
方法去处理请求,然后做一些清理工作。serverHandler
只是一个中间的辅助结构,关键点就在于 ServeHTTP
方法。
func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())for {// 读取请求内容w, err := c.readRequest(ctx)// 执行用户的视图函数serverHandler{c.server}.ServeHTTP(w, w.req)// 返回响应w.finishRequest()}
}
ServeHTTP
方法先判断Handler
(Server
结构体中的Handler
,在http.ListenAndServe(":8080", nil)
时,这个nil
其实就是作为handler
参数传入的)若为nil
则使用默认的DefaultServeMux
,这时就会发现通过http.HandlerFunc
函数将路由和视图函数 塞进了DefaultServeMux
结构体被使用到了。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}handler.ServeHTTP(rw, req)
}
handler.ServeHTTP(rw, req)
就相当于调用了DefaultServeMux
结构体的ServeHTTP
方法,并将前面读取到的请求内存和封装的响应w
传递进去
ServeMux
结构体的ServeHTTP
方法内部通过调用mux.Handler(r)
方法来获得一个Handler
接口,最后调用h
接口的ServeHTTP
方法。
那么h
接口是由什么类型实现呢?我们往后看。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {// 返回用户的视图函数,是HandleFunc类型。h, _ := mux.Handler(r)h.ServeHTTP(w, r)
}
- mux.Handler(r ) 调用
mux.Handler(r)
方法最后调用handler
方法,通过用户请求的地址,使用match
方法来匹配视图函数并返回。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {return mux.handler(host, r.URL.Path)
}func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {h, pattern = mux.match(path)return
}func (mux *ServeMux) match(path string) (h Handler, pattern string) {// Check for exact match first.// 检测path是否存在ServeMux的m中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.// 匹配路由地址是否为/结尾的路由中的一部分// 比如/ab 是满足 /ab/c/的前缀匹配。注意es中是已经按照路由从长到短排序过了的。for _, e := range mux.es {if strings.HasPrefix(path, e.pattern) {return e.h, e.pattern}}return nil, ""
}
- h.ServeHTTP(w, r) 调用
前面 h, _ := mux.Handler(r)
返回的h
就是HandlerFunc 类型转换后的视图函数,所以就相当于调用HandlerFunc
类型的ServeHTTP
方法ServeHTTP
方法内部就是调用自身,也就是执行了用户的视图函数。
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}
最后在w.finishRequest()
方法返回响应,至此从定义视图函数,到接受请求并执行视图函数,最后返回响应体的基本路线已经完成了。
附上两张调用流程图,我觉得图片做的挺好的。
图片来源:https://blog.csdn.net/s2603898260/article/details/121575056
参考文章:https://darjun.github.io/2021/07/13/in-post/godailylib/nethttp/