πŸ“¬Routing

First impression from the framework. We're going to do it fine. Do we?

Router supports registering endpoints via shortcuts (methods, named in respect to HTTP methods). However, registering via router.Route() works fine, too

Talking about routers, almost always specifically inbuilt is meant. simple and virtual routers aren't widely covered here

List of methods cannot be extended and is in fact hardcoded

Dynamic routing

r.Get("/user/{id}", getUser)
r.Post("/user/{id}", createUser)
r.Put("/user/{id}",  updateUser)
r.Delete("/user/{id}", deleteUser)

Dynamic path consists of wildcards, covered by figure braces, unlike some other frameworks may do. Each wildcard must hold the whole β€œsegment” β€” from slash to slash. Therefore, the following route wouldn't be valid and would cause a panic:

r.Get("/user/id{id}", getUser)

Using wildcard values

// r.Get("/user/{id}", getUser)
func getUser(req *http.Request) *http.Response {
    // request.Params holds all the wildcards.
    // It shares an identical data structure under 
    // the hood, as request headers do.
    id := req.Params.Value("id") 
    userModel := getUserByID(id) // some ephemeral function
    
    return http.JSON(req, userModel)
}

Almost every key-value storage is presented by keyvalue.Storage object. So there will be many objects holding diffirent kinds of values, yet most of them are just typealiases to the common structure

Middlewares

Middlewares can be applied either globally or locally. Globally-applied middleware is group-wide, therefore it'll be applied on each handler defined inside the group (and on handlers of all the subgroups). Locally-applied are middlewares, which are applied on specific handlers exclusively.

All middlewares are called in strict order of their registration (call chain can be represented as a FIFO queue). Globally-applied middlewares are called strictly before locally-applied ones.

Global middleware can be defined using Use() method:

// import "github.com/indigo-web/indigo/router/inbuilt/middleware"
r.Use(middleware.Recover)

r.Use() is variadic. So by that, it may take multiple middlewares at once:

r.Use(middleware.Recover, middleware.HTTPSOnly)

Meanwhile, locally-applied can be added in the following manner:

r.Get("/", myHandler, middleware.Recover, middleware.HTTPSOnly)

Groups

Groups are basically sub-routers. Each group is a dedicated router instance, that implicitly registers itself in the parent router. As an application starts, all the groups will be merged into one big router, but this is well-hidden from a user.

All the route paths are concatenated with the group's name. Nested groups are holding parent's prefix plus their own.

r.Group(prefix string)

Prefix may be empty. This may be handy to explicitly define scopes for middlewares

Each group inherits all middlewares of its parent, but not vice-versa

Resources

Resources allows you to easily set multiple methods on the same location.

r.Resource("/").
    Get(indexGet).
    Post(indexPost).
    Patch(indexPatch)

Resources are supporting everything groups do. This includes:

  • Static content distribution

  • Middlewares (resource-wide and locally-applied)

  • Path catchers

Static content distribution

Static content distribution maps static directory contents to requests' paths and uploads files automatically. MIME types are derived automatically, too, however it may be missing heuristics. Unlike net/http, MIME isn't guessed via first bytes of data.

r.Static("/static", "./static_dir")

The first argument corresponds to the prefix, by which static files are accessible. The second corresponds to the directory to the static files, respectively. Subdirectories are working, too.

Implicit redirection

Implicit redirection replaces the request's path before the actual routing starts. This may be useful, for example, in order to redirect /favicon.ico to /static/favicon.ico

r.Alias("/favicon.ico", "/static/favicon.ico")

If implicit redirection happened, the original path can be obtained from request's environment:

if request.Env.AliasFrom != "" {
    // the request appears to be implicitly redirected.
    // The originally requested resource is contained in
    // the environment value
}

Aliases are implemented via mutator.Alias, which lays at github.com/indigo-web/indigo/router/inbuilt/mutator. It can be used directly: the only reason for this method to exist, is to provide a shortcut and backward capability, as it was introduced much earlier than mutators are

Routing errors

Custom error handlers can be set via the following method:

r.RouteError(ordinaryHandler, status.NotFound, status.MethodNotAllowed)

Here, we set the ordinaryHandler to handle all the status.NotFound and status.MethodNotAllowed errors. By default, a response with only a code is returned, letting a user-agent to draw an error-page manually. By setting custom error handler, you can return custom error pages. In order to set the handler handle ALL the errors, just make it process inbuilt.AllErrors error:

r.RouteError(ordinaryHandler, status.AllErrors)

Original error object is stored in request.Env.Error:

return http.Error(request, request.Env.Error)

According to HTTP RFC, each response with 405 Method Not Allowed code must contain the header Allow with an enumeration of available methods. For this purposes, the environment also stores this value just in case:

return http.Error(req, status.MethodNotAllowed).
    Header("Allow", req.Env.AllowedMethods)

Path catchers

Path catchers are ordinary handlers, which are called if no other handler was picked.

r.Catch("/static/", staticHandler, middlewares...)

In this example, all requests with path starting with /static/ will be passed into the staticHandler. Trailing slash may be avoided, but then paths like /staticky will also satisfy the condition.

Last updated