middleware

package
v3.1.0+incompatible Latest Latest
Warning

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

Go to latest
Published: May 2, 2019 License: MIT Imports: 18 Imported by: 0

README

Middleware

Bastion's middlewares are just stdlib net/http middleware handlers. There is nothing special about them, which means the router and all the tooling is designed to be compatible and friendly with any middleware in the community.

Recovery

Gracefully absorb panics and prints the stack trace. It log the panic (and a backtrace) in os.Stdout by default, this can be change with the RecoveryLoggerOutput functional option and returns a HTTP 500 (Internal Server Error) status if possible.

Options
  • RecoveryLoggerOutput(w io.Writer) set the logger output writer. Default os.Stdout.
package main

import (
	"os"
	
	"github.com/ifreddyrondon/bastion/middleware"
)

func main() {
	// default
	middleware.Recovery()

	// with options
	middleware.Recovery(
		middleware.RecoveryLoggerOutput(os.Stdout),
	)
}

InternalError

InternalError intercept responses to verify their status and handle the error. It gets the response code and if it's >= 500 handles the error with a default error message without disclosure internal information. The real error keeps logged.

Options
  • InternalErrMsg(s string) set default error message to be sent. Default "looks like something went wrong".
  • InternalErrLoggerOutput(w io.Writer) set the logger output writer. Default os.Stdout.
package main

import (
	"errors"
	
	"github.com/ifreddyrondon/bastion/middleware"
)

func main() {
	// default
	middleware.InternalError()

	// with options
	middleware.InternalError(
		middleware.InternalErrMsg(errors.New("well, this is awkward")),
	)
}

Logger

Logger is a middleware that logs the start and end of each request, along with some useful data about what was requested, what the response status was, and how long it took to return.

Alternatively, look at https://github.com/rs/zerolog#integration-with-nethttp.

Options
  • AttachLogger(log zerolog.Logger) chain the logger with the middleware.
  • EnableLogReqIP() show the request ip.
  • EnableLogUserAgent() show the user agent of the request.
  • EnableLogReferer() show referer of the request.
  • DisableLogMethod() hide the request method.
  • DisableLogURL() hide the request url.
  • DisableLogStatus() hide the request status.
  • DisableLogSize() hide the request size.
  • DisableLogDuration() hide the request duration.
  • DisableLogRequestID() hide the request id.
package main

import (
	"github.com/ifreddyrondon/bastion/middleware"
)

func main() {
	// default
	middleware.Logger()

	// for full info in production
	middleware.Logger(
		middleware.EnableLogReqIP(),
		middleware.EnableLogUserAgent(),
		middleware.EnableLogReferer(),
	)
}

Listing

Parses the url from a request and stores a listing.Listing on the context, it can be accessed through middleware.GetListing.

Sample usage.. for the url: /repositories/1?limit=10&offset=25

package main

import (
	"net/http"
	
	"github.com/ifreddyrondon/bastion"
	"github.com/ifreddyrondon/bastion/middleware"
)

func list(w http.ResponseWriter, r *http.Request) {
	listing, _ := middleware.GetListing(r.Context())
	// do something with listing
}

func main() {
	app := bastion.New()
	app.Use(middleware.Listing())
	app.Get("/repositories/{id}", list)
	app.Serve()
}

WrapResponseWriter

What happens when it is necessary to know the http status code or the bytes written or even the response it self? WrapResponseWriter provides an easy way to capture http related metrics from your application's http.Handlers or event hijack the response.

Sample usage.. The defaultMiddleware capture the metrics http status code and the bytes written, the copyWriterMiddleware captures the default metrics and creates a copy of the written content and the hijackWriterMiddleware does the same as the previous ones but don't flush the content.

package main

import (
	"bytes"
	"fmt"
	"net/http"

	"github.com/ifreddyrondon/bastion"
	"github.com/ifreddyrondon/bastion/middleware"
)

func h(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(201)
	w.Write([]byte("created"))
}

func defaultMiddleware(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		m, snoop := middleware.WrapResponseWriter(w)
		next.ServeHTTP(snoop, r)
		fmt.Println(m.Code)
		fmt.Println(m.Bytes)
	}
	return http.HandlerFunc(fn)
}

