Go 源码学习 - http库:浅析一次 http 请求的处理过程

出发

Go 的 web 编程非常简洁。python 写 web app,比如 flask 或者 django,都要使用 uwsgi 或者 gunicorn 这种 web server 来提供生产级服务,web server 可以启动多进程来利用多核 CPU 实现更高的并发处理能力。Go 天生的 goroutine 实现了用户级线程,不仅更快而且可以充分利用多核 CPU,并发性能非常好,编译成二进制文件以后直接启动就可以提供生产可用的服务了,非常简洁。

web app 必要构件

最简单的 web app 只需要如下几个组成部分:

  • 请求处理函数
func test(w http.ResponseWriter, _ *http.Request){
    
    
	fmt.Fprintf(w, "test\n")
}
  • 注册路由
http.HandleFunc("/test", test)
  • 启动服务器
log.Fatal(http.ListenAndServe(":5000", nil))

整体流程

在这里插入图片描述

阅读源码

下面我从服务启动函数读起,逐步了解一次 http 请求的处理过程。

1 http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error {
    
    
	server := &Server{
    
    Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

该函数监听 TCP 地址然后调用处理器(handler)处理到来的请求,典型的 handler 为 nil,这时使用默认的路由器来路由请求。ListenAndServe 只会返回非 nil 的错误。

这个函数的主要作用是初始化一个服务器结构体(Server)的实例,为它的Handler属性赋值。

看一下 Server 结构体,它定义了运行一个 http 服务器所需的参数,这个结构体的零值(默认值)就是一个可用的配置。

type Server struct {
    
    
	Addr    string  // 监听的 TCP 地址,为空的话默认为 ":http",也就是80端口
	Handler Handler // 处理器,如果为 nil 的话使用默认的路由器 http.DefaultServeMux

	// TLSConfig 是可选项,配置 TLS
	TLSConfig *tls.Config

	// 读取完整请求的超时时长,包括请求体
	ReadTimeout time.Duration

	// 允许读取请求头的时长。
	ReadHeaderTimeout time.Duration

	// 写回响应的超时时长
	WriteTimeout time.Duration

	// 启用 keep-alives 时,一个长连接的两个请求之间间隔的最大时长
	IdleTimeout time.Duration

	// 允许的请求头的最大字节数,不限制请求体
	MaxHeaderBytes int

	// TLS 相关的配置
	TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

	// 当客户端连接状态改变时调用的回调函数,可选
	ConnState func(net.Conn, ConnState)

	// 可选的自定义 logger,不设就是标准日志库的 logger
	ErrorLog *log.Logger

	// 为来到此服务器的请求指定基 context ,不设就是 context.Background()
	BaseContext func(net.Listener) context.Context

	// 可选,通常指定一个函数,用于修改新连接的 context
	ConnContext func(ctx context.Context, c net.Conn) context.Context

	disableKeepAlives int32     // 关闭 keep-alives
	inShutdown        int32     // 非0表示正在关闭
	nextProtoOnce     sync.Once // http2相关
	nextProtoErr      error     // http2相关

	mu         sync.Mutex
	listeners  map[*net.Listener]struct{
    
    }
	activeConn map[*conn]struct{
    
    }
	doneChan   chan struct{
    
    }
	onShutdown []func()
}

2 Server.ListenAndServe

接下来是 Server 的 ListenAndServe 方法,它监听 TCP 网络地址 srv.Addr 然后调用 Serve 函数处理到达连接的请求。支持 TCP keep-alives。该函数返回非空的 error。

func (srv *Server) ListenAndServe() error {
    
    
    // 如果服务关闭了,返回的错误为 ErrServerClosed。
	if srv.shuttingDown() {
    
    
		return ErrServerClosed
	}
	addr := srv.Addr
	// 如果结构体的 Addr 属性为空就设置
	// 默认值 ":http"
	if addr == "" {
    
    
		addr = ":http"
	}
	// 调用 net 的 Listen 方法,得到一个 TCPListener
	ln, err := net.Listen("tcp", addr)
	if err != nil {
    
    
		return err
	}
	返回 srv 的 Serve 方法,上面得到的 TCPListener作为参数
	return srv.Serve(ln)
}

net.Listen 方法监听本地的网络地址,network参数可以是 tcp、tcp4、tcp6、 unix 或者 unixpacket。address 参数可以用 hostname,但是不建议,因为这样创建的 listener(监听器)最多监听主机的一个IP地址。如果 address 参数的 port 为空或者“0”,如“127.0.0.1:” 或者 “[::1]:0”,将自动选择一个端口号。
这个函数的主要作用就是初始化一个net包的 TCPListener 结构体的实例,然后调用服务器的Serve方法为这个连接提供服务。

3 Server.Server

Server 结构体的 Serve 方法。它为 listerner 上每个到达的连接创建一个新的服务协程(goroutine)。服务协程读取request(请求)然后调用 srv.Handler 方法做出回应。

Serve 方法返回非空的error并关闭listener。
这个方法运行一个死循环,等待底层tcp或者tls连接到来的请求数据,没有请求数据就休眠一段时间,数据到来以后把服务器实例(Server)和底层连接(net.Conn)封装为一个内部的conn结构体的实例,执行它的serve方法。

func (srv *Server) Serve(l net.Listener) error {
    
    
    // 如果有一个包裹了 srv 和 listener 的钩子函数,就执行它
	if fn := testHookServerServe; fn != nil {
    
    
		fn(srv, l) // call hook with unwrapped listener
	}

    // 把 listener 赋值给 origListener 变量,然后将原来的 linstner 
    // 作为 Listener 属性初始化一个onceCloseListener结构体的实例,
    // 顾名思义,只关闭一次的监听器,这个包裹是为了防止有多个 Close 调用。
	origListener := l
	l = &onceCloseListener{
    
    Listener: l}
	defer l.Close()

    // srv.setupHTTP2_Serve 方法有条件的配置 srv 的 HTTP/2。
	if err := srv.setupHTTP2_Serve(); err != nil {
    
    
		return err
	}

    // srv.trackListener 向跟踪的监听器集(srv.listeners)中添加一个监听器(l),
    // 并保证它最终会删除这个监听器
	if !srv.trackListener(&l, true) {
    
    
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	var tempDelay time.Duration // 如果没有接收到请求的话,休眠多久

    // 定义基础context
	baseCtx := context.Background()
	if srv.BaseContext != nil {
    
    
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
    
    
			panic("BaseContext returned a nil context")
		}
	}

    // 为ctx的ServerContextKey赋值srv
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	// 死循环,即伺服
	for {
    
    
	// rw 是net包里的Conn接口,他是一个通用的面向流的网络连接。
		rw, e := l.Accept()
		// 如果监听器接收报错
		if e != nil {
    
    
			select {
    
    
			// 如果 srv.getDoneChan 中有内容,返回 ErrServerClosed
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			// 如果 e 是 net.Error, 并且错误是临时性的
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
    
    
			    // 第一次没接收到数据,睡眠5毫秒
				if tempDelay == 0 {
    
    
					tempDelay = 5 * time.Millisecond
				// 每次随眠结束,唤醒后还是没接收到数据,睡眠时间加倍
				} else {
    
    
					tempDelay *= 2
				}
				// 单次睡眠时间上限设定为1秒
				if max := 1 * time.Second; tempDelay > max {
    
    
					tempDelay = max
				}
				// 记录接收数据失败日志,休眠 tempDelay
				srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			// 如果e不是net.Error,或者不是临时性错误,就返回错误
			return e
		}
		
		// 如果接收到数据就做如下处理:
		// 如果指定了srv的ConnContext,就用它修改连接的context
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
    
    
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
    
    
				panic("ConnContext returned nil")
			}
		}
		// 休眠定时器归零
		tempDelay = 0
		// 使用当前的Conn接口构建新的 conn 实例,它包含了srv服务器和rw连接。
		// conn 实例代表一个 HTTP 连接的服务端,rw是底层的网络连接
		c := srv.newConn(rw)
		// 设置底层连接的状态
	    // 第一次调用的时候初始化srv.activeConn为一个map[*conn]struct{}
	    // srv.activeConn是一个集合,保存服务器的活跃连接
	    // setState方法将当前连接加入集合跟踪状态
		c.setState(c.rwc, StateNew) // before Serve can return
		// 启动一个goroutine处理请求
		go c.serve(connCtx)
	}
}

看一下这个server.go 内的conn结构体

// conn 代表 HTTP 连接的服务侧.
type conn struct {
    
    
	// 连接到达的服务器。
	// 不可变,不能为空。
	server *Server

	// 取消连接层面的context的取消函数
	cancelCtx context.CancelFunc

	// rwc 是底层网络连接。
	// 它不能被其他类型包裹,通常是 *net.TCPConn 或 *tls.Conn 类型。
	rwc net.Conn

	// remoteAddr 就是 rwc.RemoteAddr().String()。它不在 Listener 接收数据的 goroutine,里同步填充。
	// 它在 (*conn).serve goroutine里被立即填充
	// 这是 Handler 的 (*Request).RemoteAddr 的值。
	remoteAddr string

	// tlsState 是使用 TLS 时 TLS 的连接状态。
	// nil 表示不用 TLS。
	tlsState *tls.ConnectionState

	// werr is set to the first write error to rwc.
	// 通过 checkConnErrorWriter{w} 设置,bufw 写入。
	werr error

	// r 是 bufr 的读入源。它是 rwc 的一个包裹器,提供io.LimitedReader
	// 式的限制 (在读取请求头的时候)
	// 并支持 CloseNotifier 的功能。详阅 *connReader 的文档。
	r *connReader

	// bufr 从 r 读取。
	bufr *bufio.Reader

	// bufw 写入 checkConnErrorWriter{c}, 当发生错误时填充 werr。
	bufw *bufio.Writer

	// lastMethod 是当前连接的最后一个请求的方法。
	lastMethod string

	curReq atomic.Value // of *response (which has a Request in it)

	curState struct{
    
     atomic uint64 } // packed (unixtime<<8|uint8(ConnState))

	// mu 保护 hijackedv
	mu sync.Mutex

	// hijackedv 表示这个连接是否被一个带有Hijacker接口的 Handler hijacked了。
	hijackedv bool
}

4 *conn.Serve

下面看看goroutine处理http(s) 请求的过程:

调用上面srv.newConn(rw)方法创建的conn实例的serve方法服务一个新的连接

func (c *conn) serve(ctx context.Context) {
    
    
    // 设置远端地址和本端地址
	c.remoteAddr = c.rwc.RemoteAddr().String()
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    // 错误处理
	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)
		}
	}()

    // https相关
	if tlsConn, ok := c.rwc.(*tls.Conn); ok {
    
    
		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))
		}
		if err := tlsConn.Handshake(); err != nil {
    
    
			// If the handshake failed due to the client not speaking
			// TLS, assume they're speaking plaintext HTTP and write a
			// 400 response on the TLS conn's underlying net.Conn.
			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
		}
		c.tlsState = new(tls.ConnectionState)
		*c.tlsState = tlsConn.ConnectionState()
		if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
    
    
			if fn := c.server.TLSNextProto[proto]; fn != nil {
    
    
				h := initNPNRequest{
    
    ctx, tlsConn, serverHandler{
    
    c.server}}
				fn(c.server, tlsConn, h)
			}
			return
		}
	}

	// HTTP/1.x 相关从这里开始
    // 确保执行cancelCtx
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

    // connReader 是一个包裹了 *conn的 io.Reader。
	c.r = &connReader{
    
    conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{
    
    c}, 4<<10)

	for {
    
    
	    // 从连接中读取请求,在读取超时时间内解析验证请求头中的内容。
	    // w 是返回的response实例
		w, err := c.readRequest(ctx)
		if c.r.remain != c.server.initialReadLimitSize() {
    
    
			// If we read any bytes off the wire, we're active.
			c.setState(c.rwc, StateActive)
		}
		// 根据错误码,向底层的net.Conn(实现了读写关闭接口)实例写入错误信息
		if err != nil {
    
    
			const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

			switch {
    
    
			case err == errTooLarge:
			    // 请求头字段过大。
				// Their HTTP client may or may not be
				// able to read this if we're
				// responding to them and hanging up
				// while they're still writing their
				// request. Undefined behavior.
				const publicErr = "431 Request Header Fields Too Large"
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
				// 写回响应并关闭连接。
				c.closeWriteAndWait()
				return

			case isUnsupportedTEError(err):
			    // 无法识别请求编码。
				// Respond as per RFC 7230 Section 3.3.1 which says,
				//      A server that receives a request message with a
				//      transfer coding it does not understand SHOULD
				//      respond with 501 (Unimplemented).
				code := StatusNotImplemented

				// We purposefully aren't echoing back the transfer-encoding's value,
				// so as to mitigate the risk of cross side scripting by an attacker.
				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:
			    // err 是 badRequestError 类型
				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
			}
		}

		// Expect 100 Continue support
		req := w.req
		if req.expectsContinue() {
    
    
			if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
    
    
				// Wrap the Body reader with one that replies on the connection
				req.Body = &expectContinueReader{
    
    readCloser: req.Body, resp: w}
			}
		} else if req.Header.get("Expect") != "" {
    
    
			w.sendExpectationFailed()
			return
		}

		c.curReq.Store(w)

		if requestBodyRemains(req.Body) {
    
    
			registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
		} else {
    
    
			w.conn.r.startBackgroundRead()
		}

		// HTTP 不能同时有多个活跃的请求。[*]
		// 服务器回复了一个请求后才能读取下一个请求,
		// 所以我们可以在这个 goroutine 里执行 handler 。
		// [*] 并非严格限定: HTTP pipelining. 即便它们需要串行地响应,
		// 我们也可以让它们全部并行地处理。
		// 但我们不打算实现 HTTP pipelining because it
		// was never deployed in the wild and the answer is HTTP/2.
		
		// 将 server 包裹为 serverHandler 的实例,执行它的
		// ServeHTTP 方法,处理请求,返回响应。
		// serverHandler 委托给 server 的 Handler 或者 DefaultServeMux(默认路由器)
		// 来处理 "OPTIONS *" 请求。
		serverHandler{
    
    c.server}.ServeHTTP(w, w.req)
		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() {
    
    
			// We're in shutdown mode. We might've replied
			// to the user without "Connection: close" and
			// they might think they can send another
			// request, but such is life with 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{
    
    })
	}
}

