Skip to content

Router in Gin

Given a URL /api/users/1234/pages, how an HTTP server finds the corresponding handler for it? Router helps to convert the concrete URL to a pattern and its handler.

This blog introduces the routers in Gin framework. In short, Gin uses trie(prefix tree) to maintain the URL-handler mapping.

Gin Router

Gin Router Matching Mode

Gin router supports to match URLs exactly, wildly and catch all, and the table below shown how the matching works.

+----------+--------------------+------------------------+---------+
|          | URL                | Pattern                | Note    |
+==========+====================+========================+=========+
| Exact    | /api/users         | /api/users             | matched |
|          |                    +------------------------+---------+
|          |                    | /api/user              | x       |
+----------+--------------------+------------------------+---------+
| Wild     | /api/users/:id     | /api/users/123         | matched |
|          |                    +------------------------+---------+
|          |                    | /api/users/            | x       |
|          |                    +------------------------+---------+
|          |                    | /api/users/123/profile | x       |
+----------+--------------------+------------------------+---------+
| CatchAll | /api/users/*action | /api/users/123         | matched |
|          |                    +------------------------+---------+
|          |                    | /api/users/            | matched |
|          |                    +------------------------+---------+
|          |                    | /api/users/123/profile | matched |
+----------+--------------------+------------------------+---------+

Construct Patterns Trees in Gin Router

Basic Types and Representation

Gin router uses the idea of http router to implement its router. Gin router defines a map to store patterns of different methods because the patterns of methods vary.

type methodTree struct {
    method string
    root   *node
}

type Engine struct {
    // ignore many fields, modified by blog author
    trees methodTrees
}

The basic unit of common prefix is struct node and it has several types as listed below.

type nodeType uint8

const (
    static nodeType = iota
    root 
    param
    catchAll
)

The root means this node is the root node of a method under methodTrees. The static refers to the exact match, param refers to the wild match and the catchAll matches as its name means. The node types are generally used for pattern matching when analyzing a concrete URL, but the topic here focus on the tree construction.

Different Nodes for Different Cases

Given /api/users, the standard trie uses one node to store its whole content. This idea works well if we support exact matching in HTTP router. However, this strategy breaks when we want to support the wild/catchAll matching because the placeholder is not a normal string but a placeholder.

To analyze them, (param or catchAll) node will be created once * or : is encountered. Registering a single pattern /api/users/:id produces a routing tree looks like this:

/api/users (root node)
         └── :id    ==> /api/users/:id  (param node)

Moreover, the methodTrees look like this if /api/users and /api/users/:id are registered.

/api/users (root node)
         └── /          ==> /api/users/     (static node)
             └── :id    ==> /api/users/:id  (param node)

The routing tree looks like this if we register the following patterns.

  • /api/version
  • /api/users/:id
  • /api/users
  • /api/users/:id/messages
  • /api/users/:id/messages/*action
(root node)
/api/   
    ├── version                  ==> /api/version/   (static node)
    └── users
         └── /                   ==> /api/users/     (static node)
             └── :id             ==> /api/users/:id  (param node)
                  └── messages
                         └── /   ==> /api/users/:id/messages  (param node)
                             └── *action  
                             /api/users/:id/messages/*action  (catchAll node)

Indices of Children Nodes

The trie implementation doesn't use the integer value of character as the index to access the children nodes. Instead, it stores the indices in field indices as a string, so we can loop the string and find the index as well.

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

Unicode is supported as well by converting unicode to bytes as well, you can see httprouter#65 to learn more.

n.indices = bytesconv.BytesToString([]byte{n.path[i]})

Resolve Concrete URLs

Gin router helps to resolve concrete URLs into pattern and the corresponding handlers and the resolving entry is at the implementation of ServerHTTP interface.

After receiving the request, Gin engine gets the handlers by finding a pattern of the URL via node.getValue. Besides the trie finding functionality, resolving wild and catchAll pattern and redirecting are supported in Gin.

During resolving, it truncates the current path until a slash to extract the parameter value, creates the parameter key-value pair and appends it to parameter result. Due to the natural structure of HTTP URL is well seperated by slash, wild and catchAll matching are easy to implement.

    // Find param end (either '/' or path end)
    end := 0
    for end < len(path) && path[end] != '/' {
        end++
    }

    // Save param value
    if params != nil {
        // ignore trivial lines, added by blog author xieyuschen

        // got next index to insert the new analyzed key-value process
        i := len(*value.params)
        *value.params = (*value.params)[:i+1]
        val := path[:end]
        if unescape {
            if v, err := url.QueryUnescape(val); err == nil {
                val = v
            }
        }
        (*value.params)[i] = Param{
            Key:   n.path[1:],
            Value: val,
        }
    }