httprouterx

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2024 License: BSD-3-Clause Imports: 3 Imported by: 0

README

httprouter

The josestg/httprouterx is a wrapper for the julienschmidt/httprouter package that modifies the handler signature to return an error and accept optional middleware.

Installation

go get github.com/josestg/httprouterx

Usage

Default Configuration
func main() {
	log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))

	mux := httprouterx.NewServeMux() // default configuration.
	/*
	   the default configuration:

	   mux := httprouterx.NewServeMux(
	          httprouterx.Options.HandleOption(true),
	          httprouterx.Options.RedirectFixedPath(true),
	          httprouterx.Options.RedirectTrailingSlash(true),
	          httprouterx.Options.HandleMethodNotAllowed(true),
	          httprouterx.Options.PanicHandler(httprouterx.DefaultHandlers.Panic),
	          httprouterx.Options.NotFoundHandler(httprouterx.DefaultHandlers.NotFound()),
	          httprouterx.Options.LastResortErrorHandler(httprouterx.DefaultHandlers.LastResortError),
	          httprouterx.Options.MethodNotAllowedHandler(httprouterx.DefaultHandlers.MethodNotAllowed()),
	      )
	*/

	// register using httprouterx.Handler
	mux.Handle("GET", "/", httprouterx.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
		_, err := fmt.Fprintf(w, "method: %s, url: %s", r.Method, r.URL)
		return err
	}))

	// or using httprouterx.HandlerFunc directly.
	mux.HandleFunc("GET", "/ping", func(w http.ResponseWriter, r *http.Request) error {
		_, err := io.WriteString(w, "PONG!")
		return err
	})

	// or using httprouterx.Route
	mux.Route(httprouterx.Route{
		Method: "GET",
		Path:   "/hello",
		Handler: func(w http.ResponseWriter, r *http.Request) error {
			_, err := io.WriteString(w, "World!")
			return err
		},
	})

	log.Info("server is started")
	if err := http.ListenAndServe(":8081", mux); err != nil {
		log.Error("listen and serve failed", "error", err)
	}
}
Global Middleware
func main() {
	log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))

	global := httprouterx.FoldMiddleware(
		// anything up here will be executed before the logged middleware.
		logged(log),
		// anything down here will be executed after the logged middleware.
	)

	mux := httprouterx.NewServeMux(
		httprouterx.Options.Middleware(global),
	)

	// or using httprouterx.Route
	mux.Route(httprouterx.Route{
		Method: "GET",
		Path:   "/hello",
		Handler: func(w http.ResponseWriter, r *http.Request) error {
			_, err := io.WriteString(w, "World!")
			return err
		},
	})

	log.Info("server is started")
	if err := http.ListenAndServe(":8081", mux); err != nil {
		log.Error("listen and serve failed", "error", err)
	}
}

func logged(log *slog.Logger) httprouterx.Middleware {
	return func(h httprouterx.Handler) httprouterx.Handler {
		return httprouterx.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
			l := log.With("method", r.Method, "url", r.URL)
			if err := h.ServeHTTP(w, r); err != nil {
				l.ErrorContext(r.Context(), "request failed", "error", err)
			} else {
				l.InfoContext(r.Context(), "request succeeded")
			}
			return nil
		})
	}
}
Route-Specific Middleware
func main() {
	log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))

	mux := httprouterx.NewServeMux()

	// or using httprouterx.Route
	route := httprouterx.Route{
		Method: "GET",
		Path:   "/hello",
		Handler: func(w http.ResponseWriter, r *http.Request) error {
			_, err := io.WriteString(w, "World!")
			return err
		},
	}

	// route-specific middleware, only applied to `GET /hello` route.
	mux.Route(route, logged(log))

	log.Info("server is started")
	if err := http.ListenAndServe(":8081", mux); err != nil {
		log.Error("listen and serve failed", "error", err)
	}
}

func logged(log *slog.Logger) httprouterx.Middleware {
	return func(h httprouterx.Handler) httprouterx.Handler {
		return httprouterx.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
			l := log.With("method", r.Method, "url", r.URL)
			if err := h.ServeHTTP(w, r); err != nil {
				l.ErrorContext(r.Context(), "request failed", "error", err)
			} else {
				l.InfoContext(r.Context(), "request succeeded")
			}
			return nil
		})
	}
}
Best Practice for using mux.Route instead of mux.Handle

Keep the route's Swagger docs and route configuration closed to minimize misconfigurations when there are changes to the API contract.

For example:

func main() {
    log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))

    mux := httprouterx.NewServeMux()
    mux.Route(TodoRoute())

    log.Info("server is started")
    if err := http.ListenAndServe(":8081", mux); err != nil {
        log.Error("listen and serve failed", "error", err)
    }
}

