- " net.Listen(“tcp”, addr)": 多路复用相关初始化,初始化socket,端口连接绑定,开启监听
- “srv.Serve(ln)”: 等待接收客户端连接Accept(),与接收到连接后"go c.serve(ctx)" 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接
for {
//1.等待接收客户端请求
rw, e := l.Accept()
...
//2.接收到请求后封装conn连接
c, err := srv.newConn(rw)
//3.设置连接状态
c.setState(c.rwc, StateNew)
//4.通过协程执行(并发的体现)
go c.serve()
}
for{
//1.封装Request和response的逻辑
w, err := c.readRequest(ctx)
...
//2.匹配在http.HandleFunc()中注册的路由,找到对应的处理函数,执行我们写的业务逻辑
serverHandler{c.server}.ServeHTTP(w, w.req)
...
//3.进行最后处理工作,异常处理,资源回收,状态更新等
w.finishRequest()
}
- 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)
- 如果有路由满足,调用这个路由handler的ServeHTTP
- 如果没有路由满足,调用NotFoundHandler的ServeHTTP
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)
}
type serverHandler struct {
srv *Server
}
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)
}
如果在浏览器上访问 http://localhost:8080/tree 浏览器会自动给你加上"/" 向后台真正请求的路径为http://localhost:8080/tree/所以无法debug该逻辑处理
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" {
// 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)
}
// 去掉主机名上的端口号
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
Moved Permanently.
*/
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)
}
// 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)
##
# 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.m(map[string]muxEntry)中进行查询,如果有路径对应的处理函数则直接返回对应的handler和pattern
- 如果映射表中不存在对应的处理函数,则再遍历mux.es([]muxEntry)进行查找,因为mux.es是存放所有以"/" 结尾的路由路径的切片,并且路由长的位于切片的前面(排序过的)。strings.HasPrefix(path, e.pattern)判断字符串 path 是否以 e.pattern 开头,是的话返回对应的处理器函数handler和pattern, 所以注册路由时,只会在以"/" 结尾的路由路径中才会出现需要选择最长匹配方案
// 从代码中可以看出,匹配规则过于简单,导致基本所有的go框架
// 最重要的一点就是:重写路由匹配这部分,比如gin
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, ""
}
- " net.Listen(“tcp”, addr)": 多路复用相关初始化,初始化socket,端口连接绑定,开启监听
- “srv.Serve(ln)”: 等待接收客户端连接Accept(),与接收到连接后的处理流程
- 方法内通过for开启了一个死循环
- 在循环内部,调用Listener的Accept()方法,假设当前是TCP连接调用的就是TCPListener下的Accept(),阻塞监听客户端连接,是阻塞的(该方法内部有多路复用的相关逻辑,此处先不关注)
- 当有接收到连接请求后Accept()方法返回,拿到一个新的net.Conn连接实例,继续向下执行,封装net.Conn连接,设置连接状态为StateNew
- 通过协程执行连接的serve()方法,每一个连接开启一个goroutine来处理
- 然后for循环继续执行等待下一个连接
- 首先调用newBufioReader() 封装了一个bufio.Reader
- 开启了一个无限for循环,循环内
- 调用conn的readRequest(ctx)方法读取请求的内容,比如解析HTTP请求协议,读取请求头,请求参数,封装Request和response,在解析时会读取请求头的 Content-Length,不为 0会通过TCPConn.Read() 方法读取指定长度的数据并存入请求体中,如果 Content-Length 为 0 或者没有设置,则请求体为空
- 封装serverHandler调用serverHandler上的ServeHTTP(w, w.req)方法进行路由匹配,找到对应的处理函数,执行我们写的业务逻辑
- 调用response的finishRequest()方法进行最后处理工作,当底层 bufio.Writer 缓冲区的大小达到阈值或者Flush() 被显式调用时,就会将缓冲区内的数据写入到底层连接中,并触发 Conn 的 Write() 方法将数据发送到客户端,另外finishRequest()方法还会进行一些比如异常处理,资源回收,状态更新等操作
- 最后调用conn的setState()设置连接状态为StateIdle,方便后续重用连接
- 在前面我们也了解到DefaultServeMux中包含一个map属性m,在调用HandleFunc()方法,将接口与接口路径进行绑定,注册路由注册时,会将接口路径为key,接口路径与接口处理器封装为muxEntry作为value保存到这个map中
- 在ServeHTTP()这个方法中首先会拿到DefaultServeMux多路复用器,执行多路复用器上的ServeHTTP()方法
- 在ServeHTTP()中会调用ServeMux下的 handler()方法—>调用ServeMux 下的 match(path string),获取用户请求的URL进行合法处理,比如拼接后缀"/“,去除特殊字符比如”.“,处理后,获取到接口路径在DefaultServeMux内部的map属性中查找到对应的接口处理器,注意会优先拼接"主机名+路由路径"进行匹配,如果匹配不到再用路径去匹配,还有一个注意点, 如果匹配不到会在mux.es([]muxEntry)进行查找,因为mux.es是存放所有以”/" 结尾的路由路径的切片
- 最后获取到Handler接口处理器后,我们根据HandlerFunc格式编写业务处理器,HandlerFunc又实现了ServeHTTP,Handler是一个接口内部有ServeHTTP方法,最终业务处理器作为Handler,维护路由关系封装到了ServerMux的map容器与数组切片中, 当请求url与pattern匹配成功后获取到对应的Handler执行即可