Ozzo-routing: an Extremely Fast Go (golang) HTTP Router

ozzo-routing

You may consider using go-rest-api to jumpstart your new RESTful applications with ozzo-routing.

Description

ozzo-routing is a Go package that provides high performance and powerful HTTP routing capabilities for Web applications. It has the following features:

  • middleware pipeline architecture, similar to that of the Express framework.
  • extremely fast request routing with zero dynamic memory allocation (the performance is comparable to that of httprouter and gin, see the performance comparison below)
  • modular code organization through route grouping
  • flexible URL path matching, supporting URL parameters and regular expressions
  • URL creation according to the predefined routes
  • compatible with http.Handler and http.HandlerFunc
  • ready-to-use handlers sufficient for building RESTful APIs
  • graceful shutdown

If you are using fasthttp, you may use a similar routing package fasthttp-routing which is adapted from ozzo-routing.

Requirements

Go 1.13 or above.

Installation

In your Go project using go mod, run the following command to install the package:

go get github.com/go-ozzo/ozzo-routing/v2

Getting Started

For a complete RESTful application boilerplate based on ozzo-routing, please refer to the golang-restful-starter-kit. Below we describe how to create a simple REST API using ozzo-routing.

Create a server.go file with the following content:

package main

import (
    "log"
    "net/http"
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/access"
    "github.com/go-ozzo/ozzo-routing/v2/slash"
    "github.com/go-ozzo/ozzo-routing/v2/content"
    "github.com/go-ozzo/ozzo-routing/v2/fault"
    "github.com/go-ozzo/ozzo-routing/v2/file"
)

func main() {
    router := routing.New()

    router.Use(
        // all these handlers are shared by every route
        access.Logger(log.Printf),
        slash.Remover(http.StatusMovedPermanently),
        fault.Recovery(log.Printf),
    )

    // serve RESTful APIs
    api := router.Group("/api")
    api.Use(
        // these handlers are shared by the routes in the api group only
        content.TypeNegotiator(content.JSON, content.XML),
    )
    api.Get("/users", func(c *routing.Context) error {
        return c.Write("user list")
    })
    api.Post("/users", func(c *routing.Context) error {
        return c.Write("create a new user")
    })
    api.Put(`/users/<id:\d+>`, func(c *routing.Context) error {
        return c.Write("update user " + c.Param("id"))
    })

    // serve index file
    router.Get("/", file.Content("ui/index.html"))
    // serve files under the "ui" subdirectory
    router.Get("/*", file.Server(file.PathMap{
        "/": "/ui/",
    }))

    http.Handle("/", router)
    http.ListenAndServe(":8080", nil)
}

Create an HTML file ui/index.html with any content.

Now run the following command to start the Web server:

go run server.go

You should be able to access URLs such as http://localhost:8080, http://localhost:8080/api/users.

Routes

ozzo-routing works by building a routing table in a router and then dispatching HTTP requests to the matching handlers found in the routing table. An intuitive illustration of a routing table is as follows:

RoutesHandlers
GET /usersm1, m2, h1, ...
POST /usersm1, m2, h2, ...
PUT /users/<id>m1, m2, h3, ...
DELETE /users/<id>m1, m2, h4, ...

For an incoming request GET /users, the first route would match and the handlers m1, m2, and h1 would be executed. If the request is PUT /users/123, the third route would match and the corresponding handlers would be executed. Note that the token <id> can match any number of non-slash characters and the matching part can be accessed as a path parameter value in the handlers.

If an incoming request matches multiple routes in the table, the route added first to the table will take precedence. All other matching routes will be ignored.

The actual implementation of the routing table uses a variant of the radix tree data structure, which makes the routing process as fast as working with a hash table, thanks to the inspiration from httprouter.

To add a new route and its handlers to the routing table, call the To method like the following:

router := routing.New()
router.To("GET", "/users", m1, m2, h1)
router.To("POST", "/users", m1, m2, h2)

You can also use shortcut methods, such as Get, Post, Put, etc., which are named after the HTTP method names:

router.Get("/users", m1, m2, h1)
router.Post("/users", m1, m2, h2)

If you have multiple routes with the same URL path but different HTTP methods, like the above example, you can chain them together as follows,

router.Get("/users", m1, m2, h1).Post(m1, m2, h2)

If you want to use the same set of handlers to handle the same URL path but different HTTP methods, you can take the following shortcut:

router.To("GET,POST", "/users", m1, m2, h)

