Hi, I’m HHF.

Past review:

  • Gin source Code Reading (1) – Gin in relation to NET/HTTP
  • Gin Source Code Reading (2) – How do HTTP requests flow into GIN?

The previous two articles have basically covered the logic of how Web Server receives client requests and how requests flow to GIN. For those of you who haven’t read it, read the first one.

Gin principle analysis here, it is completely into the logic of GIN. Gin has already received the HTTP request, and the first important thing must be to rewrite the route, so this section focuses on analyzing gin’s routing.

Gin’s routing is not entirely self-written. In fact, an important part of the code is the use of open source Julienschmidt/Httprouter. Gin also adds some of its own functions, such as RouterGroup.

What is routing?

This is actually quite easy to understand, is based on different URLS to find the corresponding handler.

Currently, the design of server-side API interfaces generally follows RESTful specifications. Of course, I have also seen some large companies, in order to reduce the mental burden and learning cost of developers, make no distinction between GET/POST/DELETE requests at all.

Take a simple example like “Delete user”

RESTful: DELETE /user/hhf No RESTful: GET /deleteUser? name=hhfCopy the code

This RESTful approach does sometimes reduce communication issues and learning costs, but it can only be used internally. Web frameworks that don’t differentiate between GET and POST tend to be designed to be flexible, but have a wide variety of developers, leading to a lot of “interface cancer” that you can’t do anything about until you find it, such as the following interfaces:

GET /selectUserList? UserIds =[1,2,3] -> can arguments be arrays? GET /getStudentlist? SkuIdCntMap ={"200207366":1} -> Can the parameter be a dictionary?Copy the code

This interface design results in open source frameworks being unable to parse and having to decode strings by hand, layer by layer, which I won’t cover in detail here, but more on in the next section when I cover the Gin Bind series of functions.

Returning to the RESTful interface above, take the following simple requests:

GET    /user/{userID} HTTP/1.1
POST   /user/{userID} HTTP/1.1
PUT    /user/{userID} HTTP/1.1
DELETE /user/{userID} HTTP/1.1
Copy the code

This is a standard RESTful API design, which represents:

  • Get the user information of the userID
  • Update the userID’s user information (and of course its JSON body, not written)
  • The user who created the userID (and, of course, its JSON body, which is not written out)
  • Example Delete the user of userID

You can see that the same URI, different request methods, and ultimately the other representatives have completely different things to deal with.

If you were to design this route, how would you design it to satisfy these functions?

Gin Routing Design

How to design different methods?

From the introduction above, we already know that RESTful is about distinguishing methods, and different methods have completely different meanings. How does GIN achieve this?

As a matter of fact, each Method is a routing tree, so when GIN registers routes, it registers different routing trees according to different methods.

GET    /user/{userID} HTTP/1.1
POST   /user/{userID} HTTP/1.1
PUT    /user/{userID} HTTP/1.1
DELETE /user/{userID} HTTP/1.1
Copy the code

For these four requests, four routing trees will be registered.

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    //....
    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)
    // ...
}
Copy the code

The implementation code is also easy to read,

  • When you get a method method, go through the Trees Slice
  • If the method exists in the Trees slice, the handler for the URL is added directly to the route tree found
  • If not, create a new method tree and add the URL handler to the routing tree

Gin routing registration process

func main(a) {
    r := gin.Default()
    r.GET("/ping".func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}
Copy the code

In this simple code, R.gate registers a route /ping into the GET tree. This is the most common and most commonly used way to register.

In normal cases, we will take the handler to the Controller layer, and put the registered route in the special route management. We will not expand in detail here, and we will talk about gin architecture layer design later.

//controller/somePost.go
func SomePostFunc(ctx *gin.Context) {
    // do something
    context.String(http.StatusOK, "some post done")}Copy the code

```go
// route.go
router.POST("/somePost", controller.SomePostFunc)
Copy the code

Using RouteGroup

v1 := router.Group("v1")
{
    v1.POST("login".func(context *gin.Context) {
        context.String(http.StatusOK, "v1 login")})}Copy the code

RouteGroup is a very important function. For example, a complete server service can use RouteGroup to implement urls that can be divided into authentication interfaces and non-authentication interfaces. In fact, it is most commonly used to distinguish between interface version upgrades. These operations will eventually be on the routing tree that reflects to GIN

Gin routing concrete implementation

func main(a) {
    r := gin.Default()
    r.GET("/ping".func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}
Copy the code

So let’s start with this simple example. We just need to figure out the following three questions:

  • Where is the URL->ping?
  • Where is handler-> put?
  • How do urls and handlers relate?

1. GET/POST/DELETE/.. Ultimate destination

func (group *RouterGroup) GET(relativePath string, handlers ... HandlerFunc) IRoutes {
	  return group.handle(http.MethodGet, relativePath, handlers)
}
Copy the code

The Handle function is called when the HTTP functions related to the route, such as POST, GET, and HEAD, are called. Handle is a unified entry point for GIN routing.

// routergroup.go:L72-77
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()
}
Copy the code

2. Generate a routing tree

Let’s consider a case where you have a route that looks like this. How would you design the route tree?

GET /abc 
GET /abd
GET /af
Copy the code

Of course, the simplest and most crude way is to occupy a leaf node of the tree per string, but this design brings problems:

  • As we can see, ABC, ABD and AF all use the same prefix. If the prefix can be shared, the memory can be saved

The GIN routing tree is a prefix tree. We talked earlier about each of gin’s methods (POST, GET…) Each has its own tree, of course, this is based on the way you register, not the first way to register all. Each gin route looks like the following

There is too much code in this process, so I will not post the specific code here. If you are interested, you can follow this idea.

3. Handler is associated with the URL

type node struct {
    path      string
    indices   string
    wildChild bool
    nType     nodeType
    priority  uint32
    children  []*node // child nodes, at most 1 :param style node at the end of the array
    handlers  HandlersChain
    fullPath  string
}
Copy the code

Node is the overall structure of the routing tree

  • Children are the leaves of a tree. Each route, stripped of its prefix, is distributed in the children array
  • Path is the longest prefix for the current leaf node
  • The Handlers store the route handlers for the current leaf node

How do I find the handler of the route when receiving a client request?

Gin Source Code Reading (2) – How do HTTP Requests Flow into GIN? The second article talked about the very important asp.net/HTTP function ServeHTTP, which the server must go to when it receives a request. Since GIN implements this server HTTP, the traffic is diverted into GIN’s logic.

// gin.go:L439-443
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)
}
Copy the code

Therefore, when gin receives a request from a client, the first thing it does is go into the routing tree to match the corresponding URL, find the relevant route, and retrieve the relevant handler. That’s exactly what handleHTTPRequest does.


func (engine *Engine) handleHTTPRequest(c *Context) {
    // ...
    t := engine.trees
    for i, tl := 0.len(t); i < tl; i++ {
        ift[i].method ! = httpMethod {continue
        }
        root := t[i].root
        // Find route in tree
        value := root.getValue(rPath, c.params, unescape)
        ifvalue.params ! =nil {
            c.Params = *value.params
        }
        ifvalue.handlers ! =nil {
            c.handlers = value.handlers
            c.fullPath = value.fullPath
            c.Next()
            c.writermem.WriteHeaderNow()
            return
        }
        ifhttpMethod ! ="CONNECT"&& rPath ! ="/" {
            if value.tsr && engine.RedirectTrailingSlash {
                redirectTrailingSlash(c)
                return
            }
            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
                return}}break
    }
  // ...
}
Copy the code

The process is actually quite simple from the code:

  • Traverse all routing trees to find the tree corresponding to the method
  • The route was matched
  • Find the corresponding handler

conclusion

Here, basically the whole process of GIN routing is clear, but the detailed implementation of routing tree is relatively general, welcome interested students to join the group for detailed discussion (add my friend, I will pull you into the group).

Writing an article is not easy, if you think this article is good, please help to like the share, thank you.