go的Http底层原理

前言

在Go的web开发中,肯定离不开http的使用,本人也很少去读源码,为了锻炼能力与学习http的底层原理,就记录下了这篇文章,如果有不对的,欢迎各位同行纠正~

一个简单的http请求

服务端

// 服务端
func TestS(t *testing.T) {
	http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Fprintf(writer, "pong")
		writer.Write([]byte("pong"))
	})
	//默认模式 单例
	http.ListenAndServe(":8080", nil)
}

1.打断点,启动服务器

在这里插入图片描述

2.进入断点

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if use121 {
		DefaultServeMux.mux121.handleFunc(pattern, handler)
	} else {
	//进入这里面  mux121应该是版本更新时留下的老版本,用来版本更新过渡的
		DefaultServeMux.register(pattern, HandlerFunc(handler))
	}
}

3.进入DefaultServeMux.register(pattern, HandlerFunc(handler))

func (mux *ServeMux) register(pattern string, handler Handler) {
	if err := mux.registerErr(pattern, handler); err != nil {
		panic(err)
	}
}

这里为什么DefaultServeMux可以调用ServeMux的register这个方法呢?
我们可以点开DefaultServeMux,可以知道DefaultServeMux 就是ServeMux类型

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

4.在这里 我们可以先看一下ServeMux的结构

type ServeMux struct {
    //读写锁
    mu       sync.RWMutex
    //路由树  对应处理方法
    tree     routingNode
    //索引 加快查找
    index    routingIndex
    patterns []*pattern  // TODO(jba): remove if possible
    //这是老版本的存储方式
    mux121   serveMux121 // used only when GODEBUG=httpmuxgo121=1 
}

下面的对于ServeMux 的解释,是我询问gpt和自我理解的
tree routingNode: 每次添加新的路径模式时,都会将其分解成各个路径段,并按层级插入到路由树中。请求到来时,按照请求路径的各个段依次匹配树中的节点。
**index routingIndex:**每次添加新的路径模式时,都会将其分解成各个路径段,并生成对应的索引键,存入索引中。请求到来时,可以通过索引快速找到可能匹配的路径模式,然后再使用路由树进行精确匹配。
**patterns []*pattern:**每次添加新的路径模式时,都会将其解析并存入这个切片中。记录所有的路径模式,可能会用于一些辅助功能或者调试目的。
添加:
将路径解析成pattern 结构,原路径,方式,主机名
将解析后的路径模式按层级插入到 tree 路由树中
将路径模式的各个段生成索引键,存入 index 路由索引中。
将路径模式存入 patterns 切片中。
处理请求:
使用 index 路由索引快速查找可能匹配的路径模式。
使用 tree 路由树进行逐层级的精确路径匹配。
找到匹配的路径模式后,调用对应的处理函数。

5.进入mux.registerErr(pattern, handler),如果这个方法出错 会直接panic

这个方法是进行注册路由的,方法中判断了路由和对应的处理函数的正确性,以及是否被注册过

func (mux *ServeMux) registerErr(patstr string, handler Handler) error {
	if patstr == "" {
		return errors.New("http: invalid pattern")
	}
	if handler == nil {
		return errors.New("http: nil handler")
	}
	if f, ok := handler.(HandlerFunc); ok && f == nil {
		return errors.New("http: nil handler")
	}
	//解析匹配规则
	pat, err := parsePattern(patstr)
	if err != nil {
		return fmt.Errorf("parsing %q: %w", patstr, err)
	}
	// Get the caller's location, for better conflict error messages.
	// Skip register and whatever calls it.
	_, file, line, ok := runtime.Caller(3)
	if !ok {
		pat.loc = "unknown location"
	} else {
		pat.loc = fmt.Sprintf("%s:%d", file, line)
	}
	mux.mu.Lock()
	defer mux.mu.Unlock()
	// Check for conflict.
	if err := mux.index.possiblyConflictingPatterns(pat, func(pat2 *pattern) error {
		if pat.conflictsWith(pat2) {
			d := describeConflict(pat, pat2)
			return fmt.Errorf("pattern %q (registered at %s) conflicts with pattern %q (registered at %s):\n%s",
				pat, pat.loc, pat2, pat2.loc, d)
		}
		return nil
	}); err != nil {
		return err
	}
	//添加到路由树和索引之中,索引是为了加快路由树的匹配【在找请求的时候】
	mux.tree.addPattern(pat, handler)
	mux.index.addPattern(pat)
	mux.patterns = append(mux.patterns, pat)
	return nil
}

