https://github.com/gin-gonic/gin
Gin
is aWeb
framework written inGolang
, a routing based onRadix
tree, small memory footprint, no reflection, predictableAPI
framework, the speed is nearly 40 times higher, support Middleware, routing group processing, JSON and other multi-mode validation, built-inJSON
,XML
,HTML
and other rendering. It is an easy-to-useAPI
framework; this series of articles will specifically reveal the source code ofGin
. Please see an example below:
func main() {
// init a rounter
r := gin.New()
// global middleware
// Logger: into gin.DefaultWriter
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery: recover any panic。Will be 500。
r.Use(gin.Recovery())
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// authorized := r.Group("/", AuthRequired())
authorized := r.Group("/")
// AuthRequired()
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
testing := authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
r.Run(":8080")
}
The following figure describes the entire
Engine
related structure combination;gin.Engine
is a container object, which is the basis of the entire framework;gin.RouterGroup
is responsible for storing all middleware, including request paths;gin.trees
Responsible for storing the mapping between routes andhandle
methods, using theRadix
tree structure; the following will focus on a brief description ofEngine
parameters; other parts will be analyzed in subsequent series of articles.
RouterGroup
Responsible for storing all middleware, including request paths.
RedirectTrailingSlash
Whether to enable redirection requests for routes with the suffix /, such as requesting /foo/ but only /foo routes exist, the client uses http status 307 to redirect to /foo for requests
RedirectFixedPath
If this parameter is enabled, the router will try to fix the current request address by itself when no handler is registered.
- The first redundant element will be deleted (../ or //);
- Then the routing will perform a case-insensitive lookup on the new path;
- If the corresponding handler can be found normally, the route will be redirected to the correct handler and return 301 or 307. (For example: users visiting /FOO and /..//Foo may be redirected to /foo on the route)
HandleMethodNotAllowed
Check if the current path allows routing using other request methods.
ForwardedByClientIP
Whether to forward the client ip.
Remote IP Headers
Obtain the client IP in the following cases. ForwardedByClientIP is true; context.Request.RemoteAddr has a value
Trusted Proxies
Network origin list (IPv4 address, IPv4 CIDR, IPv6 address or trust IPv6 CIDR of a request header containing: when
(*gin.Engine).ForwardedByClientIP
istrue
.
App Engine
If enabled, a header starting with "X-AppEngine..." will be added to the request.
UseRawPath
If enabled, url.RawPath will be used to find parameters
UnescapePathValues
If true, path values will not be escaped. If UseRawPath is false (which is the default), UnescapePathValues is actually true, as url.Path will be used, which has been escaped.
MaxMultipartMemory
The value assigned to the 'maxMemory' parameter of ParseMultipartForm of http.Request.
RemoveExtraSlash
Whether to remove extra backslashes (requests with extra slashes can be parsed at the beginning)
delims
Delims represent a set of left and right delimiters for HTML template rendering.
secureJSONPrefix
Set the json prefix in Context.SecureJSON China
HTMLRender
Return HTMLRender interface (used to render HTMLProduction and HTMLDebug structure type templates)
FuncMap
FuncMap map[string]interface{} in the html/template package, used to define the mapping from names to functions.
allNoRoute
Make a copy of the global
handlers
, addNoRoute
processing method
allNoMethod
Make a copy of the global
handlers
, add theNoMethod
processing method
noRoute
noRoute adds a handler for NoRoute. It returns 404 code by default
noMethod
noMethod adds a handler for NoMethod, which returns a 405 code by default
pool
Mainly used to store
context
context object, used to optimize performance when processinghttp
requests
trees
Responsible for storing the mapping of routes and handle methods, using the Radix tree structure.
maxParams
When allocating
context
, initialize the slice length of the parameterParams
trustedCIDRs
IP network address pointer slice
The
Gin
framework exposes two methods for initializingGin
instances, theNew
andDefault
methods. The difference between the two is that theDefault
method adds the use ofLogger
andRecovery
comes with middleware processing. If you need to write these two middleware by yourself, you can add theUse
method on the basis of callingNew
to register the middleware. Two core functions are mainly implemented in the initialization of theGin
instance: the initialization of the routing groupRouterGroup
and the initialization of thepool
are mainly used to store thecontext
context object, which is used to optimize the performance ofhttp
request processing.
// return Engine
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{ //init 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), //init router tree
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} { //init Pool
return engine.allocateContext()
}
return engine
}
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
There are mainly two kinds of middleware in
Gin
framework: global middleware and routing group middleware.
Global Middleware
: Write records toRouterGroup.Handlers
by callingGin.Use()
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
Router Group Middleware
: Return a newly generatedRouterGroup
pointer throughRouterGroup.Group
, which is used to separate each routing group to load different middleware. Note thatgroup.combineHandlers(handlers)
will copy a copy of the global middleware to the newly generatedRouterGroup
.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
Several methods for registering routes are specified in the
Gin
framework:GET
,POST
,DELETE
,PUT
,HEAD
,OPTIONS
,ANY
,PATCH
. Regardless of the request method,RouterGroup.handler
will be called to add routes. In this method,group.combineHandlers(handlers)
will also copy a copy of the global middleware and add thehandlers
of the corresponding route. Form aList
into the tree nodes, and finally calltree.addRoute
to add nodes.
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPost, relativePath, handlers)
}
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodDelete, relativePath, handlers)
}
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPatch, relativePath, handlers)
}
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodPut, relativePath, handlers)
}
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodOptions, relativePath, handlers)
}
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodHead, relativePath, handlers)
}
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
group.handle(http.MethodGet, relativePath, handlers)
group.handle(http.MethodPost, relativePath, handlers)
group.handle(http.MethodPut, relativePath, handlers)
group.handle(http.MethodPatch, relativePath, handlers)
group.handle(http.MethodHead, relativePath, handlers)
group.handle(http.MethodOptions, relativePath, handlers)
group.handle(http.MethodDelete, relativePath, handlers)
group.handle(http.MethodConnect, relativePath, handlers)
group.handle(http.MethodTrace, relativePath, handlers)
return group.returnObj()
}
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()
}
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)
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
}
HTTP
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
}
HTTPS
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() { debugPrintError(err) }()
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
return
}
Unix Socket
func (engine *Engine) RunUnix(file string) (err error) {
debugPrint("Listening and serving HTTP on unix:/%s", file)
defer func() { debugPrintError(err) }()
listener, err := net.Listen("unix", file)
if err != nil {
return
}
defer listener.Close()
defer os.Remove(file)
err = http.Serve(listener, engine)
return
}
File
func (engine *Engine) RunFd(fd int) (err error) {
debugPrint("Listening and serving HTTP on fd@%d", fd)
defer func() { debugPrintError(err) }()
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
listener, err := net.FileListener(f)
if err != nil {
return
}
defer listener.Close()
err = engine.RunListener(listener)
return
}
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)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method //http请求方法,get、post
rPath := c.Request.URL.Path //path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// scan httpMethod node in tree
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// find path
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params //Params
}
if value.handlers != nil {
c.handlers = value.handlers //handlers
c.fullPath = value.fullPath //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
}
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
// http code = 404
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}