func copyWriterMiddleware(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		var out bytes.Buffer
		m, snoop := middleware.WrapResponseWriter(w, middleware.WriteHook(middleware.CopyWriterHook(&out)))
		next.ServeHTTP(snoop, r)
		fmt.Println(m.Code)
		fmt.Println(m.Bytes)
		fmt.Println(out.String())
	}
	return http.HandlerFunc(fn)
}

func hijackWriterMiddleware(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		var out bytes.Buffer
		m, snoop := middleware.WrapResponseWriter(w, middleware.WriteHook(middleware.HijackWriteHook(&out)))
		next.ServeHTTP(snoop, r)
		fmt.Println(m.Code)
		fmt.Println(m.Bytes)
		fmt.Println(out.String())
	}
	return http.HandlerFunc(fn)
}

func main() {
	app := bastion.New()
	app.With(defaultMiddleware).Get("/", h)
	app.With(copyWriterMiddleware).Get("/copy", h)
	app.With(hijackWriterMiddleware).Get("/hijack", h)
	app.Serve()
}

Auxiliary middlewares and more references

For more references check chi middlewares

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// CollectHeaderHook capture the response code into a WriterMetricsCollector and forward the execution to the main
	// WriteHeader method. It's the default hook for WriteHeader when WrapWriter is used.
	CollectHeaderHook = func(collector *WriterMetricsCollector) func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
		return func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
			return func(code int) {
				next(code)
				collector.locker.Lock()
				defer collector.locker.Unlock()
				if !collector.wroteHeader {
					collector.Code = code
					collector.wroteHeader = true
				}
			}
		}
	}

	// HijackWriteHeaderHook capture the response code into a WriterMetricsCollector. Warning it'll not forward to the
	// main WriteHeader method execution.
	HijackWriteHeaderHook = func(collector *WriterMetricsCollector) func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
		return func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
			return func(code int) {
				collector.locker.Lock()
				defer collector.locker.Unlock()
				if !collector.wroteHeader {
					collector.Code = code
					collector.wroteHeader = true
				}
			}
		}
	}

	// CollectBytesHook capture the amount of bytes into a WriterMetricsCollector and forward the execution to the main
	// Write method. It's the default hook for Write when WrapWriter is used.
	CollectBytesHook = func(collector *WriterMetricsCollector) func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc {
		return func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc {
			return func(p []byte) (int, error) {
				n, err := next(p)
				collector.locker.Lock()
				defer collector.locker.Unlock()
				collector.Bytes += int64(n)
				collector.wroteHeader = true
				return n, err
			}
		}
	}
)
View Source
var (
	// ListingCtxKey is the context.Context key to store the Listing for a request.
	ListingCtxKey = &contextKey{"Listing"}
)

Functions

func CopyWriterHook

func CopyWriterHook(w io.Writer) func(collector *WriterMetricsCollector) func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc

CopyWriterHook makes a copy of the bytes into a io.Writer and forward the execution to the main Write method. It'll save the amount of bytes into a WriterMetricsCollector.

func Filter

func Filter(criteria ...filtering.FilterDecoder) func(*listingConfig)

Filter set criteria to filter

func GetListing

func GetListing(ctx context.Context) (*listing.Listing, error)

GetListing will return the listing reference assigned to the context, or nil if there is any error or there isn't a Listing instance.

func HijackWriteHook

func HijackWriteHook(w io.Writer) func(collector *WriterMetricsCollector) func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc

HijackWriteHook write the response bytes into a io.Writer. It'll save the amount of bytes into a WriterMetricsCollector. Warning it'll not forward to the main Write method execution.

func InternalErrLoggerOutput

func InternalErrLoggerOutput(w io.Writer) func(*internalErr)

InternalErrLoggerOutput set the output for the logger

func InternalErrMsg

func InternalErrMsg(err error) func(*internalErr)

InternalErrMsg set default error message to be sent

func InternalError

func InternalError(opts ...func(*internalErr)) func(http.Handler) http.Handler

InternalError intercept responses to verify their status and handle the error. It gets the response code and if it's >= 500 handles the error with a default error message without disclosure internal information. The real error keeps logged.

func Limit

func Limit(limit int) func(*listingConfig)

Limit set the paging limit default.

func Listing

