Go使用net/http标准库(三)源码学习之- serverHandler{c.server}.ServeHTTP(w, w.req)

原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/110351336  ©王赛超

前面两章学习了 注册路由的http.HandleFunc()函数监听端口以及请求的处理 http.ListenAndServe()

第一章:路由注册 http.HandleFunc()
第二章:监听端口 处理请求 http.ListenAndServe()

经过第二章之后,已经了解了一次http请求的处理流程,但是第二章中还有一小部分没讲,就是 serverHandler{c.server}.ServeHTTP(w, w.req) 以及内部代码还没看,这块主要就是请求过来如何根据路由找到我们写的处理函数,执行处理函数。

还把那个最简单的代码拿过来,如下:

一. 搭建简单的web服务器

对于Go,实现一个最简单的http server用不着几行代码,如下:

func main() {
    http.HandleFunc("/", HelloServer)
    _ = http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte(`hello world`))
    if err != nil {
        fmt.Println(err)
    }
}

http.HandleFunc("/", HelloServer)这个函数就是注册路由,细节我们已经在第一章讲过了。
http.ListenAndServe(":8080", nil)这个函数监听8080端口,接收请求,根据路由找到处理函数处理请求。

不多废话了,不清楚的可以先看一下前2章的内容。

二.分析一下serverHandler{c.server}.ServeHTTP(w, w.req)

serverHandler.ServeHTTP()

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 此handler即为http.ListenAndServe 中的第二个参数
    // 获取Server对应的Handler 封装结构体的时候传入的是nil所以使用默认的DefaultServeMux
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    // 如果是 OPTIONS Method 请求且 URI 是 *,就使用 globalOptionsHandler 
    // Method == "OPTIONS" Preflighted Request(带预检的跨域请求) 
    // Preflighted Request在发送真正的请求前,会先发送一个方法为OPTIONS的预请求(Preflighted Request)
    // 用于试探服务端是否能接受真正的请求。如果options获得的回应时拒绝性质的,如404、403、500等状态,就会停止post、get请求的发出。
    // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_request_headers)
    // 测试方法 使用 net/http/serve_test.go 中的 TestOptions 函数
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    // 传的是nil 执行 DefaultServeMux.ServeHTTP() 方法
    handler.ServeHTTP(rw, req)
}

serverHandler实现的ServeHTTP()方法里的sh.srv.Handler就是我们最初在http.ListenAndServe()中传入的Handler参数,如果该Handler对象为nil,则会使用默认的DefaultServeMux。也就是我们自定义的ServeMux对象。最后调用ServeMuxServeHTTP()方法匹配当前路由对应的handler方法。

ServeMux.ServeHTTP()

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    // 如果RequestURI为 "*" 判断是不是HTTP/1.1 然后关闭长连接 响应 BadRequest
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    // 如果是一个正常的 GET POST 请求 执行ServeMux.Handler() 方法 寻找匹配的路由
    h, _ := mux.Handler(r)
    // 执行匹配到的路由的ServeHTTP方法
    h.ServeHTTP(w, r)
}

ServeMux.ServeHTTP()方法很简单,就是先查找路由,然后执行对应的处理函数HandlerServeHTTP()执行业务逻辑。

mux.Handler(r *Request)

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    // 对CONNECT请求的处理,CONNECT 处理代理场景
    // Method == "CONNECT" 类似于我们常使用的 POST GET ,http 1.1定义了8种方法,connect为其中之一
    if r.Method == "CONNECT" {
        // If r.URL.Path is /tree and its handler is not registered,
        // the /tree -> /tree/ redirect applies to CONNECT requests
        // but the path canonicalization does not.
        // redirectToPathSlash函数主要用于自动检测是否重定向URL并修改重定向URL路径,当注册的URL路径为/tree/,而请求URL路径为/tree,
        // redirectToPathSlash函数无法在mux.m中查找注册的handler,则将设请求URL设置为/tree/
        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }

        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    // 去掉主机名上的端口号
    host := stripHostPort(r.Host)
    // 处理URL,去掉 ".", ".."
    path := cleanPath(r.URL.Path)

    // If the given path is /tree and its handler is not registered,
    // redirect for /tree/.
    // 非代理场景重定向的处理,与"CONNECT"逻辑相同
    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }

    // 如果处理后的path和请求的URL.Path不一致,如请求路径为"/tree",处理后的路径为"/tree/",执行重定向并返回URL路径
    // 重定向通过http.redirectHandler.ServeHTTP函数进行处理,如下:
    /*
       HTTP/1.1 301 Moved Permanently
       Content-Type: text/html; charset=utf-8
       Location: /tree/
       Date: Sun, 29 Nov 2020 09:15:24 GMT
       Content-Length: 41
       Connection: keep-alive

       <a href="/tree/">Moved Permanently</a>.
    */ 
    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    // 在mux.m和mux.es中根据host/url.path寻找对应的handler
    return mux.handler(host, r.URL.Path)
}