A route may contain parameter tokens which are in the format of <name:pattern>, where name stands for the parameter name, and pattern is a regular expression which the parameter value should match. A token <name> is equivalent to <name:[^/]*>, i.e., it matches any number of non-slash characters. At the end of a route, an asterisk character can be used to match any number of arbitrary characters. Below are some examples:

  • /users/<username>: matches /users/admin
  • /users/accnt-<id:\d+>: matches /users/accnt-123, but not /users/accnt-admin
  • /users/<username>/*: matches /users/admin/profile/address

When a URL path matches a route, the matching parameters on the URL path can be accessed via Context.Param():

router := routing.New()

router.Get("/users/<username>", func (c *routing.Context) error {
    fmt.Fprintf(c.Response, "Name: %v", c.Param("username"))
    return nil
})

Route Groups

Route group is a way of grouping together the routes which have the same route prefix. The routes in a group also share the same handlers that are registered with the group via its Use method. For example,

router := routing.New()
api := router.Group("/api")
api.Use(m1, m2)
api.Get("/users", h1).Post(h2)
api.Put("/users/<id>", h3).Delete(h4)

The above /api route group establishes the following routing table:

RoutesHandlers
GET /api/usersm1, m2, h1, ...
POST /api/usersm1, m2, h2, ...
PUT /api/users/<id>m1, m2, h3, ...
DELETE /api/users/<id>m1, m2, h4, ...

As you can see, all these routes have the same route prefix /api and the handlers m1 and m2. In other similar routing frameworks, the handlers registered with a route group are also called middlewares.

Route groups can be nested. That is, a route group can create a child group by calling the Group() method. The router serves as the top level route group. A child group inherits the handlers registered with its parent group. For example,

router := routing.New()
router.Use(m1)

api := router.Group("/api")
api.Use(m2)

users := api.Group("/users")
users.Use(m3)
users.Put("/<id>", h1)

Because the router serves as the parent of the api group which is the parent of the users group, the PUT /api/users/<id> route is associated with the handlers m1, m2, m3, and h1.

Router

Router manages the routing table and dispatches incoming requests to appropriate handlers. A router instance is created by calling the routing.New() method.

Because Router implements the http.Handler interface, it can be readily used to serve subtrees on existing Go servers. For example,

router := routing.New()
http.Handle("/", router)
http.ListenAndServe(":8080", nil)

Handlers

A handler is a function with the signature func(*routing.Context) error. A handler is executed by the router if the incoming request URL path matches the route that the handler is associated with. Through the routing.Context parameter, you can access the request information in handlers.

A route may be associated with multiple handlers. These handlers will be executed in the order that they are registered to the route. The execution sequence can be terminated in the middle using one of the following two methods:

  • A handler returns an error: the router will skip the rest of the handlers and handle the returned error.
  • A handler calls Context.Abort(): the router will simply skip the rest of the handlers. There is no error to be handled.

A handler can call Context.Next() to explicitly execute the rest of the unexecuted handlers and take actions after they finish execution. For example, a response compression handler may start the output buffer, call Context.Next(), and then compress and send the output to response.

Context

For each incoming request, a routing.Context object is populated with the request information and passed through the handlers that need to handle the request. Handlers can get the request information via Context.Request and send a response back via Context.Response. The Context.Param() method allows handlers to access the URL path parameters that match the current route.

Using Context.Get() and Context.Set(), handlers can share data between each other. For example, an authentication handler can store the authenticated user identity by calling Context.Set(), and other handlers can retrieve back the identity information by calling Context.Get().

Reading Request Data

Context provides a few shortcut methods to read query parameters. The Context.Query() method returns the named URL query parameter value; the Context.PostForm() method returns the named parameter value in the POST or PUT body parameters; and the Context.Form() method returns the value from either POST/PUT or URL query parameters.

The Context.Read() method supports reading data from the request body and populating it into an object. The method will check the Content-Type HTTP header and parse the body data as the corresponding format. For example, if Content-Type is application/json, the request body will be parsed as JSON data. The public fields in the object being populated will receive the parsed data if the data contains the same named fields. For example,

func foo(c *routing.Context) error {
    data := &struct{
        A string
        B bool
    }{}

    // assume the body data is: {"A":"abc", "B":true}
    // data will be populated as: {A: "abc", B: true}
    if err := c.Read(&data); err != nil {
        return err
    }
}

By default, Context supports reading data that are in JSON, XML, form, and multipart-form data. You may modify routing.DataReaders to add support for other data formats.

Note that when the data is read as form data, you may use struct tag named form to customize the name of the corresponding field in the form data. The form data reader also supports populating data into embedded objects which are either named or anonymous.

Writing Response Data

The Context.Write() method can be used to write data of arbitrary type to the response. By default, if the data being written is neither a string nor a byte array, the method will will call fmt.Fprint() to write the data into the response.

You can call Context.SetWriter() to replace the default data writer with a customized one. For example, the content.TypeNegotiator will negotiate the content response type and set the data writer with an appropriate one.

Error Handling

A handler may return an error indicating some erroneous condition. Sometimes, a handler or the code it calls may cause a panic. Both should be handled properly to ensure best user experience. It is recommended that you use the fault.Recover handler or a similar error handler to handle these errors.

If an error is not handled by any handler, the router will handle it by calling its handleError() method which simply sets an appropriate HTTP status code and writes the error message to the response.

When an incoming request has no matching route, the router will call the handlers registered via the Router.NotFound() method. All the handlers registered via Router.Use() will also be called in advance. By default, the following two handlers are registered with Router.NotFound():

  • routing.MethodNotAllowedHandler: a handler that sends an Allow HTTP header indicating the allowed HTTP methods for a requested URL
  • routing.NotFoundHandler: a handler triggering 404 HTTP error

Serving Static Files

Static files can be served with the help of file.Server and file.Content handlers. The former serves files under the specified directories, while the latter serves the content of a single file. For example,

import (
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/file"
)

router := routing.NewRouter()

// serve index file
router.Get("/", file.Content("ui/index.html"))
// serve files under the "ui" subdirectory
router.Get("/*", file.Server(file.PathMap{
    "/": "/ui/",
}))

Handlers

ozzo-routing comes with a few commonly used handlers in its subpackages:

Handler nameDescription
access.Loggerrecords an entry for every incoming request
auth.Basicprovides authentication via HTTP Basic
auth.Bearerprovides authentication via HTTP Bearer
auth.Queryprovides authentication via token-based query parameter
auth.JWTprovides JWT-based authentication
content.TypeNegotiatorsupports content negotiation by response types
content.LanguageNegotiatorsupports content negotiation by accepted languages
cors.Handlerimplements the CORS (Cross Origin Resource Sharing) specification from the W3C
fault.Recoveryrecovers from panics and handles errors returned by handlers
fault.PanicHandlerrecovers from panics happened in the handlers
fault.ErrorHandlerhandles errors returned by handlers by writing them in an appropriate format to the response
file.Serverserves the files under the specified folder as response content
file.Contentserves the content of the specified file as the response
slash.Removerremoves the trailing slashes from the request URL and redirects to the proper URL

The following code shows how these handlers may be used:

import (
    "log"
    "net/http"
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/access"
    "github.com/go-ozzo/ozzo-routing/v2/slash"
    "github.com/go-ozzo/ozzo-routing/v2/fault"
)

router := routing.New()

router.Use(
    access.Logger(log.Printf),
    slash.Remover(http.StatusMovedPermanently),
    fault.Recovery(log.Printf),
)

...

Third-party Handlers

The following third-party handlers are specifically designed for ozzo-routing:

Handler nameDescription
jwt.JWTsupports JWT Authorization

ozzo-routing also provides adapters to support using third-party http.HandlerFunc or http.Handler handlers. For example,

router := routing.New()

// using http.HandlerFunc
router.Use(routing.HTTPHandlerFunc(http.NotFound))

// using http.Handler
router.Use(routing.HTTPHandler(http.NotFoundHandler))

3rd-Party Extensions and Code Examples

Benchmarks

Last updated on Jan 6, 2017

Ozzo-routing is very fast, thanks to the radix tree data structure and the usage of sync.Pool (the idea was originally from HttpRouter and Gin). The following table (by running go-http-routing-benchmark) shows how ozzo-routing compares with Gin, HttpRouter, and Martini in performance.

BenchmarkOzzo_GithubAll                    50000             37989 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_GithubAll                    20000             91003 ns/op            6496 B/op        203 allocs/op
BenchmarkGin_GithubAll                     50000             26717 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_GithubAll              50000             36052 ns/op           13792 B/op        167 allocs/op
BenchmarkMartini_GithubAll                   300           4162283 ns/op          228216 B/op       2483 allocs/op

BenchmarkOzzo_GPlusAll                   1000000              1732 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_GPlusAll                    300000              4523 ns/op             416 B/op         13 allocs/op
BenchmarkGin_GPlusAll                    1000000              1171 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_GPlusAll             1000000              1533 ns/op             640 B/op         11 allocs/op
BenchmarkMartini_GPlusAll                  20000             75634 ns/op           14448 B/op        165 allocs/op

BenchmarkOzzo_ParseAll                    500000              3318 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_ParseAll                    200000              7336 ns/op             832 B/op         26 allocs/op
BenchmarkGin_ParseAll                    1000000              2075 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_ParseAll             1000000              2034 ns/op             640 B/op         16 allocs/op
BenchmarkMartini_ParseAll                  10000            122002 ns/op           25600 B/op        276 allocs/op

Credits

ozzo-routing has referenced many popular routing frameworks, including Express, Martini, httprouter, and gin.

Author: Go-ozzo
Source Code: https://github.com/go-ozzo/ozzo-routing 
License: MIT license

#go #golang #http #restful 

What is GEEK

Buddha Community

Ozzo-routing: an Extremely Fast Go (golang) HTTP Router

Ozzo-routing: an Extremely Fast Go (golang) HTTP Router

ozzo-routing

You may consider using go-rest-api to jumpstart your new RESTful applications with ozzo-routing.

Description

ozzo-routing is a Go package that provides high performance and powerful HTTP routing capabilities for Web applications. It has the following features:

  • middleware pipeline architecture, similar to that of the Express framework.
  • extremely fast request routing with zero dynamic memory allocation (the performance is comparable to that of httprouter and gin, see the performance comparison below)
  • modular code organization through route grouping
  • flexible URL path matching, supporting URL parameters and regular expressions
  • URL creation according to the predefined routes
  • compatible with http.Handler and http.HandlerFunc
  • ready-to-use handlers sufficient for building RESTful APIs
  • graceful shutdown

If you are using fasthttp, you may use a similar routing package fasthttp-routing which is adapted from ozzo-routing.

Requirements

Go 1.13 or above.

Installation

In your Go project using go mod, run the following command to install the package:

go get github.com/go-ozzo/ozzo-routing/v2

Getting Started

For a complete RESTful application boilerplate based on ozzo-routing, please refer to the golang-restful-starter-kit. Below we describe how to create a simple REST API using ozzo-routing.

Create a server.go file with the following content:

package main

import (
    "log"
    "net/http"
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/access"
    "github.com/go-ozzo/ozzo-routing/v2/slash"
    "github.com/go-ozzo/ozzo-routing/v2/content"
    "github.com/go-ozzo/ozzo-routing/v2/fault"
    "github.com/go-ozzo/ozzo-routing/v2/file"
)

func main() {
    router := routing.New()

    router.Use(
        // all these handlers are shared by every route
        access.Logger(log.Printf),
        slash.Remover(http.StatusMovedPermanently),
        fault.Recovery(log.Printf),
    )

    // serve RESTful APIs
    api := router.Group("/api")
    api.Use(
        // these handlers are shared by the routes in the api group only
        content.TypeNegotiator(content.JSON, content.XML),
    )
    api.Get("/users", func(c *routing.Context) error {
        return c.Write("user list")
    })
    api.Post("/users", func(c *routing.Context) error {
        return c.Write("create a new user")
    })
    api.Put(`/users/<id:\d+>`, func(c *routing.Context) error {
        return c.Write("update user " + c.Param("id"))
    })

    // serve index file
    router.Get("/", file.Content("ui/index.html"))
    // serve files under the "ui" subdirectory
    router.Get("/*", file.Server(file.PathMap{
        "/": "/ui/",
    }))

    http.Handle("/", router)
    http.ListenAndServe(":8080", nil)
}

Create an HTML file ui/index.html with any content.

Now run the following command to start the Web server:

go run server.go

You should be able to access URLs such as http://localhost:8080, http://localhost:8080/api/users.

Routes

ozzo-routing works by building a routing table in a router and then dispatching HTTP requests to the matching handlers found in the routing table. An intuitive illustration of a routing table is as follows:

RoutesHandlers
GET /usersm1, m2, h1, ...
POST /usersm1, m2, h2, ...
PUT /users/<id>m1, m2, h3, ...
DELETE /users/<id>m1, m2, h4, ...

For an incoming request GET /users, the first route would match and the handlers m1, m2, and h1 would be executed. If the request is PUT /users/123, the third route would match and the corresponding handlers would be executed. Note that the token <id> can match any number of non-slash characters and the matching part can be accessed as a path parameter value in the handlers.

If an incoming request matches multiple routes in the table, the route added first to the table will take precedence. All other matching routes will be ignored.

The actual implementation of the routing table uses a variant of the radix tree data structure, which makes the routing process as fast as working with a hash table, thanks to the inspiration from httprouter.

To add a new route and its handlers to the routing table, call the To method like the following:

router := routing.New()
router.To("GET", "/users", m1, m2, h1)
router.To("POST", "/users", m1, m2, h2)

You can also use shortcut methods, such as Get, Post, Put, etc., which are named after the HTTP method names:

router.Get("/users", m1, m2, h1)
router.Post("/users", m1, m2, h2)

If you have multiple routes with the same URL path but different HTTP methods, like the above example, you can chain them together as follows,

router.Get("/users", m1, m2, h1).Post(m1, m2, h2)

If you want to use the same set of handlers to handle the same URL path but different HTTP methods, you can take the following shortcut:

router.To("GET,POST", "/users", m1, m2, h)

A route may contain parameter tokens which are in the format of <name:pattern>, where name stands for the parameter name, and pattern is a regular expression which the parameter value should match. A token <name> is equivalent to <name:[^/]*>, i.e., it matches any number of non-slash characters. At the end of a route, an asterisk character can be used to match any number of arbitrary characters. Below are some examples:

  • /users/<username>: matches /users/admin
  • /users/accnt-<id:\d+>: matches /users/accnt-123, but not /users/accnt-admin
  • /users/<username>/*: matches /users/admin/profile/address

When a URL path matches a route, the matching parameters on the URL path can be accessed via Context.Param():

router := routing.New()

router.Get("/users/<username>", func (c *routing.Context) error {
    fmt.Fprintf(c.Response, "Name: %v", c.Param("username"))
    return nil
})

Route Groups

Route group is a way of grouping together the routes which have the same route prefix. The routes in a group also share the same handlers that are registered with the group via its Use method. For example,

router := routing.New()
api := router.Group("/api")
api.Use(m1, m2)
api.Get("/users", h1).Post(h2)
api.Put("/users/<id>", h3).Delete(h4)

The above /api route group establishes the following routing table:

RoutesHandlers
GET /api/usersm1, m2, h1, ...
POST /api/usersm1, m2, h2, ...
PUT /api/users/<id>m1, m2, h3, ...
DELETE /api/users/<id>m1, m2, h4, ...

As you can see, all these routes have the same route prefix /api and the handlers m1 and m2. In other similar routing frameworks, the handlers registered with a route group are also called middlewares.

Route groups can be nested. That is, a route group can create a child group by calling the Group() method. The router serves as the top level route group. A child group inherits the handlers registered with its parent group. For example,

router := routing.New()
router.Use(m1)

api := router.Group("/api")
api.Use(m2)

users := api.Group("/users")
users.Use(m3)
users.Put("/<id>", h1)

Because the router serves as the parent of the api group which is the parent of the users group, the PUT /api/users/<id> route is associated with the handlers m1, m2, m3, and h1.

Router

Router manages the routing table and dispatches incoming requests to appropriate handlers. A router instance is created by calling the routing.New() method.

Because Router implements the http.Handler interface, it can be readily used to serve subtrees on existing Go servers. For example,

router := routing.New()
http.Handle("/", router)
http.ListenAndServe(":8080", nil)

Handlers

A handler is a function with the signature func(*routing.Context) error. A handler is executed by the router if the incoming request URL path matches the route that the handler is associated with. Through the routing.Context parameter, you can access the request information in handlers.

A route may be associated with multiple handlers. These handlers will be executed in the order that they are registered to the route. The execution sequence can be terminated in the middle using one of the following two methods:

  • A handler returns an error: the router will skip the rest of the handlers and handle the returned error.
  • A handler calls Context.Abort(): the router will simply skip the rest of the handlers. There is no error to be handled.

A handler can call Context.Next() to explicitly execute the rest of the unexecuted handlers and take actions after they finish execution. For example, a response compression handler may start the output buffer, call Context.Next(), and then compress and send the output to response.

Context

For each incoming request, a routing.Context object is populated with the request information and passed through the handlers that need to handle the request. Handlers can get the request information via Context.Request and send a response back via Context.Response. The Context.Param() method allows handlers to access the URL path parameters that match the current route.

Using Context.Get() and Context.Set(), handlers can share data between each other. For example, an authentication handler can store the authenticated user identity by calling Context.Set(), and other handlers can retrieve back the identity information by calling Context.Get().

Reading Request Data

Context provides a few shortcut methods to read query parameters. The Context.Query() method returns the named URL query parameter value; the Context.PostForm() method returns the named parameter value in the POST or PUT body parameters; and the Context.Form() method returns the value from either POST/PUT or URL query parameters.

The Context.Read() method supports reading data from the request body and populating it into an object. The method will check the Content-Type HTTP header and parse the body data as the corresponding format. For example, if Content-Type is application/json, the request body will be parsed as JSON data. The public fields in the object being populated will receive the parsed data if the data contains the same named fields. For example,

func foo(c *routing.Context) error {
    data := &struct{
        A string
        B bool
    }{}

    // assume the body data is: {"A":"abc", "B":true}
    // data will be populated as: {A: "abc", B: true}
    if err := c.Read(&data); err != nil {
        return err
    }
}

By default, Context supports reading data that are in JSON, XML, form, and multipart-form data. You may modify routing.DataReaders to add support for other data formats.

Note that when the data is read as form data, you may use struct tag named form to customize the name of the corresponding field in the form data. The form data reader also supports populating data into embedded objects which are either named or anonymous.

Writing Response Data

The Context.Write() method can be used to write data of arbitrary type to the response. By default, if the data being written is neither a string nor a byte array, the method will will call fmt.Fprint() to write the data into the response.

You can call Context.SetWriter() to replace the default data writer with a customized one. For example, the content.TypeNegotiator will negotiate the content response type and set the data writer with an appropriate one.

Error Handling

A handler may return an error indicating some erroneous condition. Sometimes, a handler or the code it calls may cause a panic. Both should be handled properly to ensure best user experience. It is recommended that you use the fault.Recover handler or a similar error handler to handle these errors.

If an error is not handled by any handler, the router will handle it by calling its handleError() method which simply sets an appropriate HTTP status code and writes the error message to the response.

When an incoming request has no matching route, the router will call the handlers registered via the Router.NotFound() method. All the handlers registered via Router.Use() will also be called in advance. By default, the following two handlers are registered with Router.NotFound():

  • routing.MethodNotAllowedHandler: a handler that sends an Allow HTTP header indicating the allowed HTTP methods for a requested URL
  • routing.NotFoundHandler: a handler triggering 404 HTTP error

Serving Static Files

Static files can be served with the help of file.Server and file.Content handlers. The former serves files under the specified directories, while the latter serves the content of a single file. For example,

import (
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/file"
)

router := routing.NewRouter()

// serve index file
router.Get("/", file.Content("ui/index.html"))
// serve files under the "ui" subdirectory
router.Get("/*", file.Server(file.PathMap{
    "/": "/ui/",
}))

Handlers

ozzo-routing comes with a few commonly used handlers in its subpackages:

Handler nameDescription
access.Loggerrecords an entry for every incoming request
auth.Basicprovides authentication via HTTP Basic
auth.Bearerprovides authentication via HTTP Bearer
auth.Queryprovides authentication via token-based query parameter
auth.JWTprovides JWT-based authentication
content.TypeNegotiatorsupports content negotiation by response types
content.LanguageNegotiatorsupports content negotiation by accepted languages
cors.Handlerimplements the CORS (Cross Origin Resource Sharing) specification from the W3C
fault.Recoveryrecovers from panics and handles errors returned by handlers
fault.PanicHandlerrecovers from panics happened in the handlers
fault.ErrorHandlerhandles errors returned by handlers by writing them in an appropriate format to the response
file.Serverserves the files under the specified folder as response content
file.Contentserves the content of the specified file as the response
slash.Removerremoves the trailing slashes from the request URL and redirects to the proper URL

The following code shows how these handlers may be used:

import (
    "log"
    "net/http"
    "github.com/go-ozzo/ozzo-routing/v2"
    "github.com/go-ozzo/ozzo-routing/v2/access"
    "github.com/go-ozzo/ozzo-routing/v2/slash"
    "github.com/go-ozzo/ozzo-routing/v2/fault"
)

router := routing.New()

router.Use(
    access.Logger(log.Printf),
    slash.Remover(http.StatusMovedPermanently),
    fault.Recovery(log.Printf),
)

...

Third-party Handlers

The following third-party handlers are specifically designed for ozzo-routing:

Handler nameDescription
jwt.JWTsupports JWT Authorization

ozzo-routing also provides adapters to support using third-party http.HandlerFunc or http.Handler handlers. For example,

router := routing.New()

// using http.HandlerFunc
router.Use(routing.HTTPHandlerFunc(http.NotFound))

// using http.Handler
router.Use(routing.HTTPHandler(http.NotFoundHandler))

3rd-Party Extensions and Code Examples

Benchmarks

Last updated on Jan 6, 2017

Ozzo-routing is very fast, thanks to the radix tree data structure and the usage of sync.Pool (the idea was originally from HttpRouter and Gin). The following table (by running go-http-routing-benchmark) shows how ozzo-routing compares with Gin, HttpRouter, and Martini in performance.

BenchmarkOzzo_GithubAll                    50000             37989 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_GithubAll                    20000             91003 ns/op            6496 B/op        203 allocs/op
BenchmarkGin_GithubAll                     50000             26717 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_GithubAll              50000             36052 ns/op           13792 B/op        167 allocs/op
BenchmarkMartini_GithubAll                   300           4162283 ns/op          228216 B/op       2483 allocs/op

BenchmarkOzzo_GPlusAll                   1000000              1732 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_GPlusAll                    300000              4523 ns/op             416 B/op         13 allocs/op
BenchmarkGin_GPlusAll                    1000000              1171 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_GPlusAll             1000000              1533 ns/op             640 B/op         11 allocs/op
BenchmarkMartini_GPlusAll                  20000             75634 ns/op           14448 B/op        165 allocs/op

BenchmarkOzzo_ParseAll                    500000              3318 ns/op               0 B/op          0 allocs/op
BenchmarkEcho_ParseAll                    200000              7336 ns/op             832 B/op         26 allocs/op
BenchmarkGin_ParseAll                    1000000              2075 ns/op               0 B/op          0 allocs/op
BenchmarkHttpRouter_ParseAll             1000000              2034 ns/op             640 B/op         16 allocs/op
BenchmarkMartini_ParseAll                  10000            122002 ns/op           25600 B/op        276 allocs/op

Credits

ozzo-routing has referenced many popular routing frameworks, including Express, Martini, httprouter, and gin.

Author: Go-ozzo
Source Code: https://github.com/go-ozzo/ozzo-routing 
License: MIT license

#go #golang #http #restful 

Fannie  Zemlak

Fannie Zemlak

1599854400

What's new in the go 1.15

Go announced Go 1.15 version on 11 Aug 2020. Highlighted updates and features include Substantial improvements to the Go linker, Improved allocation for small objects at high core counts, X.509 CommonName deprecation, GOPROXY supports skipping proxies that return errors, New embedded tzdata package, Several Core Library improvements and more.

As Go promise for maintaining backward compatibility. After upgrading to the latest Go 1.15 version, almost all existing Golang applications or programs continue to compile and run as older Golang version.

#go #golang #go 1.15 #go features #go improvement #go package #go new features

Nigel  Uys

Nigel Uys

1654358400

Bxog: A Simple and Fast HTTP Router for Go (HTTP Request Multiplexer)

Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer).

Usage

An example of using the multiplexer:

package main

import (
    "io"
    "net/http"

    bx "github.com/claygod/Bxog"
)

// Handlers
func IHandler(w http.ResponseWriter, req *http.Request, r *bx.Router) {
    io.WriteString(w, "Welcome to Bxog!")
}
func THandler(w http.ResponseWriter, req *http.Request, r *bx.Router) {
    params := r.Params(req, "/abc/:par")
    io.WriteString(w, "Params:\n")
    io.WriteString(w, " 'par' -> "+params["par"]+"\n")
}
func PHandler(w http.ResponseWriter, req *http.Request, r *bx.Router) {
    // Getting parameters from URL
    params := r.Params(req, "country")
    io.WriteString(w, "Country:\n")
    io.WriteString(w, " 'name' -> "+params["name"]+"\n")
    io.WriteString(w, " 'capital' -> "+params["city"]+"\n")
    io.WriteString(w, " 'valuta' -> "+params["money"]+"\n")
    // Creating an URL string
    io.WriteString(w, "Creating an URL from the current route (This is an example of creating an another URL):\n")
    io.WriteString(w, r.Create("country", map[string]string{"name": "Russia", "city": "Moscow", "money": "rouble"}))
}

// Main
func main() {
    m := bx.New()
    m.Add("/", IHandler)
    m.Add("/abc/:par", THandler)
    m.Add("/country/:name/capital/:city/valuta/:money", PHandler).
        Id("country"). // For a convinience you can indicate a short ID
        Method("GET")  // It is not necessary to indicate the GET method here as the GET method is used by default but this way is used to set an allowed method
    m.Test()
    m.Start(":9999")
}

Click URLs:

Settings

Necessary changes in the configuration of the multiplexer can be made in the configuration file config.go

Perfomance

Bxog is the fastest router, showing the speed of query processing. Its speed is comparable to the speed of the popular multiplexers: Bone, Httprouter, Gorilla, Zeus. The test is done on a computer with a i3-6320 3.7GHz processor and 8 GB RAM. In short (less time, the better):

  • Bxog 163 ns/op
  • HttpRouter 183 ns/op
  • Zeus 12302 ns/op
  • GorillaMux 14928 ns/op
  • GorillaPat 618 ns/op
  • Bone 47333 ns/op

Detailed benchmark here

API

Methods:

  • New - create a new multiplexer
  • Add - add a rule specifying the handler (the default method - GET, ID - as a string to this rule)
  • Start - start the server indicating the listening port
  • Params - extract parameters from URL
  • Create - generate URL of the available options
  • Shutdown - graceful stop the server
  • Stop - aggressive stop the server
  • Test - Start analogue (for testing only)

Example: m := bxog.New() m.Add("/", IHandler)

Named parameters

Arguments in the rules designated route colon. Example route: /abc/:param , where abc is a static section and :param - the dynamic section(argument).

Static files

The directory path to the file and its nickname as part of URL specified in the configuration file. This constants FILE_PREF and FILE_PATH

Author: Claygod
Source Code: https://github.com/claygod/Bxog 
License: View license

#go #golang #http 

FastRouter: A Fast, Flexible HTTP Router Written in Go

FastRouter

FastRouter is a fast, flexible HTTP router written in Go.

FastRouter contains some customizable options, such as TrailingSlashesPolicy, PanicHandler, OptionsHandler, MethodNotAllowedHandler, NotFoundHandler and so on.

FastRouter also provides some useful features, such as grouping and middleware.   

Features

Fast: See Go Web Framework Benchmark

Flexible: FastRouter provides some customizable options for you:

  • TrailingSlashesPolicy:
    • IgnoreTrailingSlashes: ignore trailing slashes.
    • AppendTrailingSlashes: append trailing slashes and redirect if request path is not end with '/'.
    • RemoveTrailingSlashes: remove trailing slashes and redirect if request path is end with '/'.
    • StrictTrailingSlashes: remove or append trailing slashes according to corresponding pattern.
  • PanicHandler
  • OptionsHandler
  • MethodNotAllowedHandler
  • NotFoundHandler

Compatible: FastRouter is an implementation of http.Handler, so it is compatible with third-party packages.

Middleware: Middleware is a chaining tool for chaining http.Handler, see Middleware.

Grouping: Grouping is an useful feature of FastRouter, it allows to nest and specify middleware of group, see Grouping.

Documentation

See Documentation for details.

Examples

See Examples for details.

Author: Razonyang
Source Code: https://github.com/razonyang/fastrouter 
License: BSD-3-Clause license

#go #golang #http #middleware 

Lars: A Fast Radix-tree Based, Zero Allocation, HTTP Router for Go

LARS

    

LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples. If looking for a more pure Go solution, be sure to check out pure which is essentially a pure version of lars

Why Another HTTP Router?

Have you ever been painted into a corner by a framework, ya me too! and I've noticed that allot of routers out there, IMHO, are adding so much functionality that they are turning into Web Frameworks, (which is fine, frameworks are important) however, not at the expense of flexibility and configurability. So with no further ado, introducing LARS an HTTP router that can be your launching pad in creating a framework for your needs. How? Context is an interface see example here, where you can add as little or much as you want or need and most importantly...under your control.

Key & Unique Features

  •  Context is an interface - this allows passing of framework/globals/application specific variables. example
  •  Smart Route Logic - helpful logic to help prevent adding bad routes, keeping your url's consistent. i.e. /user/:id and /user/:user_id - the second one will fail to add letting you know that :user_id should be :id
  •  Uber simple middleware + handlers - middleware and handlers actually have the exact same definition!
  •  Custom Handlers - can register custom handlers for making other middleware + handler patterns usable with this router; the best part about this is can register one for your custom context and not have to do type casting everywhere see here
  •  Diverse handler support - Full support for standard/native http Handler + HandlerFunc + some others see here
    • When Parsing a form call Context's ParseForm amd ParseMulipartForm functions and the URL params will be added into the Form object, just like query parameters are, so no extra work
  •  Fast & Efficient - lars uses a custom version of httprouter so incredibly fast and efficient.

Installation

go get -u github.com/go-playground/lars

Usage

Below is a simple example, for a full example see here

package main

import (
    "fmt"
    "net/http"

    "github.com/go-playground/lars"
    mw "github.com/go-playground/lars/_examples/middleware/logging-recovery"
)

func main() {
    l := lars.New()
    // LoggingAndRecovery is just an example copy paste and modify to your needs
    l.Use(mw.LoggingAndRecovery)

    l.Get("/", HelloWorld)

    http.ListenAndServe(":3007", l.Serve())
}

// HelloWorld ...
func HelloWorld(c lars.Context) {
    c.Response().Write([]byte("Hello World"))

    // this will also work, Response() complies with http.ResponseWriter interface
    fmt.Fprint(c.Response(), "Hello World")
}

URL Params

l := l.New()

// the matching param will be stored in the Context's params with name "id"
l.Get("/user/:id", UserHandler)

// serve css, js etc.. c.Param(lars.WildcardParam) will return the remaining path if 
// you need to use it in a custom handler...
l.Get("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) 

...

Note: Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns /user/new and /user/:user for the same request method at the same time. The routing of different request methods is independent from each other. I was initially against this, and this router allowed it in a previous version, however it nearly cost me in a big app where the dynamic param value say :type actually could have matched another static route and that's just too dangerous, so it is no longer allowed.

Groups


l.Use(LoggingAndRecovery)
...
l.Post("/users/add", ...)

// creates a group for user + inherits all middleware registered using l.Use()
user := l.Group("/user/:userid")
user.Get("", ...)
user.Post("", ...)
user.Delete("/delete", ...)

contactInfo := user.Group("/contact-info/:ciid")
contactinfo.Delete("/delete", ...)

// creates a group for others + inherits all middleware registered using l.Use() + adds 
// OtherHandler to middleware
others := l.GroupWithMore("/others", OtherHandler)

// creates a group for admin WITH NO MIDDLEWARE... more can be added using admin.Use()
admin := l.GroupWithNone("/admin")
admin.Use(SomeAdminSecurityMiddleware)
...

Custom Context + Avoid Type Casting / Custom Handlers

...
// MyContext is a custom context
type MyContext struct {
    *lars.Ctx  // a little dash of Duck Typing....
}

// CustomContextFunction is a function that is specific to your applications needs that you added
func (mc *MyContext) CustomContextFunction() {
    // do something
}

// newContext is the function that creates your custom context +
// contains lars's default context
func newContext(l *lars.LARS) lars.Context {
    return &MyContext{
        Ctx:        lars.NewContext(l),
    }
}

// casts custom context and calls you custom handler so you don;t have to type cast lars.Context everywhere
func castCustomContext(c lars.Context, handler lars.Handler) {
    // could do it in all one statement, but in long form for readability
    h := handler.(func(*MyContext))
    ctx := c.(*MyContext)

    h(ctx)
}

func main() {
    l := lars.New()
    l.RegisterContext(newContext) // all gets cached in pools for you
    l.RegisterCustomHandler(func(*MyContext) {}, castCustomContext)
    l.Use(Logger)

    l.Get("/", Home)

    http.ListenAndServe(":3007", l.Serve())
}

// Home ...notice the receiver is *MyContext, castCustomContext handled the type casting for us
// quite the time saver if you ask me.
func Home(c *MyContext) {
    c.CustomContextFunction()
    ...
}

Decoding Body

For full example see here. currently JSON, XML, FORM + Multipart Form's are support out of the box.

    // first argument denotes yes or no I would like URL query parameter fields
    // to be included. i.e. 'id' in route '/user/:id' should it be included.
    // run, then change to false and you'll see user.ID is not populated.
    if err := c.Decode(true, maxBytes, &user); err != nil {
        log.Println(err)
    }

Misc

...
// can register multiple handlers, the last is considered the last in the chain and others 
// considered middleware, but just for this route and not added to middleware like l.Use() does.
l.Get(/"home", AdditionalHandler, HomeHandler)

// set custom 404 ( not Found ) handler
l.Register404(404Handler)

// Redirect to or from ending slash if route not found, default is true
l.SetRedirectTrailingSlash(true)

// Handle 405 ( Method Not allowed ), default is false
l.SetHandle405MethodNotAllowed(false)

// automatically handle OPTION requests; manually configured
// OPTION handlers take precedence. default true
l.SetAutomaticallyHandleOPTIONS(set bool)

// register custom context
l.RegisterContext(ContextFunc)

// Register custom handler type, see https://github.com/go-playground/lars/blob/master/util.go#L62
// for example handler creation
l.RegisterCustomHandler(interface{}, CustomHandlerFunc)

// NativeChainHandler is used as a helper to create your own custom handlers, or use custom handlers 
// that already exist an example usage can be found here 
// https://github.com/go-playground/lars/blob/master/util.go#L86, below is an example using nosurf CSRF middleware

l.Use(nosurf.NewPure(lars.NativeChainHandler))


// Context has 2 methods of which you should be aware of ParseForm and ParseMulipartForm, they just call the 
// default http functions but provide one more additional feature, they copy the URL params to the request 
// Forms variables, just like Query parameters would have been.
// The functions are for convenience and are totally optional.

Special Note

I don't know if it was an oversight or just an assumption about how middleware would be used with Go 1.7's new context integration into the *http.Request but there are a few quirks. As you know lars handles multiple handler types, including the native handler, this functionality is possible because of the way lar handles the middleware; lars does not chain the middleware in the normal way, but rather calles each in sequence; because of this all you have to do is call c.Next() or it has already been wrapped to do so for you transparently. OK getting back to the point, if you are not using lars.Context to set the context information you will have to set the request object so that the information gets back to the calling package. eg.

// because 'r' is a copy of a pointer to allow the information to get
// back to the caller, need to set the value of 'r' as below with '*r'
func(w http.ResponseWriter, r *http.Request) {
    *r = *r.WithContext(context.WithValue(r.Context(), 0, "testval1"))
}

this is not an issue specific to lars, but a quirk of the way context is tied to the http.Request object.

Middleware

There are some pre-defined middlewares within the middleware folder; NOTE: that the middleware inside will comply with the following rule(s):

  • Are completely reusable by the community without modification

Other middleware will be listed under the _examples/middleware/... folder for a quick copy/paste modify. as an example a logging or recovery middleware are very application dependent and therefore will be listed under the _examples/middleware/...

Benchmarks

Run on MacBook Pro (15-inch, 2017) 3.1 GHz Intel Core i7 16GB DDR3 using Go version go1.9.2 darwin/amd64

NOTICE: lars uses a custom version of httprouter, benchmarks can be found here

go test -bench=. -benchmem=true
#GithubAPI Routes: 203
   LARS: 49032 Bytes

#GPlusAPI Routes: 13
   LARS: 3640 Bytes

#ParseAPI Routes: 26
   LARS: 6632 Bytes

#Static Routes: 157
   LARS: 30120 Bytes

goos: darwin
goarch: amd64
pkg: github.com/joeybloggs/go-http-routing-benchmark
BenchmarkLARS_Param            20000000            51.6 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_Param5           20000000            85.7 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_Param20          10000000           215 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_ParamWrite       20000000            94.3 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GithubStatic     20000000            68.7 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GithubParam      20000000           103 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GithubAll          100000         21066 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GPlusStatic      30000000            53.1 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GPlusParam       20000000            70.3 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GPlus2Params     20000000            84.4 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_GPlusAll          2000000           894 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_ParseStatic      20000000            53.5 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_ParseParam       20000000            60.4 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_Parse2Params     20000000            68.7 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_ParseAll          1000000          1602 ns/op           0 B/op           0 allocs/op
BenchmarkLARS_StaticAll          100000         13777 ns/op           0 B/op           0 allocs/op

Package Versioning

I'm jumping on the vendoring bandwagon, you should vendor this package as I will not be creating different version with gopkg.in like allot of my other libraries.

Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE, it is so freeing not to worry about it and will help me keep pouring out bigger and better things for you the community.

This package is inspired by the following

Author: Go-playground
Source Code: https://github.com/go-playground/lars 
License: MIT license

#go #golang #http