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

这篇具有很好参考价值的文章主要介绍了gin源码分析(2)gin启动http服务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

启动http服务

//使用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函数

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)
}
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()函数后,所有的中间件(这里包括请求的处理函数)都会遍历运行

func (c *Context) Next() {
    c.index++
    //index大于handlers数量后停止后面中间件的运行
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}
Next()调用顺序分析
//中间件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添加的最大数量

func (c *Context) Abort() {
    c.index = abortIndex
}

关于Http Server

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

ListenAndServe是gin启动http服务的调用函数文章来源地址https://www.toymoban.com/news/detail-844959.html

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)
	}
}
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{})
    }
}

到了这里,关于gin源码分析(2)gin启动http服务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang Gin HTTP 请求和参数解析

    我们介绍了Gin框架,并做了Gin框架的安装,完成了第一个Gin工程的创建。 创建Engine 在gin框架中,Engine被定义成为一个结构体,Engine代表gin框架的一个结构体定义, 其中包含了路由组、中间件、页面渲染接口、框架配置设置等相关内容。 默认的Engine可以通过gin.Default进行创建

    2024年02月01日
    浏览(79)
  • 基于Gin框架的HTTP接口限速实践

    在当今的微服务架构和RESTful API主导的时代,HTTP接口在各个业务模块之间扮演着重要的角色。随着业务规模的不断扩大,接口的访问频率和负载也随之增加。为了确保系统的稳定性和性能,接口限速成了一个重要的话题。 1 接口限速的使用场景 接口限速的使用场景主要涉及以

    2024年02月10日
    浏览(35)
  • [golang gin框架] 39.Gin商城项目-微服务实战之微服务架构

    单体架构在 中小企业内部 用的是非常多的,当 业务不复杂 , 团队规模不大 的时候,单体架构比微服务架构具有 更高的生产率 单体架构 当 业务比较复杂 , 并发量 比较大, 团队规模扩大的时候, 就需要引入微服务架构了,它比单体架构 具有 更高的生产率, 可以 节省成本 , 解

    2024年02月12日
    浏览(43)
  • [golang gin框架] 40.Gin商城项目-微服务实战之Captcha验证码微服务

    本次内容需要 gin框架基础知识, golang微服务基础知识才能更好理解 在前面,讲解了微服务的架构等,这里,来讲解前面商城项目的 Captcha验证码 微服务 ,captcha验证码功能在前台,后端 都要用到 ,可以把它 抽离出来 ,做成微服务功能 编辑 这个验证码功能封装代码captcha.go如下: 把这个

    2024年02月16日
    浏览(44)
  • [golang gin框架] 42.Gin商城项目-微服务实战之后台Rbac微服务角色增删改查微服务

    上一节讲解了后台Rbac微服务用户登录功能以及Gorm数据库配置单独抽离,Consul配置单独抽离,这一节讲解 后台Rbac微服务 角色 增删改查微服务 功能,Rbac微服务角色增删改查微服务和 后 台Rbac用户登录微服务 是属于 同一个Rbac微服务 的 不同子微服务功能 ,为了区分不同子微

    2024年02月15日
    浏览(41)
  • [golang gin框架] 44.Gin商城项目-微服务实战之后台Rbac微服务之权限的增删改查微服务

    上一节讲解了[golang gin框架] 43.Gin商城项目-微服务实战之后台Rbac微服务之管理员的增删改查以及管理员和角色关联,这里讲解权限管理Rbac微服务权限的增删改查微服务 要实现权限的增删改查,就需要创建对应的模型,故在server/rbac/models下创建Access.go模型文件,参考[golang gin框架]

    2024年02月14日
    浏览(43)
  • [golang gin框架] 45.Gin商城项目-微服务实战之后台Rbac微服务之角色权限关联

    角色和权限的关联关系在前面文章中有讲解,见[golang gin框架] 14.Gin 商城项目-RBAC管理之角色和权限关联,角色授权,在这里通过微服务来实现 角色对权限的授权 操作,这里要实现的有两个功能,一个是进入授权,另一个是,授权提交操作,页面如下:  这里需要在proto/rbacRole.proto中增加

    2024年02月14日
    浏览(57)
  • go web框架 gin-gonic源码解读03————middleware

    今天打完游戏有空整理整理之前看的gin的中间件设计,go的中间件设计相较于前两站还是蛮简单,蛮容易看懂的,所以顺便把context也一起写一下。 中间件是现在web服务里统一化拓展最常用的功能,,他是为了在我们的web服务中实现一些可重复使用,可组合的功能方法、可以让

    2024年02月11日
    浏览(38)
  • go web框架 gin-gonic源码解读01————Engine

    gin-gonic是go语言开发的轻量级web框架,性能优异,代码简洁,功能强大。有很多值得学习的地方,最近准备把这段时间学习gin的知识点,通过engine,context,router,middleware几篇博客文章总结总结。 而Engine是gin框架最核心的结构体。 为什么gin需要设计一个 Engine 结构体? 因为gi

    2024年02月14日
    浏览(39)
  • go web框架 gin-gonic源码解读02————router

    本来想先写context,但是发现context能简单讲讲的东西不多,就准备直接和router合在一起讲好了 router是web服务的路由,是指讲来自客户端的http请求与服务器端的处理逻辑或者资源相映射的机制。(这里简单说说,详细的定义网上都可以查到) 那一个优秀的web router应该提供以下

    2024年02月12日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包