关于server.go
server.go 里实现了 在发送请求后服务器如何对请求做出相对应响应的方法。开发人员只需要根据几行代码就可以直接 快速搭建http server服务。如下:
编写handler处理函数–>注册路由–>创建服务并开启监听
// 请求处理函数
func indexHandler(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "hello, world!\n")
}
func main() {
http.HandleFunc("/", indexHandler) // (默认ServeMux对象)注册路由(定义处理函数)
err := http.ListenAndServe(":8001", nil) // (封装Server对象)创建服务并开启监听
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
因此,请求会先进入路由,路由为请求找到合适的handler,handler对request进行处理,并构建response传给客户端
源码解析
1、服务器如何接收到客户端发出的请求?这里有一个重要对象:Server结构
// Server 是一个http服务对象, 是一个http服务的总体数据结构。
type Server struct {
Addr string // 指定要侦听的服务器的TCP地址,为空时:http (端口 80)
Handler Handler // 所有请求需要调用的Handler(实际上是ServeMux)如果为空则为DefaultServeMux
TLSConfig *tls.Config // 供ServeTLS和ListenAndServeTLS使用的TLS配置
ReadTimeout time.Duration // 读整个请求的最大时间,可和deadline 兼用
ReadHeaderTimeout time.Duration // 读请求头的最大时间,deadline被重置
WriteTimeout time.Duration // 是响应写入超时之前的最长持续时间
IdleTimeout time.Duration // 启用keep alives时等待下一个请求的最长时间.如果t为零,则使用ReadTimeout。如果两者都为零,则不存在超时
MaxHeaderBytes int // 解析请求头的键和值(包括请求行)时服务器将读取的最大字节数
// 可选地指定一个函数,以便在发生ALPN协议升级时接管所提供的TLS连接的所有权,key是协商的协议名称
// 传参Handler用于处理HTTP请求,如果尚未设置,则将初始化请求的TLS和RemoteAddr。
// 当函数返回时,连接将自动关闭
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState) // 指定一个可选回调函数,当客户端连接更改状态时调用该函数。
ErrorLog *log.Logger // 接受连接的错误、处理程序的意外行为和底层文件系统错误指定可选的记录器
BaseContext func(net.Listener) context.Context// 指定一个函数返回此服务器上传入请求的基本上下文
ConnContext func(ctx context.Context, c net.Conn) context.Context// 指定函数修改 新连接c上下文
inShutdown atomicBool //当服务器关闭时为true
...
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
1.1、 关于 Server.Handler字段为何定义成 Handler接口类型
该字段实际是一个ServeMux结构体对象,但这里定义成了Handler接口。具体解析后面有
该字段的5个使用处发现:只有第三个函数会 判断 Server.Handler字段 可能不是ServeMux类型 而是 globalOptionsHandler 结构体,所以Server的处理程序字段必须是Handler接口类型,而不仅仅是ServeMux结构体
①外部调用方法 根据监听器 处理http请求的Serve()、②ServeTLS()、③serverHandler对象的ServeHTTP()方法会调用Handler字段(其中判断了如果是OPTION*请求时,需要另外设置一个新的 处理程序)、④外部调用方法 使用处理程序 处理addr的 ListenAndServe()、⑤ListenAndServeTLS()
func Serve(l net.Listener, handler Handler) error {
func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error {
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
func ListenAndServe(addr string, handler Handler) error {
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
1.2、该server对象提供的一些方法
func (srv *Server) Serve(l net.Listener) error //对某个端口进行监听,里面是调用for进行accept的处理
func (srv *Server) ListenAndServe() error //开启http server服务,内部调用Serve
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error //开启https server服务,内部调用Serve
贴出核心源码:核心逻辑包括:监听端口、等待连接、创建连接、处理请求
其中 Serve()方法:传入监听器,使用监听器的传入连接,为每个连接创建一个新的服务goroutine(服务goroutines读取请求,然后调用srv.Handler回复他们; 该函数主要包装监听器,然后判断上下文,设置休眠循环监听,根据上下文调用内部方法serve实现
func (srv *Server) Serve(l net.Listener) error {
// 1、 使用未包装的监听器调用hook
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
// 2、包装监听器
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
// 3、在srv上有条件地配置HTTP/2
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
// 4、 将l添加到已跟踪的 监听器集,之后便删除
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
// 5、如果已经存在上下文,则设置成 背景上下文
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // 接受失败要休眠的时间
// 6、取出"http-server"为key的上下文
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
// 7、 循环等待并返回到监听器的下一个连接
rw, err := l.Accept()
if err != nil {
select {
// 8、如果监听失败,且关闭通道写出则 返回错误
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
// 9、如果监听失败,但是关闭通道没有写出,则断言错误类型并设置 休眠时间再继续监听
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 := ctx
// 10、指定函数修改上下文返回连接的上下文connCtx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0 // 重置休眠时间
// 11、从已有的连接 创建新连接
c := srv.newConn(rw)
// 12、给连接设置 首次状态
c.setState(c.rwc, StateNew) // 在Serve可以返回之前
// 13、传入连接上下文,调用内部实现,创建协程处理请求
go c.serve(connCtx)
}
}
其中 ListenAndServeTLS()与ListenAndServe()相同,只是它需要HTTPS连接。此外,必须提供包含服务器的证书和匹配私钥的文件。如果证书是由证书颁发机构签名的,则certFile应该是服务器证书、任何中间证书和CA证书的串联
// 监听TCP网络地址,然后调用用于处理传入连接上的请求
func (srv *Server) ListenAndServe() error {
// 1、如果服务已经关闭,则返回
if srv.shuttingDown() {
return ErrServerClosed
}
// 2、取出服务的地址并监听tcp网络,返回一个Listener对象ln
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// 3、调用Serve()创建新的监听服务
return srv.Serve(ln)
}
1.3、当然,server.go提供了外部直接调用函数,开发者可不需要再去实例一个server对象
// 开启监听服务(外部可直接调用的),每一个path对应一个不同处理程序(这个处理函数只要实现Handler接口)
// 实际上封装了一个实例化Server结构体对象,然后再调用Server结构体对象 的ListenAndServe方法
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServeTLS(certFile, keyFile)
}
2、处理请求:服务监听到addr后,最后还是调用内部 c.serve来处理请求的
go c.serve(connCtx)源码 详解如下:
// 内部实现:一个新的连接 处理请求
func (c *conn) serve(ctx context.Context) {
// 1、取出连接中 远程访问的地址
c.remoteAddr = c.rwc.RemoteAddr().String()
// 2、给入参上下文新增 当前地址 数据(connect到达的当地地址)
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// 3、程序结束后执行
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
// 4、判断并转换 net.conn类型 成 TLS连接,如果是,则进行以下操作
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
// 5、取出 达到服务器读整个请求的最大时间并设置成截止读时间
if d := c.server.ReadTimeout; d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
}
// 设置截止写时间
if d := c.server.WriteTimeout; d != 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
// 6、运行客户端和服务器握手协议
if err := tlsConn.Handshake(); err != nil {
// 如果握手失败是因为客户机为传输TLS,那么假设他们讲的是纯文本HTTP,并在TLS连接的底层网络连接 上写入400响应
if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
re.Conn.Close()
return
}
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
// 7、创建连接状态
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
// 8、取出 与ALPN协商的应用程序协议,判断 是否是有效的ALPN协议名称
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
// 9、如果有效,则使用该协议指定一个函数fn来接管TLS连接(fn函数返回后,连接会关闭)
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
// HTTP/1.x从这里开始
// 10、如果不是TLS连接,则先创建关闭连接的上下文函数
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
// 11、使用连接 实例出一个连接读取器
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r) // 读取源创建的 读取器
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) //编写器
for {
// 12、从连接读取下一个请求,返回响应对象
w, err := c.readRequest(ctx)
// 13、如果剩余字节数 不等于 初始读取器设置的 限制字节大小,代表还在读取,此时设置连接状态
if c.r.remain != c.server.initialReadLimitSize() {
// 如果我们从线路上读到任何字节,我们就是活跃的
c.setState(c.rwc, StateActive)
}
// 14、处理错误
if err != nil {
const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
switch {
case err == errTooLarge:
// 如果我们在响应他们并在他们仍在编写请求时挂断,他们的HTTP客户端可能无法读取此内容。未定义的行为
const publicErr = "431 Request Header Fields Too Large"
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
c.closeWriteAndWait()
return
case isUnsupportedTEError(err):
//按照要求进行响应,规定接收到传输编码不清楚的请求消息的服务器应使用501(未实现)进行响应
code := StatusNotImplemented
// 我们故意不回显传输编码的值,以减轻攻击者编写跨端脚本的风险
fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
return
case isCommonNetReadError(err):
return // don't reply
default:
publicErr := "400 Bad Request"
if v, ok := err.(badRequestError); ok {
publicErr = publicErr + ": " + string(v)
}
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
return
}
}
req := w.req
// 15、如果请求 期望长连接,且协议是1.1内容长度不为0,则使用持续读取器读取请求体
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// 用一个在连接上回复的读取器包装请求体读取器
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
// 给响应编写器设置 持续写
w.canWriteContinue.setTrue()
}
} else if req.Header.get("Expect") != "" {
// 16、否则如果不是长连接,但是又有其他期望请求,直接不响应且关闭请求
w.sendExpectationFailed()
return
}
// 17、将当前响应的请求的值设置为 w(响应对象)
c.curReq.Store(w)
// 18、判断请求体读取是否还有更多数据,如果有则分类型读,没有则直接开始读取
if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}
// HTTP不能同时有多个活动请,因此可在此goroutine中运行处理程序。
// 19、核心逻辑:实例化出一个 服务处理对象,并调用其 ServeHTTP函数 处理请求
serverHandler{c.server}.ServeHTTP(w, w.req)
// 20、关闭连接一系列操作
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
if !w.conn.server.doKeepAlives() {
// 我们处于关机模式。我们可能在没有“Connection:close”的情况下回复了用户,他们可能认为可以发送另一个请求,但HTTP/1.1就是这样
return
}
if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
该函数 先构建响应对象, 处理了请求 的 是否是HTTPS和请求是否 期望长连接时不同的处理,然后调用内部的处理函数来响应 请求,这个一对一响应请求就是由路由实现
2.1、核心代码1:w, err := c.readRequest(ctx)解析
该方法 从连接读取下一个请求,填充并 返回了响应连接对象
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
...
w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
...
return w, nil
}
// response包含了所有server端的http返回信息
type response struct {
conn *conn // 保存此次HTTP连接的信息
req *Request // 对应请求信息
chunking bool // 是否使用chunk
wroteHeader bool // header是否已经执行过写操作
wroteContinue bool // 100 Continue response was written
header Header // 返回的http的Header
written int64 // Body的字节数
contentLength int64 // Content长度
status int // HTTP状态
needSniff bool // 是否需要使用sniff。(当没有设置Content-Type的时候,开启sniff能根据HTTP body来确定Content-Type)
closeAfterReply bool //是否保持长链接。如果客户端发送的请求中connection有keep-alive,这个字段就设置为false。
requestBodyLimitHit bool //是否requestBody太大了(当requestBody太大的时候,response是会返回411状态的,并把连接关闭)
}
response对象的几个方法如下,可以看出response实现了ResponseWriter,Flusher,Hijacker这三个接口
func (w *response) Header() Header
func (w *response) WriteHeader(code int)
func (w *response) Write(data []byte) (n int, err error)
func (w *response) Flush()
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)
ResponseWriter接口
// 响应写入器接口:HTTP处理程序用 来构造HTTP响应
// 在Handler.ServeHTTP方法已经返回后,ResponseWriter可能不会使用
type ResponseWriter interface {
// 写响应头方法(返回将由WriteHeader发送的头)
Header() Header
// 写响应体方法,如果尚未写响应头,则写响应体前会自动调用一次(http.StatusOK状态)
// 如果标头不包含内容类型行,Write会将内容类型集添加到将写入数据的初始512字节传递给DetectContentType的结果中
// 此外,如果所有写入数据的总大小小于几KB,并且没有刷新调用,则会自动添加Content-Length头
// 调用写头和写body方法 会阻止将来 读取请求体 ,一旦头被刷新,请求正文就不可用
// 对于HTTP/2请求,go http服务器允许处理程序继续读取请求主体,同时写入响应(如果可能的话,处理程序应该先读后写,以最大限度地提高兼容性)
Write([]byte) (int, error)
// WriteHeader发送带有所提供状态代码的HTTP响应头
// 如果未显式调用WriteHeader,则第一次调用Write将触发隐式WriteHeader(http.StatusOK状态) ,因此,对WriteHeader的显式调用主要用于发送错误代码 ,只能写入一个标头
WriteHeader(statusCode int)
}
2.2、核心代码2:serverHandler{c.server}.ServeHTTP(w, w.req)
首先,serverHandler 对象定义ServeHTTP()函数实际也实现了Handler接口
其次,函数中指明了如果 服务端处理对象没有 自定义 Handler字段(这里指得是 实例化ServeMux结构体对象),则使用默认的ServeMux来进行 匹配请求
此时,handler实际已经是一个ServeMux对象了,然后才会调用该对象的 ServeHTTP()来匹配:根据路由请求去和ServeMux的m做匹配,找到合适的handler
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 1、取出服务处理对象的Handler处理程序字段 (接口类型),实际是ServeMux对象
handler := sh.srv.Handler
// 2、如果 字段 是nil,则设置为默认ServeMux
if handler == nil {
handler = DefaultServeMux
}
// 3、然后判断请求是否是“OPTIONS*”请求,如果是则 将处理程序设置为对应的handler(至此,说明了1.1)
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
2.3、ServeMux对象
ServeMux是一个HTTP请求多路转发器(路由器),负责接受http handler的注册和路由解析。在服务开始对外服务之前接受handler的注册,将所有的路径与处理函数成对的存放在一个map表中,当接收到URL请求的时候根据URL请求所带的路径到map表中进行查询,查到对应的handler函数对该请求进行处理或者说成是服务。
它将每个传入请求的URL与已注册模式的列表相匹配,并为与URL最接近的模式调用处理程序,较长的模式优先于较短的模式。
如果已经注册了子树,并且接收到一个请求,该请求命名子树根而不使用其尾部斜杠,则ServeMux会将该请求重定向到子树根(添加尾部斜杠),此行为可以通过不带尾部斜杠的路径的单独注册来覆盖。
ServeMux还负责清除URL请求路径和主机头,剥离端口号并重定向任何包含的请求
type ServeMux struct {
mu sync.RWMutex // 锁,由于请求设计到并发处理,因此这里需要一个锁机制
m map[string]muxEntry // 保存了路由路径和路由处理函数的映射关系,注册路由时,会往map中写入数据
es []muxEntry // 从最长到最短排序的muxEntry 对象切片
hosts bool // 是否有模式包含主机名
}
// muxEntry对象,当确定了路由表达式对应的muxEntry实体后,再取出h字段(Handler是一个包装ServeHTTP方法的接口),调用方法:h.ServeHTTP(ResponseWriter, *Request)来组装Response并返回
type muxEntry struct {
h Handler // 这个路由表达式对应哪个handler, 匹配路由时,从map中找到合适的handler处理
pattern string // 模式
}
// 将请求分派给模式与请求URL最匹配的处理程序并 响应该请求
// 接口实现:这个方法使 ServeMux对象实现了Handler接口,所以ServeMux实际上也是一个Handler
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 1、uri 判断
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
// 2、传入r调用Handler开始匹配出 处理程序Handler接口--h
h, _ := mux.Handler(r)
// 3、使用 匹配后的处理程序 再调用本身的 ServeHTTP方法来 响应一个http请求,
h.ServeHTTP(w, r)
}
2.4、匹配实现:h.ServeHTTP(w, r)
// 根据请求来 返回处理接口Handler程序+模式(请求匹配的已注册模式)
// 是内部方法handler的包装,主要处理请求方法情况和path情况
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 1、连接请求未规范化
if r.Method == "CONNECT" {
// 如果请求的URL.Path是/tree且 其处理程序还未注册,/tree->/tree/redirect适用于连接请求,但路径规范化不适用。
// 2、给path加”/“
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
// 3、根据新的url返回一个hanler
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
// 4、如果不需要加“/”, 则直接调用内部方法handler获取处理程序接口Handler
return mux.handler(r.Host, r.URL.Path)
}
// 所有其他请求在传递给处理程序之前都会剥离任何端口并清理路径
// 5、如果请求规范化,则先清理host和path
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// 6、如果给定的路径是/tree且其处理程序未注册,请重定向/tree/
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path // 在内部生成重定向的情况下,返回在重定向之后将匹配的模式
}
// 7、如果路径不是其规范形式,则处理程序将是一个内部生成的处理程序,它将重定向到规范路径
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
// 8、否则直接匹配
return mux.handler(host, r.URL.Path)
}
2.5 调用了 内部 mux.handler()
// 内部 根据路径返回处理程序Handler接口的实现,它实际上也是一个Handler
// 是内部方法match的包装,主要根据path情况调用match
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 1、如果有模式包含主机名,则开始匹配(Host特定模式优先于泛型模式)
if mux.hosts {
h, pattern = mux.match(host + path)
}
// 2、如果返回的处理程序是空的,则忽略host重新匹配
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
2.6 调用了 内部匹配方法mux.match()
// 路径匹配handler方法:长模式优先
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// 1、先检查是否完全匹配
v, ok := mux.m[path]
if ok {
return v.h, v.pattern // 如果存在一摸一样的路径,则取出对应的muxEntry,并返回v.h即Handler
}
// 2、检查最长有效匹配 mux.es 是[]muxEntry,包含所有的 以最长到最短的顺序排序的patterns模式
for _, e := range mux.es {
// 因为已经排序了,只要从左开始找到一个就返回
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}