An overview of the

Recently, I was developing my own Web framework Bingo. I also checked some routing toolkits on the market, but some of them failed to meet my needs.

For example, I’d like to get some features of the Laravel framework:

  • Fast route lookup

  • Dynamic Routing Support

  • Middleware support

  • Routing Group Support

The fastest is Httprouter, which I modified a few months ago: Adapting HttprOuter to support middleware, but at the time it was coupled to the Bingo framework and middleware did not support interception, here I needed to pull it out and make a third-party package that could be referenced directly without relying on the Bingo framework

So I used HttprOuter as the base package and modified it to support the above features.

Warehouse address: Bingu-router

The usage has been very clear in the README of the project, so I will not repeat it here. If you have any questions or needs, please send me an issue

It is also recommended to read the readme. md article before reading this article, otherwise there may be some places to read it.

The transformation is mainly divided into two parts

  1. The first part is going tohttprouterThe routing treetreeOn the mounthandleChange the method to our custom structure

Httprouter can be seen in 5.2 Router request Routing

In simple terms, it is to construct a prefix tree for the paths of all interfaces, and put the paths with the same prefix in the branch of a tree, so that the search speed can be accelerated. Each leaf represents a route method found, and a method is mounted.

However, the prefix tree can only mount methods and cannot add any additional information, so the first step is to mount a custom structure to the prefix tree so that we can find mounted middleware, route prefixes, etc

  1. The second part is to implement middleware functions. If you just iterate over an array of middleware, you can’t intercept some operations.

    For example, if we want to implement a middleware to verify that the user is logged in, and the user who is not logged in will return an error message, then if we iterate through an array of middleware, the final route will still be executed

    In order to achieve the interception function, I referred to the implementation principle of Pipeline function in Laravel, and realized a Pipeline object to achieve the above effect

Began to transform

1. Part 1

  1. In our plan, we plan to implement routing group, middleware, routing prefix functions, so we need to customize the structure as follows:

    
       / / routing
       type Route struct {
       	path         string             / / path
           targetMethod TargetHandle       // The method to execute
           method       string             // The access type is get, post or whatever
           name         string             / / routing
           mount        []*Route           / / zi lu by
           middleware   []MiddlewareHandle // Mounted middleware
           prefix       string             // Route prefix. This prefix is valid only for sub-routes
       }
    
    Copy the code

    The targetMethod is the handle method originally mounted in the prefix tree. We need to change all handle methods mounted in the Node structure of the original tree.go file to Route.

    Major changes, and there is nothing to pay special attention to, I will not repeat here, can see the tree. Go file

  2. In the README, the Route registration operation uses the chain of responsibility mode. Each method returns a pointer to the current object, and the chain operation can be implemented by methods such as Get and Post

  3. Implements the routing group function

    Through routing group, we can set common prefixes and middleware for sub-routes. In Laravel, multiple routes form a group object, but here, I directly use the way of sub-routing to change the group object into a common route, and the route under the group object is the sub-route of the current route

    Write a Mount() method to make way for adding child routes:

      // Mount the child route, where only the route in the callback is placed
      func (r *Route) Mount(rr func(b *Builder)) *Route {
      	builder := new(Builder)
      	rr(builder)
      	// Iterate over all child routes created under this route and place the route on the parent route
      	for _, route := range builder.routes {
      		r.mount = append(r.mount, route)
      	}
      	return r
      }
    Copy the code

    The Builder contains an array of routes. In Builder mode, give the Builder a NewRoute method, and make each route created by this method under the Routes attribute of Builder:

      func (b *Builder) NewRoute() *Route {
      	r := NewRoute()
      	b.routes = append(b.routes, r)
      	return r
      }
    Copy the code

    Just drop the pointer into the Builder at creation time

    Httprouter’s Handle method can be used to inject the Route object into the Router.

  4. Inject the route into the router

    Httprouter: Get,Post, or any other method ends up calling router.handle (), passing in an access method, a path, and the corresponding method, which we’ve just changed to a route

    So here we pass in the access method, path, and route object, and let middleware and route prefixes take effect at injection time

    Write an injection method Mount:

Go var prefix []string // The prefix of the current route increases with each layer. Var middlewares map[string][]MiddlewareHandle Func (r *Router) Mount(routes... *Route) { prefix = []string{} middlewares = make(map[string][]MiddlewareHandle) for _, Func (r *Router) MountRoute(route * route) {// MountRoute(route * route) {// SetMiddlewares (currentPointer, route) // The current path is all prefix arrays joined together, P := getPrefix(currentPointer) + route.path Prefix = append(prefix, route.prefix) if route.method! = "" && p ! {r.handler (route.method, p, route) {r.handler (route.method, p, route)} If len(route.mount) > 0 {for _, subRoute := range route.mount {currentPointer += 1 R.ountroute (subRoute)}} else {if currentPointer > 0 {currentPointer -= 1 Func getPrefix(current int) string {if len(prefix) > current-1 && len(prefix)! = 0 {return strings.Join(prefix[:current], "")} return ""} Func setMiddlewares(current int, route * route) {key := "p" + strconv.Itoa(currentPointer) for _, V := range route.middleware {middlewares[key] = append(middlewares[key], v)} for I := 0; i < currentPointer; i++ { key = "p" + strconv.Itoa(i) if list, ok := middlewares[key]; ok { for _, v := range list { route.middleware = append(route.middleware, v) } } } } ```Copy the code

Define global variables first:

  • Prefix Records the route prefix of each layer. The key is the route layer number, and the value is the route prefix

  • Middlewares records each layer of routing middleware. The key identifies the number of routing layers, and the value is the entire set of middleware in that layer

  • CurrentPointer identifies the current routing layer and uses it to fetch data belonging to the current routing layer from the two variables above

Then, after each iteration, the corresponding prefix and middleware group are stored in global variables, recursively called, and then appropriate data is extracted. Finally, Handle method is executed to inject into the router

The above is just a brief introduction to how to make, specific can directly see the code, there is no difficulty.

2. Part II

We’re building servers that implement the ServeHttp method so that when a request comes in, it’s going to go to the method we’ve defined. The ServeHttp defined by the original HttprOuter can be seen here

The process is to take the current URL, find the leaf along the prefix tree, and execute it directly. We changed the leaf to the Route structure above, so that when it is found, it needs to execute its middleware first, and then its targetMethod method

In the middleware, we can’t just use a for loop to iterate through the execution because we can’t intercept the request, and we end up in targetMethod with no post-effect. How do we do that?

Laravel uses a method called Pipeline, or Pipeline, to pass each context sequentially through each middleware, without passing it down if it is intercepted

The specific idea can be seen here

The source code for my implementation is here

The following code is used:

Here’s what we expect:

      	// Build the pipe and execute the middleware to finally reach the route
      	new(Pipeline).Send(context).Through(route.middleware).Then(func(context *Context) {
      	    route.targetMethod(context)
      	})
Copy the code

First establish a pipeline structure:

    typeStruct {send *Context} struct {send *Context} struct {send *Context through []MiddlewareHandleCopy the code

The Send() and Through() methods inject content into them, but I won’t go into that here

The main Then methods are:

Func (p *Pipeline) Then(thenFunc (context * context)) {// execute in order // willthenVar m MiddlewareHandle m = func(c *Context, next func(c *Context)) {then(c)
    		next(c)
    	}
    	p.through = append(p.through, m)
    	p.Exec()
    }

Copy the code

The then method wraps the final execution of the method as middleware, adding it to the end of the pipe, and then executes the Exec method to start sending objects through the pipe from scratch:


  func (p *Pipeline) Exec(a) {
      if len(p.through) > p.current {
          m := p.through[p.current]
          p.current += 1
          m(p.send, func(c *Context) {
              p.Exec()
          })
      }

  }
Copy the code

Take the middleware to which the current pointer is pointing, move the current pointer to the next middleware, and execute the middleware you just fetched, and the callback passed in next, which recursively executes this logic, executes the next middleware,

This allows us to control whether the next() method is pre-positioned or post-positioned in our code

It’s not much code, but the implementation is interesting, thanks to Laravel

I just rewrote part of other people’s things, thanks to open source, benefit a lot, and hang up your own Web framework Bingo, beg star, welcome PR!