httplayer

package module
v0.0.100 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2024 License: GPL-3.0 Imports: 4 Imported by: 1

README

httplayer

Disclaimer: this package is NOT another go HTTP router.

What is httplayer

Httplayer is a package designed to enhance the route building phase atop the standard Go HTTP router.

How is structured

You can access to the following functionalities:

  • Builder: A struct facilitating the construction of simple routes with middleware.
  • RoutingDefinition: A struct for building a collection of routes with shared middleware.
  • ServiceBuilder: A struct for assembling multiple services with distinct middlewares.
  • Mounting: Utilities functions to mount the built routes/services
Builder

The Builder struct offers a fluent api for constructing individual routes:

package main

import (
	"github.com/debyten/httplayer"
	"github.com/rs/cors"
	"net/http"
)

func main() {
	corsMw := cors.New(cors.Options{})
	myRoute := httplayer.NewBuilder(http.MethodGet, http.MethodPost).
		Path("/api/user").
		Middleware(corsMw).
		Handler(func(w http.ResponseWriter, r *http.Request) {
        // call stack is corsMw > handler
	}).
		Build()
	mux := http.NewServeMux()
	httplayer.MountRoute(mux, myRoute)
	http.ListenAndServe(":8080", mux)
}
RoutingDefinition

This includes more advanced middleware capabilities. You can define routes like so:

package main

import (
	"github.com/debyten/httplayer"
	"github.com/rs/cors"
	"net/http"
)

func userHandler(w http.ResponseWriter, r *http.Request) {}

func main() {
	corsMw := cors.New(cors.Options{})
	def := httplayer.NewDefinition(corsMw).
		Add(http.MethodGet, "/api/user", userHandler)
	    // Add other routes ...
	mux := httplayer.Mount(def)
	http.ListenAndServe(":8080", mux)
}
ServiceBuilder

The ServiceBuilder is useful for combining multiple services with different middlewares:

package main

import (
	"github.com/debyten/httplayer"
	"github.com/rs/cors"
	"net/http"
)

func NewUserApi(svc UserService) httplayer.Routing {
	return userApi{svc: svc}
}

type userApi struct {
	svc UserService
}

func (u userApi) Routes(l *httplayer.RoutingDefinition) []httplayer.Route {
	return l.
		Add(http.MethodGet, "/api/users/{id}", u.findByIDApi).
		Add(http.MethodPost, "/api/users", u.createApi).
		Done()
}

func NewLoginApi(svc LoginService) httplayer.Routing {
	return loginApi{svc: svc}
}

type loginApi struct {
	svc LoginService
}

func (u loginApi) Routes(l *httplayer.RoutingDefinition) []httplayer.Route {
	return l.
		Add(http.MethodPost, "/api/login", u.loginApi, u.loginRateLimit).
		Done()
}

// Middleware
func (u loginApi) loginRateLimit(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
        // rate limit middleware impl
		h(w, r)
	}
}


func main() {
	corsMw := cors.New(cors.Options{})
	authMiddleware := ...
	userApi := NewUserApi(...)
	loginApi := NewLoginApi(...)
	protectedApis := httplayer.NewServiceBuilder(authMiddleware).Add(userApi)
	publicApis := httplayer.NewServiceBuilder().Add(loginApi)
	mux := httplayer.MountServices(protectedApis, publicApis)
	// we can add cors on top
	h := corsMw.Handler(mux)
	http.ListenAndServe(":8080", h)
}

Detach and Merge

These two functions allows to group features, for example, if we define an rbac middleware like so:


package main

func RBAC(roles ...string) func(http.HandlerFunc) http.HandlerFunc {
	return func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			// implementation
		}
	}
}

Then we can use the middleware while implementing the Service:

package main

type statsApi struct {
	svc StatsService
}

func (u statsApi) Routes(l *httplayer.RoutingDefinition) []httplayer.Route {
	adminRoutes := l.Detach(RBAC("admin")).
		Add(http.MethodPost, "/api/stats/purge", u.purgeApi)
	    Add(http.MethodPost, "/api/stats", u.pushStatApi)
	
	userRoutes := l.Detach(RBAC("user", "admin")).
		Add(http.MethodGet, "/api/stats", u.viewStatsApi).
	    Add(http.MethodPatch, "/api/stats/{id}", u.pinStatApi)
	return httplayer.Merge(adminRoutes, userRoutes)
}

In this way we share the middlewares from l (which is the main routing definition)

Mounting

To finalize the routing setup, you can employ the following functions to mount the routes onto a http.ServeMux:

  • MountRoute: This function installs the specified route onto the provided http.ServeMux.
  • Mount: Use this function to install a collection of RoutingDefinitions onto a new http.ServeMux.
  • DynamicMount: This function installs the given RoutingDefinition onto a new http.ServeMux and utilizes the input channel to dynamically register new Route instances in a separate goroutine.

For the last use case for example, you can think to an api gateway which automatically registers routes as new services are discovered.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DynamicMount

func DynamicMount(ctx context.Context, ch <-chan Route, definitions ...*RoutingDefinition) *http.ServeMux

DynamicMount uses definitions to register the input routes by using the Mount function. The channel is used to dynamically register additional routes in a separate go routine.

The goroutine will halt when context is done.

func Mount