mux.Handler(r *Request)该方法无论是普通GETPOST方法请求,还是代理CONNECT请求方法,都需要先对路径进行处理,例如请求路径为"/tree",处理后的路径为"/tree/"

注意:如果在浏览器上访问 http://localhost:8080/tree 浏览器会自动给你加上"/" 向后台真正请求的路径为http://localhost:8080/tree/所以无法debug该逻辑处理。

建议使用 postman 测试 ,测试日志如下: 如果访问 /tree 服务端会返回 301 需要重定向到 /tree/服务器返回重定向的报文如下:

HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /tree/
Date: Sun, 29 Nov 2020 09:15:24 GMT
Content-Length: 41
Connection: keep-alive

<a href="/tree/">Moved Permanently</a>.

postman控制台记录:
在这里插入图片描述

mux.handler(host, path string)

处理路径之后,调用mux.handler(host, path string)mux.m(map[string]muxEntry)mux.es([]muxEntry)中查找对应的处理函数HandlerCONNECT方法除外。

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
    // 若当前 mux 中注册有带主机名的路由,就用"主机名+路由路径"去匹配
    // 也就是说带主机名的路由优先于不带的
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }

    // 若没有匹配到,就直接把路由路径拿去匹配
    if h == nil {
        h, pattern = mux.match(path)
    }
    // 如果还没有匹配到,就默认返回 NotFoundHandler,该 Handler 会往 响应里写上 "404 page not found"
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    // 返回获得的 Handler 和路由路径
    return
}

这里只需要注意,带主机名的路由优先于普通路径匹配,带主机名的路由注册如下面的方式,一般不推荐使用:

hostName, err := os.Hostname()
if err != nil {
    panic(err)
}
http.HandleFunc(hostName+"/hello", HelloServer)

然后编辑/etc/hosts文件,将主机名ip的对应关系添加到该文件中。如主机名为wangsaichaodeMacBook-Pro,局域网的ip地址为127.0.0.1,则添加后的文件如下:

##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1   localhost
127.0.0.1       server.cas.com
127.0.0.1       app1.cas.com
127.0.0.1       app2.cas.com
127.0.0.1       rest.cas.com
127.0.0.1       wangsaichaodeMacBook-Pro
255.255.255.255 broadcasthost
::1             localhost

其实跟我们之前自定义域名是一样的。

mux.match(path string)

我们再看看 match 方法是怎么进行匹配的:

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    // 若 mux.m 中已存在该路由映射,直接返回该路由的 Handler,和路径
    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(map[string]muxEntry)中进行查询,如果正好有路径对应的处理函数则直接返回对应的handlerpattern

如果映射表中不存在对应的处理函数,则再遍历mux.es([]muxEntry)进行查找,在第一章(Go使用net/http标准库(一)源码学习之- http.HandleFunc())中我们知道mux.es是存放所有以"/" 结尾的路由路径的切片,并且路由长的位于切片的前面(排序过的)。strings.HasPrefix(path, e.pattern)判断字符串 path 是否以 e.pattern 开头,是的话返回对应的处理器函数handlerpattern, 所以注册路由时,只会在以"/" 结尾的路由路径中才会出现需要选择最长匹配方案。

下面用注册路由举例说明:

比如注册的路由有:

mux.HandleFunc("/a/b/", ab)
mux.HandleFunc("/a/", a)

那么当一个请求的URL/a/b/c 的时候,就是由 ab 来处理这个请求。因为 mux.es 中元素是按照它们的长度由大到小顺序存放的。

handler.ServeHTTP(w ResponseWriter, r *Request)

再回到 mux.ServeHTTP(w ResponseWriter, r *Request)方法中,获取到handler之后,又调用了handler.ServeHTTP(w ResponseWriter, r *Request)

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

第一章(Go使用net/http标准库(一)源码学习之- http.HandleFunc())中,我们知道HandlerFunc类型是一个适配器,并且这种类型实现了ServeHTTP方法,并在ServeHTTP方法中又调用了被转换的函数自身,也就是说这个类型的函数其实就是一个Handler类型的对象,通过类型转换可以将一个具有func(ResponseWriter, *Request)签名的普通函数转换为一个Handler对象,而不需要再定义一个结构体,再让这个结构实现ServeHTTP方法,非常方便的将普通函数用作HTTP处理程序

最终调用到了我们写的 handler处理函数。逻辑处理完成之后,后续的一些写响应,复用连接等相关操作在上一章(Go使用net/http标准库(二)源码学习之- http.ListenAndServe()) 中也说过了。

到此,go http源码学习到此结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值