func Listing(opts ...func(*listingConfig)) func(http.Handler) http.Handler

Listing is a middleware that parses the url from a request and stores a listing.Listing on the context, it can be accessed through middleware.GetListing.

Sample usage.. for the url: `/repositories/1?limit=10&offset=25`

 func routes() http.Handler {
   r := chi.NewRouter()
   r.Use(middleware.Listing())
   r.Get("/repositories/{id}", ListRepositories)
   return r
 }

 func ListRepositories(w http.ResponseWriter, r *http.Request) {
	  list, _ := middleware.GetListing(r.Context())

	  // do something with listing
}

func Logger

func Logger(opts ...LoggerOpt) func(http.Handler) http.Handler

Logger is a middleware that logs the start and end of each request, along with some useful data about what was requested, what the response status was, and how long it took to return.

Alternatively, look at https://github.com/rs/zerolog#integration-with-nethttp.

func MaxAllowedLimit

func MaxAllowedLimit(maxAllowed int) func(*listingConfig)

MaxAllowedLimit set the max allowed limit default.

func Recovery

func Recovery(opts ...func(*recoveryCfg)) func(http.Handler) http.Handler

Recovery is a middleware that recovers from panics, logs the panic (and a backtrace), and returns a HTTP 500 (Internal Server Error) status if possible. Recovery prints a request ID if one is provided.

func RecoveryLoggerOutput

func RecoveryLoggerOutput(w io.Writer) func(*recoveryCfg)

RecoveryLoggerOutput set the output for the logger

func Sort

func Sort(criteria ...sorting.Sort) func(*listingConfig)

Sort set criteria to sort

func WriteHeaderHook

func WriteHeaderHook(hook func(*WriterMetricsCollector) func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc) func(*wrapWriterOpts)

WriteHeaderHook define the method interceptor when WriteHeader is called.

func WriteHook

func WriteHook(hook func(*WriterMetricsCollector) func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc) func(*wrapWriterOpts)

WriteHook define the method interceptor when Write is called.

Types

type LoggerOpt

type LoggerOpt func(*loggerCfg)

func AttachLogger

func AttachLogger(log zerolog.Logger) LoggerOpt

AttachLogger chain the logger with the middleware.

func DisableLogDuration

func DisableLogDuration() LoggerOpt

DisableLogStatus hide the request duration.

func DisableLogMethod

func DisableLogMethod() LoggerOpt

DisableLogMethod hide the request method.

func DisableLogRequestID

func DisableLogRequestID() LoggerOpt

DisableLogStatus hide the request id.

func DisableLogSize

func DisableLogSize() LoggerOpt

DisableLogStatus hide the request size.

func DisableLogStatus

func DisableLogStatus() LoggerOpt

DisableLogStatus hide the request status.

func DisableLogURL

func DisableLogURL() LoggerOpt

DisableLogURL hide the request url.

func EnableLogReferer

func EnableLogReferer() LoggerOpt

EnableLogReferer show referer of the request.

func EnableLogReqIP

func EnableLogReqIP() LoggerOpt

EnableLogReqIP show the request ip.

func EnableLogUserAgent

func EnableLogUserAgent() LoggerOpt

EnableLogUserAgent show the user agent of the request.

type WriterMetricsCollector

type WriterMetricsCollector struct {
	// Code is the first http response code passed to the WriteHeader func of
	// the ResponseWriter. If no such call is made, a default code of 200 is
	// assumed instead.
	Code int
	// bytes is the number of bytes successfully written by the Write or
	// ReadFrom function of the ResponseWriter. ResponseWriters may also write
	// data to their underlying connection directly (e.g. headers), but those
	// are not tracked. Therefor the number of Written bytes will usually match
	// the size of the response body.
	Bytes int64
	// contains filtered or unexported fields
}

WriterMetricsCollector holds metrics captured from writer.

func WrapResponseWriter

func WrapResponseWriter(w http.ResponseWriter, opts ...func(*wrapWriterOpts)) (*WriterMetricsCollector, http.ResponseWriter)

WrapResponseWriter defines a set of method interceptors for methods included in http.ResponseWriter as well as some others. You can think of them as middleware for the function calls they target. It response with a wrapped ResponseWriter and WriterMetricsCollector with some useful metrics.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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