gin源码分析(2)gin启动http服务

启动http服务

Go 复制代码
//使用gin.Run()启动http服务
//使用gin.RunTLS启动https服务
func (engine *Engine) Run(addr ...string) (err error) {
    //获取端口,默认:8080
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    //调用go实现好的http功能包,实现http功能
    //传入engine.Handler(),返回一个http Handler,由engine实现了该Handler函数ServeHTTP 
    err = http.ListenAndServe(address, engine.Handler())
    return
}

gin.Run()函数调用go的官方包启动了一个http服务,并实现了http服务的回调ServeHTTP函数。当请求来的时候会调用gin的ServeHTTP函数

Go 复制代码
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    //使用对象池初始化Context
    c := engine.pool.Get().(*Context)
    //初始化writermem
    //writermem继承http.ResponseWriter。用来http回复
    c.writermem.reset(w)
    c.Request = req
    //初始化Context
    c.reset()

    //处理请求
    engine.handleHTTPRequest(c)

    //移出对象池
    engine.pool.Put(c)
}
Go 复制代码
func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method

    //省略一些参数初始化

    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        if t[i].method != httpMethod {
            continue
        }
        root := t[i].root
        // 遍历请求树,找到方法
        value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
        if value.params != nil {
            c.Params = *value.params
        }
        // 开始调用中间件
        if value.handlers != nil {
            c.handlers = value.handlers
            c.fullPath = value.fullPath
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        //处理重定向
        if httpMethod != http.MethodConnect && rPath != "/" {
            if value.tsr && engine.RedirectTrailingSlash {
                redirectTrailingSlash(c)
                return
            }
            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                return
            }
        }
        break
    }

    //处理404请求
    if engine.HandleMethodNotAllowed {
        for _, tree := range engine.trees {
            if tree.method == httpMethod {
                continue
            }
            if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
                c.handlers = engine.allNoMethod
                serveError(c, http.StatusMethodNotAllowed, default405Body)
                return
            }
        }
    }
    c.handlers = engine.allNoRoute
    serveError(c, http.StatusNotFound, default404Body)
}

中间间调用与停止

调用

请求进来后后,调用Nex()函数后,所有的中间件(这里包括请求的处理函数)都会遍历运行

Go 复制代码
func (c *Context) Next() {
    c.index++
    //index大于handlers数量后停止后面中间件的运行
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}
Next()调用顺序分析
Go 复制代码
//中间件1
func M1(c *gin.Context) {
    fmt.Println("==============M1-A")
    c.Next()
    fmt.Println("==============M1-B")
}

//中间件2
func M2(c *gin.Context) {
    fmt.Println("==============M2-A")
}

//请求的执行函数
func List(c *gin.Context) {
	fmt.Println("user/list")
}

//请求/user/list后的打印
==============M1-A
==============M2-A
user/list
==============M1-B

分析:

NEXT()调用c.handlers[c.index](c)执行第一个中间件,在调用第一个中间件函数M1执行中途调用了Next(),进入Next(),函数下标c.index++,指向下一个中间件函数,等待下一个中间件函数M2,以及M2后面的处理函数。直到所有的函数都调用完,回到M1,执行后面的逻辑,打印M1-B

停止

停止就很简单,把该次的调用index数值设为abortIndex,abortIndex为handlers的最大数量combineHandlers函数里做了限制handler添加的最大数量

Go 复制代码
func (c *Context) Abort() {
    c.index = abortIndex
}

关于Http Server

Gin http请求并发的关键在于调用了Go的原生Http框架,这个框架有点大,我们可以慢慢地去探索一下

ListenAndServe是gin启动http服务的调用函数

Go 复制代码
func ListenAndServe(addr string, handler Handler) error {
    //初始化httpServer服务结构体 
    server := &Server{Addr: addr, Handler: handler}
    //启动服务监听
    return server.ListenAndServe()
}

//server.ListenAndServe()
func (srv *Server) ListenAndServe() error {
    //此处省掉部分参数配置

    //启动tcp监听 
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
    //传入tcp listen 
	return srv.Serve(ln)
}

//srv.Serve(ln)
func (srv *Server) Serve(l net.Listener) error {
    //去掉一些空判断 
    
    //初始化onceCloseListener结构体
    //onceCloseListener继承net.Listener,再加sync.Once
    //保证tcp只被关闭一次 
	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close()

    //初始化http2协议 
	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

    //把当前Listener当成key,value为空结构体存入map中,trackListener重要代码如下
    //s.listeners[ln] = struct{}{}  //ln为Listener为指针&l 
	//s.listenerGroup.Add(1)
	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
    //从map中删除listener 
	defer srv.trackListener(&l, false)

    //初始化goroutine的context
    //context是go原生提供用于goroutine的数据共享和流程控制 
	baseCtx := context.Background()
	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

    //把这个Server做为value存入context中 
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
        //等待请求接入 
		rw, err := l.Accept()
        //tcp出错,等待 tempDelay毫秒 后重试
		if err != nil {
            //srv是否正在关闭 
			if srv.shuttingDown() {
				return ErrServerClosed
			}
			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
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
        //初始化一个conn结构体
        //conn结构体,绑定了本Server和Accept()返回的net.Conn 
		c := srv.newConn(rw)
        //设置状态标志
        //此处的c.rwc就是上面的rw 
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        //启动一个goroutine,处理http请求 
		go c.serve(connCtx)
	}
}
Go 复制代码
func (c *conn) serve(ctx context.Context) {
    if ra := c.rwc.RemoteAddr(); ra != nil {
        c.remoteAddr = ra.String()
    }
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    var inFlightResponse *response
    //处理异常和关闭TCP 
    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)
        }
    }()

    //省略掉处理https请求代码

    // HTTP/1.x from here on.

    //由BaseContext创建一个cancelContext 
    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    //创建一个读Buffer和写Buffer 
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        //读出一个Respon 
        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)
        }
        if err != nil {
             //省略错误处理 
        }

        // 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)

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

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining. We could let them all process
        // in parallel even if their responses need to be serialized.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        inFlightResponse = w
        //调用ServeHTTP回调
        serverHandler{c.server}.ServeHTTP(w, w.req)
        inFlightResponse = nil
        w的cancelCtx与c的cancelCtx不是同一个cancelCtx
        w.cancelCtx()
        //是否被劫持
        if c.hijacked() {
            return
        }
        //把response的buffer,flush出去,并关闭一些buffer
        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{})
    }
}
相关推荐
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
Narutolxy2 小时前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader2 小时前
全面解析 Golang Gin 框架
开发语言·golang·gin
小林熬夜学编程3 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
njnu@liyong13 小时前
图解HTTP-HTTP报文
网络协议·计算机网络·http
ZachOn1y14 小时前
计算机网络:应用层 —— 应用层概述
计算机网络·http·https·应用层·dns
Graceful_scenery1 天前
https双向认证
服务器·网络·网络协议·http·https
桃园码工1 天前
2-Gin 框架中的路由 --[Gin 框架入门精讲与实战案例]
gin·路由·实战案例
海绵波波1071 天前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
海绵波波1071 天前
Gin-vue-admin(4):项目创建前端一级页面和二级页面
前端·vue.js·gin