本文将从以下几部分分析net/http
模块中关于server
部分的源码:api
Handler
类型和HandlerFunc
类型是什么?bash
ServeMux
对HTTP handler
的注册管理和分发app
Server
启动流程tcp
原生注册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
类型是什么暂时能够不用理会,会在后文提到。ui
能够发现,差别体如今HandlerFunc(handler)
这一语句,一个在内部调用,一个在外部调用。 而这一语句的做用在于将一个普通的函数转换成为Handler
类型,最终只有实现了Handler
接口的对象能够注册到HTTP服务端,为特定的路径及其子树提供服务, 它起到一个适配器的做用。url
源码以下spa
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
方法的调用也表明着请求的处理, 它的调用时机将在后文提到3d
返回一个请求处理器,该处理其对每一个请求都返回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) }
复制代码
返回一个请求处理器,该处理器对每一个请求都使用状态码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)
}
复制代码
在将请求定向到你经过参数指定的请求处理器以前,将特定的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)
为类型申请一片内存空间,并返回指向这片内存的指针
对指针变量进行取值*
操做,能够得到指针变量指向的原变量的值
返回一个采用指定时间限制的请求处理器,若是某一次调用耗时超过了时间限制,该处理器会回复请求状态码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
}
}
复制代码
ServeMux
是HTTP请求的多路转接器,它会将每个接收请求的URL与一个注册模式的列表进行匹配,并调用最匹配的模式的处理器, 第一部分的注册HTTP handler
的过程其实是在ServeMux
内部的一个哈希表中添加一条记录
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
中执行注册, 并无开始分发
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
Server
的部分结构以下:
type Server struct {
// 监听的地址和端口
Addr string
// 全部请求都要调用的Handler
Handler Handler
// 读的最大超时时间
ReadTimeout time.Duration
// 写的最大超时时间
WriteTimeout time.Duration
// 请求头的最大长度
MaxHeaderBytes int
...
}
复制代码
实例代码
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