这一步的主要工作是解析验证请求头,然后把server 包裹为 serverHandler 的实例,执行它的ServeHTTP 方法,处理请求,返回响应。

5. serverHandler.ServeHTTP

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    
    
	handler := sh.srv.Handler
	如果没有定义 server.Handler,就是用默认路由器
	if handler == nil {
    
    
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
    
    
		handler = globalOptionsHandler{
    
    }
	}
	handler.ServeHTTP(rw, req)
}

Handler 是一个接口,嵌入在Server里。默认的DefaultServeMux是一个ServeMux类型的实例。

type Handler interface {
    
    
	ServeHTTP(ResponseWriter, *Request)
}

Handler 响应 HTTP 请求。

ServeHTTP 方法将响应头贺响应体写入 ResponseWriter 并返回。返回标志着请求的结束;与 ServeHTTP 调用同时或者在ServeHTTP后使用 ResponseWriter 或者 读取 Request.Body 都是无效的。

由于 HTTP 客户端、HTTP 协议版本和任何客户端与Go服务器之间的中间人的不同,有可能在写入 ResponseWriter 后就不能读取Request.Body了。注意handlers应该先读取Request.Body然后再响应。

除了读取请求体之外,handlers不应该修改请求。

ServeHTTP panic了,(调用ServeHTTP方法的)服务器假设 panic 的影响仅限于当前活跃的请求。它恢复 panic、将堆栈追踪记入错误日志,然后根据不同的HTTP协议执行关闭网络连接或者发送一个HTTP/2 RST_STREAM,使用 ErrAbortHandler panic可以中断处理请求,客户端可以看到中断的响应而服务器不会记录错误日志。

