golang源码解读之http.server

关于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, ""
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SicMvntus

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值