type Todo struct {
    ID        int64  `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

// TodoRoute creates a new Todos route.
//
//	@Summary		Get list of todos.
//	@Accept			json
//	@Produce		json
//	@Success		200				{object}	[]Todo
//	@Router			/api/v1/todos [get]
func TodoRoute() httprouterx.Route {
    return httprouterx.Route{
        Method: "GET",
        Path:   "/api/v1/todos",
        Handler: func(w http.ResponseWriter, r *http.Request) error {
            w.WriteHeader(200)
            return json.NewEncoder(w).Encode([]Todo{{1, "todo 1", true}})
        },
    }
}

Documentation

Index

Constants

View Source
const DefaultHandlers nsDefaultHandlers = 0

DefaultHandlers is a namespace for accessing default handlers.

View Source
const Options nsOpts = 0

Options is a namespace for accessing options.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Enables automatic redirection if the current route can't be matched but a
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301 for GET requests
	// and 307 for all other request methods.
	RedirectTrailingSlash bool

	// If enabled, the router tries to fix the current request path, if no
	// handle is registered for it.
	// First superfluous path elements like ../ or // are removed.
	// Afterward the router does a case-insensitive lookup of the cleaned path.
	// If a handle can be found for this route, the router makes a redirection
	// to the corrected path with status code 301 for GET requests and 307 for
	// all other request methods.
	// For example /FOO and /..//Foo could be redirected to /foo.
	// RedirectTrailingSlash is independent of this option.
	RedirectFixedPath bool

	// If enabled, the router checks if another method is allowed for the
	// current route, if the current request can not be routed.
	// If this is the case, the request is answered with 'Method Not Allowed'
	// and HTTP status code 405.
	// If no other Method is allowed, the request is delegated to the NotFound
	// handler.
	HandleMethodNotAllowed bool

	// If enabled, the router automatically replies to OPTIONS requests.
	// Custom OPTIONS handlers take priority over automatic replies.
	HandleOPTIONS bool

	// An optional http.Handler that is called on automatic OPTIONS requests.
	// The handler is only called if HandleOPTIONS is true and no OPTIONS
	// handler for the specific path was set.
	// The "Allowed" header is set before calling the handler.
	GlobalOPTIONS http.Handler

	// Configurable http.Handler which is called when no matching route is
	// found.
	NotFound http.Handler

	// Configurable http.Handler which is called when a request
	// cannot be routed and HandleMethodNotAllowed is true.
	// The "Allow" header with allowed request methods is set before the handler
	// is called.
	MethodNotAllowed http.Handler

	// Function to handle panics recovered from http handlers.
	// It should be used to generate an error page and return the http error code
	// 500 (Internal Server Error).
	// The handler can be used to keep your server from crashing because of
	// unrecoverable panics.
	PanicHandler func(http.ResponseWriter, *http.Request, any)
}

Config is the configuration for the underlying httprouter.Router.

type Handler

type Handler interface {
	// ServeHTTP is just like http.Handler.ServeHTTP, but it returns an error.
	ServeHTTP(http.ResponseWriter, *http.Request) error
}

Handler is modified version of http.Handler.

type HandlerFunc

type HandlerFunc func(http.ResponseWriter, *http.Request) error

HandlerFunc is a function that implements Handler. It is used to create a Handler from an ordinary function.

func (HandlerFunc) ServeHTTP

func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error

ServeHTTP implements Handler.

type LastResortErrorHandler

type LastResortErrorHandler func(http.ResponseWriter, *http.Request, error)

LastResortErrorHandler is the error handler that is called if after all middlewares, there is still an error occurs.

type Middleware

type Middleware func(Handler) Handler

Middleware wraps the Handler with additional logic.

func FoldMiddleware

func FoldMiddleware(middlewares ...Middleware) Middleware

FoldMiddleware folds set of middlewares into a single middleware. For example:

FoldMiddleware(m1, m2, m3).Then(h)
will be equivalent to:
m1(m2(m3(h)))

func (Middleware) Then

func (m Middleware) Then(h Handler) Handler

Then chains the middleware with the handler.

type Option

type Option func(*ServeMux)

Option is a function that configures the ServeMux.

type Param

type Param = httprouter.Param

Param is alias of httprouter.Param.

type Params

type Params = httprouter.Params

Params is alias of httprouter.Params.

func PathParams

func PathParams(r *http.Request) Params

PathParams gets the path variables from the request.

type Route

type Route struct {
	Method  string
	Path    string
	Handler HandlerFunc
}

Route is used to register a new handler to the ServeMux.

type ServeMux

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

ServeMux is a wrapper of httprouter.Router with modified Handler. Instead of http.Handler, it uses Handler, which returns an error. This modification is used to simplify logic for creating a centralized error handler and logging.

The ServeMux also supports MuxMiddleware, which is a middleware that wraps the Handler for all routes. Since the ServeMux also implements http.Handler, the NetMiddleware can be used to create middleware that will be executed before the ServeMux middleware.

The ServeMux only exposes 3 methods: Route, Handle, and ServeHTTP, which are more simple than the original.

func NewServeMux

func NewServeMux(opts ...Option) *ServeMux

NewServeMux creates a new ServeMux with given options. If no option is given, the Default option is applied.

func (*ServeMux) Handle

func (mux *ServeMux) Handle(method, path string, handler Handler)

Handle registers a new request handler with the given method and path.

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(method, path string, handler HandlerFunc)

HandleFunc just like Handle, but it accepts HandlerFunc.

func (*ServeMux) Route

func (mux *ServeMux) Route(r Route, mid ...Middleware)

Route is a syntactic sugar for Handle(method, path, handler) by using Route struct. This route also accepts variadic Middleware, which is applied to the route handler.

func (*ServeMux) ServeHTTP

func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP satisfies http.Handler.

Jump to

Keyboard shortcuts

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