浅谈gin

相关链接

官方文档
gin项目地址
httprouter项目地址

内容概览

  1. 分析几种常见的启动http服务的方式
  2. ListenAndServe剖析
  3. 启动gin时的warning是哪里输出的
  4. gin.Default()和gin.New()主要做了什么, gin.engine是什么?
  5. 中间件源码剖析
  6. 如果将gin内置日志输出到文本
  7. gin封装了什么,基本思路是怎样的(todo: 还没整理)

文中的todo均未待补充内容, 持续更新中

1. 分析几种常见的启动http服务的方式

引入对应的包

go get -u github.com/gin-gonic/gin

几种启动http服务的方法

方式一:

func main() {
	router := gin.Default()
	router.Run()
}

方式二:

func main() {
	router := gin.Default()
	http.ListenAndServe(":8080", router)
}

方式三:

func main() {
	router := gin.Default()
	s := &http.Server{
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	s.ListenAndServe()
}

…此处省略很多很多种能启动http服务的方法

分析

下面来分析一下上面几种启动http服务的本质:
gin.go中去看router.Run()的源码:

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	trustedCIDRs, err := engine.prepareTrustedCIDRs()
	if err != nil {
		return err
	}
	engine.trustedCIDRs = trustedCIDRs
	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}

这里主要看倒数第二行的http.ListenAndServe(address, engine)
其中http是包net/http中的内容,也就是说,至此,方式一和方式二的本质是一致的

下面再来看http.ListenAndServe(address, engine)的源码:

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

不难发现,这里和方式三如出一辙
综上: 方式一二三的本质是一致的

2. ListenAndServe剖析

接着上面的代码继续看,
查看server.ListenAndServe()方法定义:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
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)
}

其中,主要看net.Listen("tcp", addr)srv.Serve(ln)
net.Listen("tcp", addr)是用于监听地址和端口的
接着看srv.Serve(ln)源码, 这里太长了,就节选部分内容:

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
	...
	go c.serve(connCtx)
	...
}

下面接着看c.serve(connCtx)

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
		...
		
		// 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)
		...
}

下面接着看ServeHTTP


func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	...
	handler.ServeHTTP(rw, req)
}

下面再接着看ServeHTTP


// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

注释的意思是这个接口是处理程序响应HTTP请求,可以理解为,实现了这个接口,就可以接收所有的请求并进行处理

即:

 Run                                                          
  │
  ↓         impliment
 gin.engine ---------→ Handler Interface
  │
  ↓
 http.ListenAndServe(address, engine)

3. 启动gin时的warning是哪里输出的

不管使用方式几去启动服务,都能在控制台看到有以下输出:
在这里插入图片描述
这里不妨思考一下,为什么会输出这个内容,具体是哪里导致的输出这个warning?
查看gin.Default()源码,如下:

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

这里大胆揣测,第一个warning是在debugPrintWARNINGDefault中输出的,这里不妨查看debugPrintWARNINGDefault源码验证一下:

func debugPrintWARNINGDefault() {
	if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
		debugPrint(`[WARNING] Now Gin requires Go 1.12+.

`)
	}
	debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

`)
}

果不其然,第一个warning是在debugPrintWARNINGDefault中输出的

gin.Default()就几行代码,肉眼可见的会输出warning的已经没了,这里不妨再假设,第二个warning是在engine := New()中输出的,那么我们就继续看engine := New()源码:

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
		TrustedProxies:         []string{"0.0.0.0/0"},
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

看到源码里的debugPrintWARNINGNew,我们不妨再点进去看看源码:

func debugPrintWARNINGNew() {
	debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

`)
}

果不其然,第二个warning是在engine := New()中的debugPrintWARNINGNew输出的

4. gin.Default()和gin.New()主要做了什么, gin.engine是什么?

gin.Default()和gin.New()主要做了什么

不妨看看gin.Default()的源码:

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

不难发现,gin.Default()主要是做了两个事情,一个是gin.New(), 一个是engine.Use(这个后面再介绍)。

下面再看看gin.New()源码:

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
		TrustedProxies:         []string{"0.0.0.0/0"},
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

不难发现,gin.Default()gin.New()的返回类型是*gin.Engine

gin.engine是什么

下面一起看看gin.Engine的源码.
下面是对*gin.Engine的备注:

// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()

