Gin是一款高性能的Go语言Web框架。 Gin的一些特性:
(w http.ResponseWriter, req *http.Request)
,易用性低,需要直接从请求中读取数据、反序列化,响应时手动序列化、设置Content-Type、写响应内容,返回码等。使用gin可以帮我们更专注于业务处理下面从一个简单的包含基础路由和路由组路由的demo开始分析
func main() {
// 初始化
mux := gin.Default()
// 设置全局通用handlers,这里是设置了engine的匿名成员RouterGroup的Handlers成员
mux.Handlers = []gin.HandlerFunc{
func(c *gin.Context) {
log.Println("log 1")
c.Next()
},
func(c *gin.Context) {
log.Println("log 2")
c.Next()
},
}
// 绑定/ping 处理函数
mux.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "ping")
})
mux.GET("/pong", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
mux.GET("/ping/hello", func(c *gin.Context) {
c.String(http.StatusOK, "ping hello")
})
mux.GET("/about", func(c *gin.Context) {
c.String(http.StatusOK, "about")
})
// system组
system := mux.Group("system")
// system->auth组
systemAuth := system.Group("auth")
{
// 获取管理员列表
systemAuth.GET("/addRole", func(c *gin.Context) {
c.String(http.StatusOK, "system/auth/addRole")
})
// 添加管理员
systemAuth.GET("/removeRole", func(c *gin.Context) {
c.String(http.StatusOK, "system/auth/removeRole")
})
}
// user组
user := mux.Group("user")
// user->auth组
userAuth := user.Group("auth")
{
// 登陆
userAuth.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "user/auth/login")
})
// 注册
userAuth.GET("/register", func(c *gin.Context) {
c.String(http.StatusOK, "user/auth/register")
})
}
mux.Run("0.0.0.0:8080")
}
初始化步骤主要是初始化engine与加载两个默认的中间件
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
// 初始化engine实例
engine := New()
// 默认加载log & recovery中间件
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
engine是gin中的核心对象,gin通过 Engine 对象来定义服务路由信息、组装插件、运行服务,是框架的核心发动机,整个 Web 服务的都是由它来驱动的 关键字段:
func New(opts ...OptionFunc) *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
// 默认的basePath为/,绑定路由时会用到此参数来计算绝对路径
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
// 池化gin核心context对象,有新请求来时会使用到该池
engine.pool.New = func() any {
return engine.allocateContext(engine.maxParams)
}
return engine.With(opts...)
}
Engine.Use函数用于将中间件添加到当前的路由上,位于gin.go中,代码如下:
// 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
}
实际上,还需要进一步调用engine.RouterGroup.Use(middleware...)
完成实际的中间件注册工作,函数位于gin.go中,代码如下:
// 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()
}
实际上就是把中间件(本质是一个函数)添加到HandlersChain类型(实质上为数组type HandlersChain []HandlerFunc
)的group.Handlers中。
type HandlerFunc func(*Context)
如果需要实现一个中间件,那么需要实现该类型,函数参数只有*Context
ServeHTTP
函数,在响应一个用户的请求时,都会先从临时对象池中取一个context对象。使用完之后再放回临时对象池。为了保证并发安全,如果在一次请求新起一个协程,那么一定要copy这个context进行参数传递。type Context struct {
writermem responseWriter
Request *http.Request // 请求对象
Writer ResponseWriter // 响应对象
Params Params // URL 匹配参数
handlers HandlersChain // // 请求处理链
...
}
调用绑定函数:
mux.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "ping")
})
函数实际上走到了engine对象的匿名成员RouterGroup的handle函数中
// POST is a shortcut for router.Handle("POST", path, handlers).
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, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
// DELETE is a shortcut for router.Handle("DELETE", path, handlers).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodDelete, relativePath, handlers)
}
// PATCH is a shortcut for router.Handle("PATCH", path, handlers).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPatch, relativePath, handlers)
}
// PUT is a shortcut for router.Handle("PUT", path, handlers).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPut, relativePath, handlers)
}
绑定逻辑
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 使用相对路径与路由组basePath 计算绝对路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 将函数参数中的 "处理函数" handlers与本路由组已有的Handlers组合起来,作为最终要执行的完整handlers列表
handlers = group.combineHandlers(handlers)
// routerGroup会存有engine对象的引用,调用engine的addRoute将绝对路径与处理函数列表绑定起来
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
从源码中 handlers = group.combineHandlers(handlers)
可以看出我们也可以给gin设置一些全局通用的handlers,这些handlers会绑定到所有的路由方法上,如下:
// 设置全局通用handlers,这里是设置了engine的匿名成员RouterGroup的Handlers成员
mux.Handlers = []gin.HandlerFunc{
func(c *gin.Context) {
log.Println("log 1")
c.Next()
},
func(c *gin.Context) {
log.Println("log 2")
c.Next()
},
}
addRoute函数
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)
// 每个HTTP方法(如:GET,POST)的路由信息都各自由一个树结构来维护,该树结构的模型与函数实现位于gin/tree.go中,此处不再继续展开。不同http方法的树根节点组成了 engine.trees 这个数组
// 从engine的路由树数组中遍历找到该http方法对应的路由树的根节点
root := engine.trees.get(method)
if root == nil {
// 如果根节点不存在,那么新建根节点
root = new(node)
root.fullPath = "/"
// 将根节点添加到路由树数组中
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 调用根节点的addRoute函数,将绝对路径与处理函数链绑定起来
root.addRoute(path, handlers)
...
}
// 路由树数组数据结构
type methodTree struct {
method string root *node
}
type methodTrees []methodTree
// system组
system := mux.Group("system")
// system->auth组
systemAuth := system.Group("auth")
{
// 获取管理员列表
systemAuth.GET("/addRole", func(c *gin.Context) {
c.String(http.StatusOK, "system/auth/addRole")
})
// 添加管理员
systemAuth.GET("/removeRole", func(c *gin.Context) {
c.String(http.StatusOK, "system/auth/removeRole")
})
}
// user组
user := mux.Group("user")
// user->auth组
userAuth := user.Group("auth")
{
// 登陆
userAuth.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "user/auth/login")
})
// 注册
userAuth.GET("/register", func(c *gin.Context) {
c.String(http.StatusOK, "user/auth/register")
})
}
Group函数会返回一个新的RouterGroup对象,每一个RouterGroup都会基于原有的RouterGroup而生成
joinPaths("/" + "system")
join("/system", "auth")
,// 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) {
...
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
可以看到,最核心的监听与服务实质上是调用Go语言内置库net/http的http.ListenAndServe
函数实现的。
// 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()
}
// 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)
}
ListenAndServe函数实例化Sever,调用其ListenAndServe
函数实现监听与服务功能。 在gin中,Engine对象以Handler接口的对象的形式被传入给了net/http库的Server对象,作为后续Serve对象处理网络请求时调用的函数。
net/http的Server结构体类型中有一个Handler接口类型的Handler。
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
// Addr optionally specifies the TCP address for the server to listen on,
// in the form "host:port". If empty, ":http" (port 80) is used.
// The service names are defined in RFC 6335 and assigned by IANA.
// See net.Dial for details of the address format.
Addr string
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// ...
}
//Handler接口有且只有一个函数,任何类型,只需要实现了该ServeHTTP函数,就实现了Handler接口,就可以用作Server的Handler,供HTTP处理时调用。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Server.Serve
函数用于监听、接受和处理网络请求,代码如下:
// 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 {
//...
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)
}
}
在Server.Serve
函数的实现中,启动了一个无条件的for循环持续监听、接受和处理网络请求,主要流程为:
l.Accept()
调用在无请求时保持阻塞,直到接收到请求时,接受请求并返回建立的连接;go c.serve(connCtx)
);func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
...
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
// 读取请求
w, err := c.readRequest(ctx)
...
// 根据请求路由调用处理器处理请求
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
...
}
}
一个连接建立之后,该连接中所有的请求都将在这个协程中进行处理,直到连接被关闭。在 for 循环里面会循环调用 readRequest 读取请求进行处理。可以在第16行看到请求处理是通过调用 serverHandler结构体的ServeHTTP函数 进行的。
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
可以看到上面第八行 handler := sh.srv.Handler
,在gin框架中,sh.srv.Handler其实就是engine.Handler()
func (engine *Engine) Handler() http.Handler {
if !engine.UseH2C {
return engine
}
// 使用了标准库的h2c(http2 client)能力,本质还是使用了engine对象自身的ServeHTTP函数
h2s := &http2.Server{}
return h2c.NewHandler(engine, h2s)
}
engine.Handler()函数使用了http2 server的能力,实际的逻辑处理还是依赖engine自身的ServeHTTP()函数。
gin在gin.go中实现了ServeHTTP
函数,代码如下:
// ServeHTTP conforms to the http.Handler interface.
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)
}
主要步骤为:
http.ResponseWriter
实例与http.Request
实例;engine.handleHTTPRequest(c)
封装实现了;gin中对每个连接都需要的上下文对象进行缓存化存取,通过缓存池节省高并发时上下文对象频繁创建销毁造成内存频繁分配与释放的代价。
handleHTTPRequest
函数封装了对请求进行处理的具体过程,位于gin/gin.go中,代码如下:
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。