美文网首页
gin.Context初探

gin.Context初探

作者: 十年磨一剑1111 | 来源:发表于2022-07-27 14:34 被阅读0次

    最近有使用gin-vue-admin框架来做一个管理后台,笔者注意到获取参数有个这样的方法:c.ShouldBindJSON() ,这样一个获取参数的方法,当然这个方法是针对post方式提交数据,于是比较好奇这个是怎么实现的,分以下两步:

    1. 数据写入Context;

    2. 从Context读取数据;

    首先我们写代码的时候会给路由函数带上一个参数(c*gin.Context), 首先这个是个固定的写法,因为gin关于http请求的handler 方法是定义是需要传入这样一个参数,下面看下源码:

    //我们在设置我们的路由一般都是这样写的,这里举几个例子
    {
            goodsManageRouter.POST("goodsList", goodsManageApi.GetGoodsList)     
            goodsManageRouter.GET("goodsDetail", goodsManageApi.GoodsDetail)   
            goodsManageRouter.POST("addGoods", goodsManageApi.AddGoods) 
        }
    //真正处理的函数一般这样写
    func(g *GoodsManageApi) GetGoodsList(c *gin.Context){
          params := request.GoodsListReq{}
       // s := c.Query("name")
        _ = c.ShouldBindJSON(¶ms)
        ...
    }
    
    //这里举两个例子POST和GET方法,这两个方法实际都是注册路由,其中handlers 这个参数是
    //HandlerFunc 类型的
    // POST is a shortcut for router.Handle("POST", path, handle).
    type HandlerFunc func(*Context)  //表示参数的类型是Context指针类型
    func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
        return group.handle(http.MethodPost, relativePath, handlers)
    }
    
    // GET is a shortcut for router.Handle("GET", path, handle).
    func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
        return group.handle(http.MethodGet, relativePath, handlers)
    }
    

    从以上代码我们知道为啥会是带c *gin.Context 参数这种固定写法了。但是我们还是不知道数据是怎么写入到这个c里面的,如果想要了解这些,我们需要从服务(这里是http服务)的运行到执行有个比较清晰的了解,整个过程大致是:监听tcp端口(listen)——>接受连接(accept)——>开启协程处理,下面附上我仔细看了下的源码:

    1)net\http\server.go

    func (srv *Server) ListenAndServe() error {
        if srv.shuttingDown() {  //检测服务是否关闭
            return ErrServerClosed
        }
        addr := srv.Addr
        if addr == "" {
            addr = ":http"
        }
        ln, err := net.Listen("tcp", addr)  //监听端口
        if err != nil {
            return err
        }
        return srv.Serve(ln)  //进行处理,并返回数据
    }
    

    上面这段基本都看得懂,下面看下真正的处理函数;

    2)net\http\server.go

    //从监听器上接受即将到来的连接,针对每个连接都会创建一个goroutine去处理
    // goroutine会读取请求然后调用handler处理并返回结果
    // 当监听器返回的是*tls.Conn的时候才支持HTTP/2,tls是加密连接
    func (srv *Server) Serve(l net.Listener) error {
     ...
     ....
     
        var tempDelay time.Duration // how long to sleep on accept failure
    
        ctx := context.WithValue(baseCtx, ServerContextKey, srv)
        for {
            rw, err := l.Accept()
            if err != nil {
                select {
                case <-srv.getDoneChan():
                    return ErrServerClosed
                default:
                }
                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
            c := srv.newConn(rw)
            c.setState(c.rwc, StateNew, runHooks) // before Serve can return
            go c.serve(connCtx)   //开启协程调用方法
        }
    }
    
    1. net\http\server.go
    // Serve a new connection.
    func (c *conn) serve(ctx context.Context) {
        ...
        ...
    
        for { 
            w, err := c.readRequest(ctx)    //循环读取请求
         ...
       ...
    
            // 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.
            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, runHooks)
            c.curReq.Store((*response)(nil))
    
            ...
            ...
        }
    }
    

    下面重点看下serverHandler{c.server}.ServeHTTP(w, w.req) 这行代码里面做的事情

    4)net\http\server.go

    type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
    }
    ....
    ....
    ....
    func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
        handler := sh.srv.Handler  //获取Handler
        if handler == nil {
            handler = DefaultServeMux
        }
        if req.RequestURI == "*" && req.Method == "OPTIONS" {
            handler = globalOptionsHandler{}
        }
    
        if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
            var allowQuerySemicolonsInUse int32
            req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
                atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
            }))
            defer func() {
                if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
                    sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
                }
            }()
        }
    
        handler.ServeHTTP(rw, req)   //处理请求
    }
    

    从上面这段代码可以看出ServeHTTP函数最终会调用handler.ServeHTTP()方法,但是handler在不为空或者请求的URI不是*号和请求的方法不是OPTIONS的情况下实际是一个接口类型,接口里面包含了ServeHTTP这个方法,也就是说这个方法的调用实际是和传入的handler参数有关,是由参入参数来实现这个方法的,于是我们需要找到路由注册的地方,也就是初始化服务传入的Handler类型,找到了对应的ServeHTTP方法

    5) gin.go

    func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
        c := engine.pool.Get().(*Context)  //从对象池里面拿到一个对象,并断言为Context指针类型变量
        c.writermem.reset(w)
        c.Request = req   // 当前请求写入到context 对象中
        c.reset()
    
        engine.handleHTTPRequest(c)  // 处理请求
    
        engine.pool.Put(c)
    }
    

    6)gin.go

    func (engine *Engine) handleHTTPRequest(c *Context) {
         ...
         ...
        // 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
            // Find route in tree
            value := root.getValue(rPath, c.params, 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 != "CONNECT" && rPath != "/" {
                if value.tsr && engine.RedirectTrailingSlash {
                    redirectTrailingSlash(c)
                    return
                }
                if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                    return
                }
            }
            break
        }
    
        ...
        ...
    }
    

    从上面一段代码可以看出如果当前可以匹配到处理的方法,便会执行value.handlers这段代码,通过调用c.Next()方法执行指定的处理函数,下面来看下c.Next()方法:

    7)gin@1.7.0\context.go

    // Next()方法只能在中间件中使用
    //在正在被调用的handler 里面执行被挂起的handler, 这是翻译的效果,笔者的理解就是在中间件
    //中去执行一个handler,执行完后接着中间件下面的代码继续执行
    
    func (c *Context) Next() {
        c.index++
        for c.index < int8(len(c.handlers)) {
            c.handlers[c.index](c)  //执行方法,c参数是被赋值了当前请求requst的相关信息
            c.index++
        }
    }
    

    到此,我们就解释了文章开头的第一个问题,这里笔者没有对这些代码做详细的分析,就大致看下整个流程。好了下面再来看第二个问题,取数据;

    取数据很容易,大致的流程是调用把请求体req.Body解析到指定的对象中,最终解析的代码如下:

    func decodeJSON(r io.Reader, obj interface{}) error {
        decoder := json.NewDecoder(r)
        if EnableDecoderUseNumber {
            decoder.UseNumber()   //防止数据被转换成float64
        }
        if EnableDecoderDisallowUnknownFields {
            decoder.DisallowUnknownFields()  //如果目标结构体中不包含传入的某个字段则会返回一个错误
        }
        if err := decoder.Decode(obj); err != nil {
            return err
        }
        return validate(obj)
    }
    

    具体是如何解析的这篇文章就不详细解释了,后面笔者会出一篇文章详细解释下decode操作。

    相关文章

      网友评论

          本文标题:gin.Context初探

          本文链接:https://www.haomeiwen.com/subject/lpjzirtx.html