gin方法可以通过用New()或者Default()来创建Engine实例查看Engine类型定义, 可以看出, Engine主要由以下内容组成:

  1. RouterGroup, 路由组(todo)
  2. pool, 用于context的复用, 减少内存的分配也提高了效率, engine.pool.New负责创建Context对象,采用sync.Pool减少频繁context实例化带来的资源消耗
  3. trees, 每一个节点的内容你可以想象成一个key->value的字典树, key是路由, 而value则是一个[]HandlerFunc, 里面存储的就是按顺序执行的中间件和handle控制器方法

其中,return前的两个地方很巧妙:

  1. engine.RouterGroup.engine = engine
    路由组 RouterGroup 中还有个 engine 的指针对象
  2. engine.pool.New = func() interface{} { // 对象池
        return engine.allocateContext()
    }
    
    看下 engine.allocateContext()
    func (engine *Engine) allocateContext() *Context {
        return &Context{engine: engine}
    }
    
    engine 中包含了 pool 对象池,这个对象池是对 gin.Context 的重用,进一步减少开销。

5. 中间件源码剖析

1.全局中间件

直接使用router.Use(), 如, gin.Default()方法中的engine.Use(Logger(), Recovery()), 也可以写成

engine.Use(Logger())
engine.Use(Recovery())

其中,Use方法的参数为middleware ...HandlerFunc, 可添加多个中间件

2.单路由的中间件

router.GET("/test", MyMiddelware(), testMethod)
这里也和全局中间件一样,可以添加多个中间件

3.群组路由的中间件

group1 := router.Group("/v1", MyMiddelware())

group1 := router.Group("/v1")
group1.Use(MyMiddelware())
{
	group1.POST("/test", testMethod)
}

group1 := router.Group("/v1")
group1.Use(MyMiddelware())
group1.POST("/test", testMethod)

剖析

engine.Use()

先看全局中间件的添加engine.Use()的方法定义:

// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

从上述代码,可以看出,

  1. Use方法单次可添加多个中间件
  2. 中间件的本质,是HandlerFunc,简称handler

再点进去查看engine.RouterGroup.Use(middleware...)的源码

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

可以看到,group有个Handlers属性,这里其实是把Use的中间件,加到group的Handlers

Group

下面看看Group方法源码

这里先看group使用中间件,最后再看单路由的

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

能看出,这里把路由组的相对路径赋值给了RouterGroupbasePath属性,其次,再调用group.combineHandlers(handlers)

group.combineHandlers(handlers)这个方法待会再细说

router.GET

下面再看看router.GET方法

router.POST等原理相同,这里就不一一举例了,统一用router.GET说明

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

router.GET本质是调用group.handle方法,源码如下:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

参数说明如下:

参数说明
httpMethodhttp调用方法
relativePath相对路径
handlers处理方法

但是回顾上面的单路由使用中间件的router.GET("/test", MyMiddelware(), testMethod),可以得出结论, 中间件和路由方法本质上是一致的, 都是HandlerFunc

group.combineHandlers(handlers)

此外, 还能看得, 在该方法里, 同样调用了group.combineHandlers(handlers)方法, 下面看看group.combineHandlers(handlers)源码:

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	// abortIndex在同一个文件中定义如下:
	// const abortIndex int8 = math.MaxInt8 / 2
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
}

上述代码的意思是:
某个单路由或者路由组下的路由, 最后都会从父路由组到子路由组最后到路由的handlerFunc依次加到一个切片里

这里会先对于handlerFunc的数量有限制, 不得大于abortIndex

group.engine.addRoute(httpMethod, absolutePath, handlers)

下面再看看group.engine.addRoute(httpMethod, absolutePath, handlers)方法定义:

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)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)

	// Update maxParams
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
		engine.maxParams = paramsCount
	}
}

addRoute主要做了以下事情:

  1. 校验路径合法性(路径, http方法, handlers)
  2. debugPrintRoute方法在debugMode下, 打印出路由信息
  3. 把路由映射关系添加到engine.trees
engine.trees

engine.treeskey-value的形式

engine.trees                                                           
         ├─ methodTree
         │       ├─ method   http方法
         │       └─ root
         │           ├─ path  路径
         │           └─ HandlersChain   Handlers切片
         ├─ ...
         ├─ ...
         ├─ ...                          
         └─ methodTree   

6. 如果将gin内置日志输出到文本

logfile, err := os.Create("./textlog.log")
if err != nil {
    fmt.Println("Could not create log file")
}
// 修改日志的defaultWriter即可
gin.DefaultWriter = io.MultiWriter(logfile)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值