router

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 21, 2024 License: MIT Imports: 14 Imported by: 1

README

router

Open in Gitpod

A declarative router running on top of Chi.

Why another router?

After checking the existing routers, all of them came short from what we expect a router to be:

  • Routing declaration should be clear, easy and concise without the extra bloat and cognitive load.

  • Routing should not be "obfuscated" behind the implementation. Declaration should be simple enough to understand without additional explanations.

  • The standard handlers in Go do not expect anything to be returned. As a result quite often an error is handled but a return statement is simply forgotten, and errors fall through. To avoid this common scenario, here the handlers expect a string to be returned.

  • Simplify declaring routes. Writing the HTTP method each time is very repetitive, and usually not needed for most endpoints. Implicitly all routes respond to all HTTP verbs. Unless explicitly specified.

  • Middlewares are defined for each route explicitly.

  • List routes in table format, with path, name and middlewares applicable

  • Easy to test. By returning a string, most routes are easy to test on their own by calling them directly and inspecting the output.

  • Lightweight, should not add extra fluff

  • Fast, should not slow down the application unnecessarily

Installation

go get -u github.com/gouniverse/router

Listing Routes

This router allows you to list routes for easy preview

router.List(globalMiddlewares, routes)
+------------------------------------+
| GLOBAL MIDDLEWARE LIST (TOTAL: 2)  |
+---+--------------------------------+
| # | MIDDLEWARE NAME                |
+---+--------------------------------+
| 1 | Append JWT Token               |
| 2 | Append Session Cookies         |
+---+--------------------------------+
+-------------------------------------------------------------------------------------------------+
| ROUTES LIST (TOTAL: 5)                                                                          |
+---+-----------------+------------+---------------------------+----------------------------------+
| # | ROUTE PATH      | METHODS    | ROUTE NAME                | MIDDLEWARE NAME LIST             |
+---+-----------------+------------+---------------------------+----------------------------------+
| 1 | /               | [ALL]      | Home                      | [Web Middleware]                 |
| 2 | /example        | [GET POST] | Example                   | [Web Middleware]                 |
| 3 | /api/form-submit| [POST]     | Submit Form               | [API Middleware, Verify Form]    |
| 4 | /user/dashboard | [ALL]      | User Dashboard            | [Check if User is Authenticated] |
| 5 | /*              | [ALL]      | Catch All. Page Not Found | []                               |
+---+-----------------+------------+---------------------------+----------------------------------+

Example Routes

checkUserAuthenticatedMiddleware := Middleware{
    Name: "Check if User is Authenticated"
    Handler: middleware.CheckUserAuthenticated,
}

routes = []router.RouteInterface{
    // Example of simple "Hello world" endpoint
    &router.Route{
        Name: "Home",
        Path: "/",
        HTMLHandler: func(w http.ResponseWriter, r *http.Request) string {
            return "Hello world"
        },
    },
    // Example of POST route
    &router.Route{
        Name: "Submit Form",
        Path: "/form-submit",
        Methods: []string{http.MethodPost],
        JSONHandler: func(w http.ResponseWriter, r *http.Request) string {
            return api.Success("Form submitted")
        },
    },
    // Example of route with local middlewares
    &router.Route{
        Name: "User Dashboard",
        Path: "/user/dashboard",
        Middlewares: []Middleware{
			checkUserAuthenticatedMiddleware,
        },
        HTMLHandler: func(w http.ResponseWriter, r *http.Request) string {
            return "Welcome to your dashboard"
        },
    },
    // Catch-all endpoint
    &router.Route{
        Name: "Catch All. Page Not Found",
        Path: "/*",
        HTMLHandler: func(w http.ResponseWriter, r *http.Request) string {
            return "Page not found"
        },
    },
}

Example with Chi

// 1. Prepare your global middleware
globalMiddlewares := []Middleware{
    NewCompressMiddleware(5, "text/html", "text/css"),
    NewGetHeadMiddleware(),
    NewCleanPathMiddleware(),
    NewRedirectSlashesMiddleware(),
    NewTimeoutMiddleware(30),
    NewLimitByIpMiddleware(20, 1),       // 20 req per second
	NewLimitByIpMiddleware(180, 60),     // 180 req per minute
	NewLimitByIpMiddleware(12000, 3600), // 12000 req hour
}

// 1.1. Example skipping middlewares while testing
if config.AppEnvironment != config.APP_ENVIRONMENT_TESTING {
	globalMiddlewares = append(globalMiddlewares, NewLoggerMiddleware())
	globalMiddlewares = append(globalMiddlewares, NewRecovererMiddleware())
}

// 1.2. Example of declaring custom middleware (on the fly)
globalMiddlewares = append(globalMiddlewares, router.Middleware{
    Name:    "My Custom Middleware",
    Handler: func (next http.Handler) http.Handler {
        // My custom implementation here
    },
})

// 2. Prepare your routes
routes := []router.RouteInterface{}
routes = append(routes, adminControllers.Routes()...)
routes = append(routes, userControllers.Routes()...)
routes = append(routes, websiteControllers.Routes()...)

// Get a Chi router
chiRouter := router.NewChiRouter(globalMiddlewares, routes)

// Now you can use it
http.ListenAndServe(":3000", chiRouter)

Example Applying Path to Multiple Routes

RoutesPrependPath is a helper method allowing you to quickly add a path to the beginning of a group of routes

// Prepend /user to the path of the user routes
userRoutes = router.RoutesPrependPath(userRoutes, "/user")

// Prepend /admin to the path of the admin routes
adminRoutes = router.RoutesPrependPath(adminRoutes, "/admin")

Example Applying Middleware to Multiple Routes

RoutesPrependMiddlewares is a helper method allowing you to quickly add local middlewares to a group of routes. These middlewares are applied to the beginning and will be called first, before the ones already defined

router.RoutesPrependMiddlewares(userRouters, []func(http.Handler) http.Handler{
    middleware.CheckUserAuthenticated,
})

Using HTML Controllers

type homeController struct{}

var _ router.HTMLControllerInterface = (*homeController)(nil)

func (controller *homeController) Handler(w http.ResponseWriter, r *http.Request) string {
	return "Hello world"
}

Using JSON Controllers

type homeController struct{}

var _ router.JSONControllerInterface = (*homeController)(nil)

func (controller *homeController) Handler(w http.ResponseWriter, r *http.Request) string {
    return api.Success("Hello world")
}

Using Idiomatic Controllers

type homeController struct{}

var _ router.ControllerInterface = (*homeController)(nil)

func (controller *homeController) Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello world"))
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func List added in v0.3.0

func List(globalMiddlewares []Middleware, routes []RouteInterface)

func NewChiRouter added in v0.2.0

func NewChiRouter(globalMiddlewares []Middleware, routes []RouteInterface) *chi.Mux

NewChiRouter creates a new chi router with the given global middlewares and routes.

Parameters: - globalMiddlewares: A slice of middlewares that will be applied to all routes. - routes: A slice of Route structs that define the routes for the router.

Returns: - chi.Mux: The newly created chi router.

func NewRouter added in v1.0.1

func NewRouter(globalMiddlewares []Middleware, routes []Route) *http.ServeMux

NewRouter creates a new router with the given global middlewares and routes.

Parameters: - globalMiddlewares: A slice of middlewares that will be applied to all routes. - routes: A slice of Route structs that define the routes for the router.

Returns: - http.ServeMux: The newly created router.

Types

type ControllerInterface added in v1.1.0

type ControllerInterface interface {
	// Handler is the single entry point for the controller.
	//
	// Parameters:
	//   w - The http.ResponseWriter object.
	//   r - The http.Request object.
	//
	// Returns:
	//   void - No return value.
	Handler(w http.ResponseWriter, r *http.Request)
}

ControllerInterface is an interface for controllers with idiomatic behavior. It will not add any headers to the response by default.

type CorsOptions added in v1.0.0

type CorsOptions struct {
	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	// If the special "*" value is present in the list, all origins will be allowed.
	// An origin may contain a wildcard (*) to replace 0 or more characters
	// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
	// Only one wildcard can be used per origin.
	// Default value is ["*"]
	AllowedOrigins []string

	// AllowOriginFunc is a custom function to validate the origin. It takes the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(r *http.Request, origin string) bool

	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (HEAD, GET and POST).
	AllowedMethods []string

	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	// If the special "*" value is present in the list, all headers will be allowed.
	// Default value is [] but "Origin" is always appended to the list.
	AllowedHeaders []string

	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposedHeaders []string

	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool

	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge int

	// OptionsPassthrough instructs preflight to let other potential next handlers to
	// process the OPTIONS method. Turn this on if your application handles OPTIONS.
	OptionsPassthrough bool

	// Debugging flag adds additional output to debug server side CORS issues
	Debug bool
}

type HTMLControllerInterface added in v1.3.0

type HTMLControllerInterface interface {
	// Handler is the single entry point for the controller.
	//
	// Parameters:
	//   w - The http.ResponseWriter object.
	//   r - The http.Request object.
	//
	// Returns:
	//   string - The response to be sent back to the client.
	Handler(w http.ResponseWriter, r *http.Request) string
}

HTMLControllerInterface is an interface for controllers that return HTML. It will automatically add the "Content-Type: text/html" header to the response.

type JSONControllerInterface added in v1.3.0

type JSONControllerInterface interface {
	// Handler is the single entry point for the controller.
	//
	// Parameters:
	//   w - The http.ResponseWriter object.
	//   r - The http.Request object.
	//
	// Returns:
	//   string - The response to be sent back to the client.
	Handler(w http.ResponseWriter, r *http.Request) string
}

JSONControllerInterface is an interface for controllers that return JSON. It will automatically add the "Content-Type: application/json" header to the response.

type Middleware added in v0.3.0

type Middleware struct {
	Name    string
	Handler func(http.Handler) http.Handler
}

func NewBasicAuthentication added in v1.2.1

func NewBasicAuthentication(expectedUsername, expectedPassword string) Middleware

func NewCleanPathMiddleware added in v0.6.0

func NewCleanPathMiddleware() Middleware

NewCleanPathMiddleware will clean out double slash mistakes from a user's request path. For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1

func NewCompressMiddleware added in v0.5.0

func NewCompressMiddleware(level int, types ...string) Middleware

NewCompressMiddleware compresses response body of a given content types to a data format based on Accept-Encoding request header. It uses a given compression level.

NOTE: make sure to set the Content-Type header on your response otherwise this middleware will not compress the response body. For ex, in your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) or set it manually.

Passing a compression level of 5 is sensible value

func NewCorsMiddleware added in v1.0.0

func NewCorsMiddleware(options CorsOptions) Middleware

NewCorsMiddleware creates a new CORS middleware

Example: <code>

router.NewCorsMiddleware(router.corsOptions{
    // AllowedOrigins:   []string{"https://foo.com"}, // Use this to allow specific origin hosts
    AllowedOrigins: []string{"https://*", "http://*"},
    // AllowOriginFunc:  func(r *http.Request, origin string) bool { return true },
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
    ExposedHeaders:   []string{"Link"},
    AllowCredentials: false,
    MaxAge:           300, // Maximum value not ignored by any of major browsers
})

</code>

func NewGetHeadMiddleware added in v0.5.0

func NewGetHeadMiddleware() Middleware

NewGetHeadMiddleware automatically route undefined HEAD requests to GET handlers.

func NewHeartbeatMiddleware added in v0.6.0

func NewHeartbeatMiddleware(endpoint string) Middleware

NewHeartbeatMiddleware endpoint middleware useful to setting up a path like `/ping` that load balancers or uptime testing external services can make a request before hitting any routes. It's also convenient to place this above ACL middlewares as well.

func NewLoggerMiddleware added in v0.5.0

func NewLoggerMiddleware() Middleware

func NewNakedDomainToWwwMiddleware added in v0.9.0

func NewNakedDomainToWwwMiddleware(hostExcludes []string) Middleware

NewNakedDomainToWwwMiddleware will redirect a "www" subdomain to naked (non-www) domain

func NewRateLimitByIpMiddleware added in v0.5.0

func NewRateLimitByIpMiddleware(requestLimit int, seconds int) Middleware

func NewRealIpMiddleware added in v0.6.0

func NewRealIpMiddleware() Middleware

NewRealIpMiddleware is a middleware that sets a http.Request's RemoteAddr to the results of parsing either the True-Client-IP, X-Real-IP or the X-Forwarded-For headers (in that order).

This middleware should be inserted fairly early in the middleware stack to ensure that subsequent layers (e.g., request loggers) which examine the RemoteAddr will see the intended value.

You should only use this middleware if you can trust the headers passed to you (in particular, the two headers this middleware uses), for example because you have placed a reverse proxy like HAProxy or nginx in front of chi. If your reverse proxies are configured to pass along arbitrary header values from the client, or if you use this middleware without a reverse proxy, malicious clients will be able to make you very sad (or, depending on how you're using RemoteAddr, vulnerable to an attack of some sort).

func NewRecovererMiddleware added in v0.5.0

func NewRecovererMiddleware() Middleware

func NewRedirectSlashesMiddleware added in v0.5.0

func NewRedirectSlashesMiddleware() Middleware

func NewTimeoutMiddleware added in v0.5.0

func NewTimeoutMiddleware(seconds int) Middleware

func NewWwwToNakedDomainMiddleware added in v0.9.0

func NewWwwToNakedDomainMiddleware() Middleware

NewWwwToNakedDomainMiddleware will redirect a "www" subdomain to naked (non-www) domain

type Route

type Route struct {
	// Domain      string
	Path        string
	Methods     []string // optional, default all methods
	Handler     func(w http.ResponseWriter, r *http.Request)
	HTMLHandler func(w http.ResponseWriter, r *http.Request) string
	JSONHandler func(w http.ResponseWriter, r *http.Request) string
	Middlewares []Middleware
	Name        string // optional, default empty string
}

func (*Route) AddMiddlewares added in v1.3.0

func (route *Route) AddMiddlewares(middlewares ...Middleware)

func (Route) GetHandler added in v1.3.0

func (route Route) GetHandler() func(w http.ResponseWriter, r *http.Request)

func (*Route) GetMethods added in v1.3.0

func (route *Route) GetMethods() []string

func (Route) GetMiddlewares added in v1.3.0

func (route Route) GetMiddlewares() []Middleware

func (*Route) GetName added in v1.3.0

func (route *Route) GetName() string

func (*Route) GetPath added in v1.3.0

func (route *Route) GetPath() string

func (*Route) PrependMiddlewares added in v1.3.0

func (route *Route) PrependMiddlewares(middlewares ...Middleware)

func (Route) ServeHTTP added in v1.3.0

func (route Route) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Route) SetHandler added in v1.4.0

func (route *Route) SetHandler(handler func(w http.ResponseWriter, r *http.Request))

func (*Route) SetMethods added in v1.4.0

func (route *Route) SetMethods(methods []string)

func (*Route) SetMiddlewares added in v1.4.0

func (route *Route) SetMiddlewares(middlewares []Middleware)

func (*Route) SetName added in v1.4.0

func (route *Route) SetName(name string)

func (*Route) SetPath added in v1.4.0

func (route *Route) SetPath(path string)

func (Route) String added in v1.3.0

func (route Route) String() string

type RouteInterface added in v1.3.0

type RouteInterface interface {
	GetHandler() func(w http.ResponseWriter, r *http.Request)
	SetHandler(handler func(w http.ResponseWriter, r *http.Request))
	GetName() string
	SetName(name string)
	GetMethods() []string
	SetMethods(methods []string)
	GetMiddlewares() []Middleware
	SetMiddlewares(middlewares []Middleware)
	GetPath() string
	SetPath(path string)

	AddMiddlewares(middlewares ...Middleware)
	PrependMiddlewares(middlewares ...Middleware)
}

func RoutesPrependMiddlewares added in v0.2.0

func RoutesPrependMiddlewares(routes []RouteInterface, middlewares []Middleware) []RouteInterface

RoutesPrependMiddlewares prepends the given middlewares to the beginning of the Middlewares field of each Route from the provided slice.

Parameters: - routes: A slice of Route structs representing the routes. - middlewares: A slice of middlewares to be prepended to each Route.

Returns: - A slice of Route structs with the updated Middlewares field.

func RoutesPrependPath added in v0.2.0

func RoutesPrependPath(routes []RouteInterface, path string) []RouteInterface

RoutesPrependPath prepends the given path to the path of each route in the provided slice.

Parameters: - routes: a slice of Route structs representing the routes. - path: a string representing the path to prepend to each route's path.

Returns: - a slice of Route structs with the updated paths.

type TextControllerInterface added in v1.3.0

type TextControllerInterface interface {
	// Handler is the single entry point for the controller.
	//
	// Parameters:
	//   w - The http.ResponseWriter object.
	//   r - The http.Request object.
	//
	// Returns:
	//   string - The response to be sent back to the client.
	Handler(w http.ResponseWriter, r *http.Request) string
}

TextControllerInterface is an interface for controllers that return plain text. It will automatically add the "Content-Type: text/plain" header to the response.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL