Gin框架源码解析

Gin框架源码解析

2018-09-18 19:44 by 轩脉刃, ... 阅读, ... 评论, 收藏, 编辑

Gin框架源码解析

Gin框架是golang的一个常用的web框架,最近一个项目中需要使用到它,所以对这个框架进行了学习。gin包非常短小精悍,不过主要包含的路由,中间件,日志都有了。我们可以追着代码思考下,这个框架是如何一步一步过来的。

从http包说起

基本上现在的golang的web库都是从http上搭建起来,golang的http包的核心如下:

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

这里的Handler是一个接口

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

所以,这里就是我们的入口,这里我们需要有一个类来实现这个接口:Engine。

type Engine struct {

}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ...
}

这里ServeHTTP的方法传递的两个参数,一个是Request,一个是ResponseWriter,Engine中的ServeHTTP的方法就是要对这两个对象进行读取或者写入操作。而且这两个对象往往是需要同时存在的,为了避免很多函数都需要写这两个参数,我们不如封装一个结构来把这两个对象放在里面:Context

type Context struct {
  writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter
  ...
}


type responseWriter struct {
    http.ResponseWriter
    size   int
    status int
}

这里有几个需要讨论的点:

Writer是否可以直接使用http包的ResponseWriter接口

type ResponseWriter interface {

    Header() Header

    Write([]byte) (int, error)

    WriteHeader(statusCode int)
}

但是考虑到我们web框架的最重要的就是输出数据给客户端,这里的输出逻辑我们极有可能需要自己封装一些框架自带的方法。所以我们不妨自定义一个结构responseWriter,来实现基本的http.ResponseWriter。并且实现一些具体的其他方法。这些具体的其他方法都有哪些呢?我们使用gin包自带的ResponseWriter接口来说明。

type ResponseWriter interface {
    responseWriterBase

    Pusher() http.Pusher
}

type responseWriterBase interface {
    http.ResponseWriter
    http.Hijacker
    http.Flusher
    http.CloseNotifier

    Status() int

    Size() int

    WriteString(string) (int, error)

    Written() bool

    WriteHeaderNow()
}

为什么Context有writermem和Writer两个实现了http.Response对象的结构?

首先我们自带的ResponseWriter必须实现比http.ResponseWriter更强大的接口功能,这个是毋庸置疑的。所以,我们不妨考虑下这里如果不是两个writermem和Writer两个的话,只有一个存在是否可能?

如果只有Writer接口存在,这个一定不可能,这个Writer实现的是我们gin自定义的接口,外部serveHTTP传递的是实现了http.ResponseWriter的类,并不能保证实现了gin自带的ResponseWriter。

如果只有writermen结构存在,这个是可能的。外部传递的http.ResponseWriter就被藏在了这个对象里面。但是这样就丢失了接口的灵活性。本质还是对外暴露的是接口还是结构的逻辑,设想一下如果使用这个框架的用户要自己实现一个ResponseWriter,就需要继承这个结构,而不是继承接口。而具体的调用的方法就变成了被继承结构的方法了。例子如下:

package main

func main() {
    customResp := new(customResponseWriter)

    c := new(context)
    c.Writermem = customResp.responseWriter
    c.Writermem.call()
}

type context struct {
    Writermem responseWriter
}

type customResponseWriter struct {
    responseWriter
}

func (r *customResponseWriter)call() {

}

type responseWriter struct{}

func (r *responseWriter)call() {

}

所以这里的Context结构,对外暴露的是接口ResponseWriter,内部的responseWriter结构实现了ResponseWriter接口。在reset()的时候进行拷贝是合理的。

func (c *Context) reset() {
    c.Writer = &c.writermem
    c.Params = c.Params[0:0]
    c.handlers = nil
    c.index = -1
    c.Keys = nil
    c.Errors = c.Errors[0:0]
    c.Accepted = nil
}

context就是某个请求的上下文结构,这个结构当然是可以不断new的,但是new这个对象的代价可以使用一个对象池进行服用,节省对象频繁创建和销毁的开销。golang中的sync.Pool就是用于这个用途的。需要注意的是,这里的对象池并不是所谓的固定对象池,而是临时对象池,里面的对象个数不能指定,对象存储时间也不能指定,只是增加了对象复用的概率而已。

type Engine struct {
  ...
    pool             sync.Pool
  ...
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

这个Context是gin中最重要的数据结构之一了,它既然已经包了request了,那么从请求中获取参数的各个接口它必然也需要包了。


func (c *Context) Param(key string) string
...

func (c *Context) Query(key string) string

func (c *Context) DefaultQuery(key, defaultValue string) string

...

func (c *Context) PostFormArray(key string) []string

路由

从http请求进来的逻辑理清楚了,下面就进入到了路由部分,路由其实还是分为两个部分,一个是路由设置部分,一个是路由匹配部分。

路由其实并不仅仅是url,还包括HTTP的请求方法,而实现一个REST风格的http请求,需要支持REST支持的方法,比如GET,PUT,POST,DELETE,OPTION等。

路由一定是有很多个路由路径,可以使用数组存储,但更巧妙的是,使用Redix树结构进行存储。这样寻找的方法更为高效。

首先我们会在Engine这个结构中增加树结构,并且提供增加路由的功能

type Engine struct {
  ...
    pool             sync.Pool
    trees            methodTrees
}

type methodTrees []methodTree

type methodTree struct {
    method string
    root   *node
}

type node struct {
    path      string
    indices   string
    children  []*node
    handlers  HandlersChain
    priority  uint32
    nType     nodeType
    maxParams uint8
    wildChild bool
}

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    assert1(path[0] == '/', "path must begin with '/'")
    assert1(method != "", "HTTP method can not be empty")
    assert1(len(handlers) > 0, "there must be at least one handler")

    debugPrintRoute(method, path, handlers)
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值