?
本文将从如下几部分分析net/http
模块中关于server
部分的源码:
-
Handler
类型和HandlerFunc
类型是什么? -
ServeMux
对HTTP handler
的注册管理和分发 -
Server
启动流程
1. Handler
1.1 从注册HTTP handler入手
原生注册HTTP handler
有如下两种写法,它们有什么区别呢?
func handler(w http.ResponseWriter, r *http.Request) {}
http.HandleFunc("/some-pattern", handler)
http.Handle("/some-pattern", http.HandlerFunc(handler))
复制代码
两个方法对应的源码如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
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")
}
mux.Handle(pattern, HandlerFunc(handler))
}
复制代码
ServeMux
类型是什么暂时可以不用理会,会在后文提到。
可以发现,差异体现在HandlerFunc(handler)
这一语句,一个在内部调用,一个在外部调用。
而这一语句的作用在于将一个普通的函数转换成为Handler
类型,最终只有实现了Handler
接口的对象可以注册到HTTP服务端,为特定的路径及其子树提供服务,
它起到一个适配器的作用。
源码如下
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
复制代码
假设f
是一个有着正确签名的函数, 那么HandlerFunc(f)
就代表一个HTTP handler
,除此之外,ServeHTTP
方法的调用也代表着请求的处理,
它的调用时机将在后文提到
1.2 内置的几个Handler源码以及使用
1.2.1 NotFoundHandler
返回一个请求处理器,该处理其对每个请求都返回404 page not found
http.Handle("/some-pattern", http.NotFoundHandler())
复制代码
源码
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
复制代码
1.2.2 RedirectHandler
返回一个请求处理器,该处理器对每个请求都使用状态码code重定向到网址url
http.Handle("/some-pattern", http.RedirectHandler("/", 301))
复制代码
源码,有一说一,其中的Redirect函数看起来没什么分析价值,跳过
type redirectHandler struct {
url string
code int
}
func RedirectHandler(url string, code int) Handler {
return &redirectHandler{url, code}
}
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
Redirect(w, r, rh.url, rh.code)
}
复制代码
1.2.3 StripPrefix
在将请求定向到你通过参数指定的请求处理器之前,将特定的prefix从URL中过滤出去
http.Handle("/api/some-pattern", http.StripPrefix("/api", handler))
复制代码
源码
func StripPrefix(prefix string, h Handler) Handler {
if prefix == "" {
return h
}
return HandlerFunc(func(w ResponseWriter, r *Request) {
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
// 浅拷贝request
r2 := new(Request)
*r2 = *r
// r.URL是一个引用,需要再拷贝一次
r2.URL = new(url.URL)
*r2.URL = *r.URL
// 重置请求路径
r2.URL.Path = p
h.ServeHTTP(w, r2)
} else {
NotFound(w, r)
}
})
}
复制代码
理解该部分只需要了解两个点:
-
new(T)
为类型申请一片内存空间,并返回指向这片内存的指针 -
对指针变量进行取值
*
操作,可以获得指针变量指向的原变量的值
1.2.4 TimeoutHandler
返回一个采用指定时间限制的请求处理器,如果某一次调用耗时超过了时间限制,该处理器会回复请求状态码503 Service Unavailable
,并将msg
作为回复的主体。
func handler(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second)
}
http.Handle("/some-pattern", http.TimeoutHandler(http.HandlerFunc(handler), 1 * time.Second, "Timeout"))
复制代码
定义
type timeoutHandler struct {
handler Handler
body string
dt time.Duration
}
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
return &timeoutHandler{
handler: h,
body: msg,
dt: dt,
}
}
复制代码
当触发请求处理器时,ServeHTTP
方法会执行下面的操作(为了保证可读性简化非关键代码)
func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
// 初始化一个可被取消的上下文
ctx, cancelCtx := context.WithTimeout(r.Context(), h.dt)
defer cancelCtx()
// 设置r.ctx
r = r.WithContext(ctx)
done := make(chan struct{})
tw := &timeoutWriter{
w: w,
h: make(Header),
}
// 起一个goroutine来执行原本的逻辑
go func() {
h.handler.ServeHTTP(tw, r)
close(done)
}()
// 等待一个通信
select {
// 如果没有超时正常返回
case <-done:
tw.mu.Lock()
defer tw.mu.Unlock()
dst := w.Header()
for k, vv := range tw.h {
dst[k] = vv
}
if !tw.wroteHeader {
tw.code = StatusOK
}
w.WriteHeader(tw.code)
w.Write(tw.wbuf.Bytes())
// 如果超时
case <-ctx.Done():
tw.mu.Lock()
defer tw.mu.Unlock()
w.WriteHeader(StatusServiceUnavailable)
io.WriteString(w, h.errorBody())
tw.timedOut = true
}
}
复制代码
2. 多路转接器ServeMux
ServeMux
是HTTP请求的多路转接器,它会将每一个接收请求的URL与一个注册模式的列表进行匹配,并调用最匹配的模式的处理器,
第一部分的注册HTTP handler
的过程实际上是在ServeMux
内部的一个哈希表中添加一条记录
2.1 注册流程
ServeMux
结构体如下
type ServeMux struct {
// 读写锁
mu sync.RWMutex
// 管理所有注册路由哈希表
m map[string]muxEntry
// 按pattern长度降序排列的匹配列表, 记录值均以/结尾
es []muxEntry
// 是否存在hosts, 即不以'/'开头的pattern
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
复制代码
上文提到了,每次注册一个HTTP handler
最终调用的都是DefaultServeMux.Handle(pattern, handler)
方法,
这个方法做的事情很简单,就是维护内部哈希表m
,省略部分错误处理代码后源码如下:
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 如果注册一个已注册的处理器,将panic
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
// 注册
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
// 以斜杠结尾的pattern将存入es切片并按pattern长度降序排列
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
// 不以"/"开头的模式将视作存在hosts
if pattern[0] != '/' {
mux.hosts = true
}
}
复制代码
注册流程结束,所以说http.Handle(pattern, handler)
只是单纯的在DefaultServeMux
的哈希表m
中执行注册, 并没有开始分发
2.2 分发
ServeMux
结构体也实现了Handler
接口,因此它才是真正的分发者!
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
// 对于http协议小于1.1的处理
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
// 寻找到最接近的HTTP handler
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
复制代码
mux.Handler(r)
方法始终会返回一个不为空的HTTP handler
,其中包含较多的对特殊情况的处理,从源码学习的角度来说,陷入这些分支是不正确的,只应该
考虑最主要的情况进行分析,因此,Handler
方法简化为:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 当请求地址为/tree,但只注册了/tree/未注册/tree时,301重定向
// 此处redirectToPathSlash并没有分析价值,检测一下两者是否在mux.m哈希表中即可
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(host, r.URL.Path)
}
复制代码
最后的handler
才是Handler
方法实现的核心,源码如下:
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 指定主机的模式优于一般的模式
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
// 如果没有匹配到任何Handler,将返回404 handler
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
复制代码
假设此时DefaultServeMux
注册了两个模式: /a/
, /a/b/
,此时DefaultServeMux
的结构为
{
m: {
"/a/": { h: HandlerA, pattern: "/a/" },
"/a/b/": { h: HandlerB, pattern: "/a/b" },
},
es: [{ h: HandlerB, pattern: "/a/b" }, { h: HandlerA, pattern: "/a/" }]
}
复制代码
当请求路径为/a/b/c
,将进入第二个if
语句,在match
方法中进行匹配:
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// 直接匹配成功的情况
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// 寻找最接近的最长匹配,mux.es切片中包含了所有子树,并降序排列,因此遍历一次即可找出最接近的模式
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
复制代码
最终路径/a/b/c
将返回handlerB
3. Server
Server
的部分结构如下:
type Server struct {
// 监听的地址和端口
Addr string
// 所有请求都要调用的Handler
Handler Handler
// 读的最大超时时间
ReadTimeout time.Duration
// 写的最大超时时间
WriteTimeout time.Duration
// 请求头的最大长度
MaxHeaderBytes int
...
}
复制代码
3.1 从启动HTTP server入手
实例代码
http.ListenAndServe(":8001", nil)
复制代码
源码中实际上是创建一个server实例,当handler
为nil
时,将使用DefaultServerMux
作为默认的handler
,
也就是第二节中提到的"多路转接器",这也是最常见的做法
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
复制代码
在server.ListenAndServe
方法中,将调用net.Listen("tcp", addr)
监听端口,
不过关于TCP
的内容不在分析范围内,直接进入srv.Serve
方法
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
复制代码
在srv.Serve
方法中则会在一个for
循环中,完成如下的工作:
-
调用
l.Accept()
获得一个新的连接,进行后续操作 -
将
TCP conn
转换为服务端的HTTP conn
-
启动一个
goroutine
来处理这个HTTP conn
func (srv *Server) Serve(l net.Listener) error {
l = &onceCloseListener{Listener: l}
defer l.Close()
// 获得根context
baseCtx := context.Background()
// 返回一个在根context的基础上添加键为ServerContextKey,值为当前Server引用的context
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
// 接收一个请求
rw, e := l.Accept()
// 将tcp conn转换为http conn
c := srv.newConn(rw)
// 启动一个goroutine处理这个请求
go c.serve(ctx)
}
}
复制代码
c.serve(ctx)
则会进行最后的处理, 此部分比较复杂,其实只需要关心serverHandler{c.server}.ServeHTTP(w, w.req)
这一行
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
// HTTP/1.x from here on.
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
ctx, cancelCtx := context.WithCancel(ctx)
defer cancelCtx()
for {
w, err := c.readRequest(ctx)
......
serverHandler{c.server}.ServeHTTP(w, w.req)
......
}
}
复制代码
最终也就是调用DefaultServeMux
作为整体的HTTP handler
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)
}
复制代码
OVER
3.2 整体流程
有疑问加站长微信联系(非本文作者)