6.执行完之后,进入

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

7.我们首先来查看一下这个Server结构体

type Server struct {
      //地址,host:port 不写的话 默认 80
    Addr string
       //注册server的路由 
    Handler Handler 
       DisableGeneralOptionsHandler,如果为真,则将“OPTIONS*”请求传递给Handler,
//否则返回200 OK和内容长度:0。
    DisableGeneralOptionsHandler bool
        //https使用的  https的一些配置
    TLSConfig *tls.Config
    //整个http请求读取的时间 超时释放
    ReadTimeout time.Duration
    //读取请求头的超时时间 设置为0 则使用整个http请求读取的时间 都为0 则没有超时时间
    ReadHeaderTimeout time.Duration
    //返回不能超过的时间
    WriteTimeout time.Duration
   //第一个和第二个请求的间隔时间
    IdleTimeout time.Duration
     //请求头的最大字节
    MaxHeaderBytes int
    //
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
//golang管理http的请求的状态  活跃|闲置|连接被劫持,由用户控制,httpServer不再管理|关闭
    ConnState func(net.Conn, ConnState)
  //打印日志
    ErrorLog *log.Logger
//拿到基础上下文
    BaseContext func(net.Listener) context.Context
//传入函数 listen使用的时候 调用这个函数
    ConnContext func(ctx context.Context, c net.Conn) context.Context
            //标记这个server是不是被关闭
    inShutdown atomic.Bool // true when server is in shutdown
    disableKeepAlives atomic.Bool
    //http2使用
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     // result of http2.ConfigureServer if used
    mu         sync.Mutex
    //每次初始化一个listeners 追加到这个map
    listeners  map[*net.Listener]struct{}
    //golong管理所有活跃中的链接
    activeConn map[*conn]struct{}
    //server回收资源的函数 关闭的使用使用
    onShutdown []func()
     //等待一组go协程完成
    listenerGroup sync.WaitGroup
}

8.进入server.ListenAndServe()

这个方法主要就是为了设置监听启动服务建立一个连接

func (srv *Server) ListenAndServe() error {
	//检查服务器是否正在关闭
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	//设置监听地址
	addr := srv.Addr
	//如果地址为空 默认为80
	if addr == "" {
		addr = ":http"
	}
	//函数创建一个 TCP 监听器,并绑定到指定的地址
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	//启动服务
	return srv.Serve(ln)
}

9.可以从这张图来了解他们的关系

在这里插入图片描述### 10.Listen 就是一个绑定

type Listener interface {
    // Accept waits for and returns the next connection to the listener.
    //接收放回链接
    Accept() (Conn, error)

    // Close closes the listener.
    // Any blocked Accept operations will be unblocked and return errors.
    Close() error

    // Addr returns the listener's network address.
    //返回网络地址·
    Addr() Addr
}

11.进入srv.Serve(ln)

func (srv *Server) Serve(l net.Listener) error {
	//基本上不用,有些情况下 测试会设置一些值
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}
	origListener := l
	//包装一下 保证不会被多次调用close方法
	l = &onceCloseListener{Listener: l}
	defer l.Close()
	//http2的一些方法
	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}
	// 使用方法 把这个listen追加到map中去
	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	//函数退出时删掉
	defer srv.trackListener(&l, false)

	baseCtx := context.Background()
	//假如我们对Context进行了一些改造,那就生成一个新的Context
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure
	//追加context
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	//死循环accept
	//这段最重要
	for {
		//接收并返回连接
		//accept建立连接之后 返回一个连接
		rw, err := l.Accept()
		if err != nil {
			//拿到这个链接
			if srv.shuttingDown() {
				return ErrServerClosed
			}
			//转换为是否是网络错误,然后延时重试
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				//限制最大值 重试为1秒
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
				//然后sleep sleep之后继续continue 也就是阻塞在这里
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		//没问题
		//拿到这个上下文
		connCtx := ctx
		//如果进行改造
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		//链接建立成功
		tempDelay = 0
		//包装为一个更上层的conn
		c := srv.newConn(rw)
		//状态设置,新建链接
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		//创建一个协成对链接进行操作
		//对每一个链接都创建一个新的协程去处理
		go c.serve(connCtx)
	}
}