6. ServeMux.ServeHTTP

ServeMux是标准库提供的HTTP请求路由器,它将到来的请求的url与一套注册的模板进行匹配,调用与请求的URL匹配最接近的handler处理请求。

type ServeMux struct {
    
    
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // 路由项切片,从长到短排好序的
	hosts bool       // 匹配的模板是否包含主机名
}

ServeHTTP 方法将请求分发给与请求URL匹配最接近的handler

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    
    
	if r.RequestURI == "*" {
    
    
		if r.ProtoAtLeast(1, 1) {
    
    
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

*ServeMux的Handler方法根据请求的方法、主机名和URL路径返回给定请求的处理器(handler)。它总是会返回非空的handler。如果路径不符合格式规范,handler将是一个跳转到规范路径的内部生成的handler。如果host有端口,在匹配handler时会忽略端口。

Handler也返回请求匹配的模板,如果是内部生成的跳转,模板就在跳转后匹配。

如果没有为请求注册handler,Handler就返回 ‘page not found’ 和空模板

h.ServeHTTP(w, r) 是具体的处理函数,最后说明。

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

	// CONNECT requests are not canonicalized.
	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.
		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)
	}

	// 删除port,整理path
	host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)

	// 如果给的路径是 /tree 但没注册这个handler,就跳转到 /tree/.
	if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
    
    
		return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
	}

	if path != r.URL.Path {
    
    
		_, pattern = mux.handler(host, path)
		url := *r.URL
		url.Path = path
		return RedirectHandler(url.String(), StatusMovedPermanently), pattern
	}

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