func Mount(definitions ...*RoutingDefinition) *http.ServeMux

Mount the provided routing definitions to `http.ServeMux` and returns it.

func MountRoute

func MountRoute(mux *http.ServeMux, r Route)

func MountServices

func MountServices(services ...*ServiceBuilder) *http.ServeMux

MountServices to a new http.ServeMux

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder represents a helper struct to build a single Route

func NewBuilder

func NewBuilder(method ...string) *Builder

NewBuilder start building a Route specifying the method parameters (POST, PUT, PATCH...)

func (*Builder) Build

func (rb *Builder) Build() Route

Build finalize the api build process applying a reverse function on middleware slice (to preserve the order) and returns the Route with the handler func concatenated with the middlewares

func (*Builder) Handler

func (rb *Builder) Handler(h http.HandlerFunc) *Builder

Handler is a simple http.HandlerFunc

func (*Builder) Middleware

func (rb *Builder) Middleware(middleware ...Middleware) *Builder

Middleware sets the middlewares to be injected to the http.HandlerFunc specified with Handler function. The slice of input `middleware` will be reversed to respect the sequentiality of the call stack.

Example:

Middleware(m1, m2, m3) => m1 -> m2 -> m3
Middleware(m7, m5, m9) => m7 -> m5 -> m9

func (*Builder) Path

func (rb *Builder) Path(p string) *Builder

Path describe the builder path, e.g. /api/v1/test

type Middleware

type Middleware func(h http.HandlerFunc) http.HandlerFunc

Middleware is an alias of http.HandlerFunc wrappers. For example the go chi compress (https://github.com/go-chi/chi/blob/1191921289e82fdc56f298a76ff254742f568ece/middleware/compress.go#L41-L44) is a Middleware too.

type Route

type Route struct {
	// contains filtered or unexported fields
}

A Route consists of an HTTP method, a URL path, and a handler function to be executed when the route is matched.

  Example:

	 GET /api/v1/users func(http.ResponseWriter, *http.Request)

func Merge

func Merge(definitions ...*RoutingDefinition) []Route

func (Route) Handler

func (a Route) Handler() http.HandlerFunc

Handler describes a `func(http.ResponseWriter, *http.Request)`.

The final Handler become the result of merged middlewares plus the handler itself.

Example:

NewBuilder("GET").Path("/").Handler(myHandler).Middleware(mid1, mid2, midN...)

Result execution stack:

mid1 -> mid2 -> midN -> myHandler

func (Route) Methods

func (a Route) Methods() []string

Methods describes the api method (GET, POST, PUT...)

func (Route) Path

func (a Route) Path() string

Path describe the api path

type Routing

type Routing interface {
	Routes(with *RoutingDefinition) []Route
}

type RoutingDefinition

type RoutingDefinition struct {
	// contains filtered or unexported fields
}

RoutingDefinition represents a slice of Route

func NewDefinition

func NewDefinition(m ...Middleware) *RoutingDefinition

NewDefinition returns a new *RoutingDefinition instance. The provided middleware functions are injected into the routes added after this call. The middleware functions are executed sequentially in the order they are provided.

Usage:
 - start with NewDefinition() and pass some middleware e.g. firstMiddleware, secondMiddleware, etc.
 - add routes using Add method.
 - invoke Build to return a slice of Route.

Example:
NewDefinition(firstMiddleware, secondMiddleware).
  Add(http.MethodGet, "/path", handler1).
  Add(http.MethodPost, "/path2", handler2, anotherMiddleware)

Resulting stack for routes:

  • (GET /path): firstMiddleware => secondMiddleware => handler1
  • (POST /path2): firstMiddleware => secondMiddleware => anotherMiddleware => handler2

func (*RoutingDefinition) Add

func (m *RoutingDefinition) Add(method string, path string, h http.HandlerFunc, mid ...Middleware) *RoutingDefinition

Add saves a Route into *RoutingDefinition instance

Example:

def := NewDefinition()
def.Route("GET", "/api/v1/users", func..., m1, m2, m3)

func (*RoutingDefinition) AddMany

func (m *RoutingDefinition) AddMany(methods []string, path string, h http.HandlerFunc, mid ...Middleware) *RoutingDefinition

AddMany is like Add but this function can address more than one http method with the same handler.

func (*RoutingDefinition) Detach

func (m *RoutingDefinition) Detach(mid ...Middleware) *RoutingDefinition

Detach creates a new RoutingDefinition by concatenating the current middlewares from m into the newly created RoutingDefinition

func (*RoutingDefinition) Done

func (m *RoutingDefinition) Done() []Route

Done returns the slice of Route.

type ServiceBuilder

type ServiceBuilder struct {
	// contains filtered or unexported fields
}

func NewServiceBuilder

func NewServiceBuilder(commonMiddlewares ...Middleware) *ServiceBuilder

func (*ServiceBuilder) Add

func (s *ServiceBuilder) Add(routing ...Routing) *ServiceBuilder

func (*ServiceBuilder) MW

func (s *ServiceBuilder) MW(mws ...Middleware) *ServiceBuilder

MW appends new middleware to the already known `middlewares` specified in NewServiceBuilder.

func (*ServiceBuilder) MountTo

func (s *ServiceBuilder) MountTo(mux *http.ServeMux)

Jump to

Keyboard shortcuts

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