12.进入 c.serve(connCtx)

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	//拿到这个链接中间的字段
	if ra := c.rwc.RemoteAddr(); ra != nil {
		c.remoteAddr = ra.String()
	}
	//赋值给ctx
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	var inFlightResponse *response
	//捕捉异常
	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 inFlightResponse != nil {
			inFlightResponse.cancelCtx()
		}
		if !c.hijacked() {
			if inFlightResponse != nil {
				inFlightResponse.conn.r.abortPendingRead()
				inFlightResponse.reqBody.Close()
			}
			c.close()
			c.setState(c.rwc, StateClosed, runHooks)
		}
	}()
//tls建立的方法
	if tlsConn, ok := c.rwc.(*tls.Conn); ok {
		tlsTO := c.server.tlsHandshakeTimeout()
		if tlsTO > 0 {
			dl := time.Now().Add(tlsTO)
			c.rwc.SetReadDeadline(dl)
			c.rwc.SetWriteDeadline(dl)
		}
		if err := tlsConn.HandshakeContext(ctx); 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
		}
		// Restore Conn-level deadlines.
		if tlsTO > 0 {
			c.rwc.SetReadDeadline(time.Time{})
			c.rwc.SetWriteDeadline(time.Time{})
		}
		c.tlsState = new(tls.ConnectionState)
		*c.tlsState = tlsConn.ConnectionState()
		if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
			if fn := c.server.TLSNextProto[proto]; fn != nil {
				h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
				// Mark freshly created HTTP/2 as active and prevent any server state hooks
				// from being run on these connections. This prevents closeIdleConns from
				// closing such connections. See issue https://golang.org/issue/39776.
				c.setState(c.rwc, StateActive, skipHooks)
				fn(c.server, tlsConn, h)
			}
			return
		}
	}

	// HTTP/1.x from here on.
	// cancelCtx方便关闭上下游的资源
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()
	//包装成connReader 二进制流 方便读
	c.r = &connReader{conn: c}
	//下面两个缓冲区的都包装一下
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
	/**
不停地读取这个链接,给这个链接里面设置一些超时的时间设置,并且根据http的规则去解析得到的二进制流,并组装成request
**/
/**这是一个死循环,也就是不断地从这个请求里面读取数据,因为连接复用的原因,可能会传来很多请求。**/

	for {
		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, runHooks)
		}
		/**
		当读取完这个http请求之后,会判断request当中时候在读取中的限定大小,然后如果读取是出错,会根据几种情况进行判断。**/
		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:
				if v, ok := err.(statusError); ok {
					fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
					return
				}
				const publicErr = "400 Bad Request"
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
				return
			}
		}
/**
如果没有发生错误
判断是否接收支持100continus 这是因为post的请求体很大,我们要先发送一个数据包看服务端愿不愿意接收这些请求体 通过ecpect这个字段来判断。
**/
		// 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}
				w.canWriteContinue.Store(true)
			}
		} else if req.Header.get("Expect") != "" {
			w.sendExpectationFailed()
			return
		}
	//相关资源回收释放需要用到的
		c.curReq.Store(w)
	//判断这个body是否还有需要读取的数据
		if requestBodyRemains(req.Body) {
			registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
		} else {
			w.conn.r.startBackgroundRead()
		}
		inFlightResponse = w
		/**sercerHandler中的server中注册很多的sercerHandler 注册一堆路由,实现http的接口。
如果所有的handler都执行完成了
就开始收尾,判断链接是否被劫持了,
然后再finishRequest中去收尾**/
		serverHandler{c.server}.ServeHTTP(w, w.req)
		inFlightResponse = nil
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		w.finishRequest()
		c.rwc.SetWriteDeadline(time.Time{})
		if !w.shouldReuseConnection() {
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
				c.closeWriteAndWait()
			}
			return
		}
		c.setState(c.rwc, StateIdle, runHooks)
		c.curReq.Store(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))
		} else {
			c.rwc.SetReadDeadline(time.Time{})
		}

		// Wait for the connection to become readable again before trying to
		// read the next request. This prevents a ReadHeaderTimeout or
		// ReadTimeout from starting until the first bytes of the next request
		// have been received.
		if _, err := c.bufr.Peek(4); err != nil {
			return
		}

		c.rwc.SetReadDeadline(time.Time{})
	}
}

这一段有疑问,应该是处理客户端请求的

serverHandler{c.server}.ServeHTTP(w, w.req)是给http.ListenAndServe(“:8080”, mux)
这种情况下mux自己设置的处理器来使用的,给每一个handler都实现http的接口。【mux := http.NewServeMux()的情况下】

	mux := http.NewServeMux()
	mux.HandleFunc("/text", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Fprintf(writer, "text")
	})
	//默认模式 单例
	http.ListenAndServe(":8080", mux)

当客户端发送请求过来的时候

http://localhost:8080/ping

1.进入这里面

	http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Fprintf(writer, "pong")
		writer.Write([]byte("pong"))
	})

2.进入http.HandleFunc()

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if use121 {
		DefaultServeMux.mux121.handleFunc(pattern, handler)
	} else {
		DefaultServeMux.register(pattern, HandlerFunc(handler))
	}
}

3.进入HandlerFunc(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
	}
	var h Handler
	//做匹配
	if use121 {
		h, _ = mux.mux121.findHandler(r)
	} else {
		h, _, r.pat, r.matches = mux.findHandler(r)
	}
	h.ServeHTTP(w, r)
}

4.findHandler()

利用路由树和索引匹配找到对应的处理函数 然后返回

func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *pattern, matches []string) {
	var n *routingNode
	host := r.URL.Host
	escapedPath := r.URL.EscapedPath()
	path := escapedPath
	// 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.
		_, _, u := mux.matchOrRedirect(host, r.Method, path, r.URL)
		if u != nil {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
		}
		// Redo the match, this time with r.Host instead of r.URL.Host.
		// Pass a nil URL to skip the trailing-slash redirect logic.
		n, matches, _ = mux.matchOrRedirect(r.Host, r.Method, path, nil)
	} else {
		// All other requests have any port stripped and path cleaned
		// before passing to mux.handler.
		host = stripHostPort(r.Host)
		path = cleanPath(path)

		// If the given path is /tree and its handler is not registered,
		// redirect for /tree/.
		var u *url.URL
		n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
		if u != nil {
			return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
		}
		if path != escapedPath {
			// Redirect to cleaned path.
			patStr := ""
			if n != nil {
				patStr = n.pattern.String()
			}
			u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
			return RedirectHandler(u.String(), StatusMovedPermanently), patStr, nil, nil
		}
	}
	if n == nil {
		// We didn't find a match with the request method. To distinguish between
		// Not Found and Method Not Allowed, see if there is another pattern that
		// matches except for the method.
		allowedMethods := mux.matchingMethods(host, path)
		if len(allowedMethods) > 0 {
			return HandlerFunc(func(w ResponseWriter, r *Request) {
				w.Header().Set("Allow", strings.Join(allowedMethods, ", "))
				Error(w, StatusText(StatusMethodNotAllowed), StatusMethodNotAllowed)
			}), "", nil, nil
		}
		return NotFoundHandler(), "", nil, nil
	}
	return n.handler, n.pattern.String(), n.pattern, matches
}

在func (c *conn) serve(ctx context.Context) {}中的 serverHandler{c.server}.ServeHTTP(w, w.req)里出来,赋值请求响应,然后执行下来 ,释放之类的流程,就结束了一个请求。

疑问点:

在这一段有疑问,应该是处理客户端请求的,这个小标题中的代码serverHandler{c.server}.ServeHTTP(w, w.req)应该是处理发过来的请求所对应的处理函数,然后给这个处理函数添加上请求响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值