7 *ServeMux.handler

这是Handler的主要实现。除了 CONNECT 方法以外,路径格式必须符合规范。

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    
    
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// 指定host的模板优先处理
	if mux.hosts {
    
    
		h, pattern = mux.match(host + path)
	}
	if h == nil {
    
    
		h, pattern = mux.match(path)
	}
	if h == nil {
    
    
		h, pattern = NotFoundHandler(), ""
	}
	return
}

这里的match函数返回HandlerFunc(f).

之前函数func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)的最后一句h.ServeHTTP(w, r)找了半天没找到是什么,其实上面这个func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)方法返回的就是通过路由匹配到的注册到Server的HandleFunc,它的ServeHTTP方法即是注册的HandleFunc函数,也就是最上面的那个test==函数。至此,一个http 请求的完整处理过程基本捋出来了。==下面是HandlerFunc类型和它的ServeHTTP方法:

// HandlerFunc 类型是一个允许一个普通函数用于HTTP处理函数的适配器。
// 如果 f 是一个签名适配的函数,HandlerFunc(f) 是一个调用 f 的 Handler。
type HandlerFunc func(ResponseWriter, *Request)

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

总结

因为很喜欢 Go 的 Http Server 所以很想了解源码。第一次啃这么大量的源码,用了几天的时间,只是捋出一个梗概的脉络,有一个总体的认知,并没有深入很多细枝末节,但是收获还是很大,后面有时间再细致研究。

代码方面的收获主要有以下几点:

  • 使用接口,实现了 ServeHTTP(ResponseWriter, *Request) 方法就是Handler接口,http包的server.go里面有大量这个方法调用。有很多类型都实现了这个方法,比如*ServeMuxHandlerFunc,而且HandlerFunc 是一个函数类型,也可以实现方法。有点麻烦的地方在于不一定能在IDE里直接跳转到方法定义,可能需要先找到正确的结构体,再找它的方法定义。
  • 结构体嵌套,代码用了很多次结构体包裹结构体的方式,实现类似继承但更加清晰简洁的复用方式。
  • 集合类型用值是空结构体的map,最节省空间。
  • 伺服函数用死循环,每次循环的等待时间成倍增加,设置一个最大等待时间的上限,这种逐渐增大等待时间配合等待时间上限即节约了cpu又保证了响应的性能。
  • type badRequestError string类型有个返回string的Error()方法,对于自定义类型,fmt.Print 会打印Error方法返回的字符串,如果没有Error方法会打印String方法返回的字符串

猜你喜欢

转载自blog.csdn.net/qq_35753140/article/details/104784910