gohm

package module
v2.6.2 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2018 License: MIT Imports: 17 Imported by: 0

README

gohm

gohm is a tiny Go library with HTTP middleware functions.

Usage

Documentation is available via GoDoc.

Description

gohm provides a small collection of HTTP middleware functions to be used when creating a Go micro webservice. With the exception of handler timeout control, all of the configuration options have sensible defaults, so an empty gohm.Config{} object may be used to initialize the http.Handler wrapper to start, and further customization is possible down the road. Using the default handler timeout elides timeout protection, so it's recommended that timeouts are always created for production code.

Here is a simple example:

func main() {
    h := http.StripPrefix("/static/", http.FileServer(http.Dir("static")))

    // gzip response if client accepts gzip encoding
    h = gohm.WithGzip(h)

    // panic & timeout protection, error handling, and logging
    h = gohm.New(h, gohm.Config{Timeout: time.Second})

    http.Handle("/static/", h)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Here is an example with a few customizations:

const staticTimeout = time.Second // Used to control how long it takes to serve a static file.

var (
    // Will store statistics counters for status codes 1xx, 2xx, 3xx, 4xx, 5xx, as well as a
    // counter for all responses
    counters gohm.Counters

    // Used to dynamically control log level of HTTP logging.  After handler created, this must
    // be accessed using the sync/atomic package.
    logBitmask = gohm.LogStatusErrors

    // Determines HTTP log format
    logFormat = "{http-CLIENT-IP} {client-ip} [{end}] \"{method} {uri} {proto}\" {status} {bytes} {duration} {message}"
)

func main() {

    h := http.StripPrefix("/static/", http.FileServer(http.Dir("static")))

    h = gohm.WithGzip(h) // gzip response if client accepts gzip encoding

    // gohm was designed to wrap other http.Handler functions.
    h = gohm.New(h, gohm.Config{
        Counters:   &counters,   // pointer given so counters can be collected and optionally reset
        LogBitmask: &logBitmask, // pointer given so bitmask can be updated using sync/atomic
        LogFormat:  logFormat,
        LogWriter:  os.Stderr,
        Timeout:    staticTimeout,
    })

    http.Handle("/static/", h)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

In the above example notice that each successive line wraps the handler of the line above it. The terms upstream and downstream do not refer to which line was above which other line in the source code. Rather, upstream handlers invoke downstream handlers. In both of the above examples, the top level handler is gohm, which is upstream of gohm.WithGzip, which in turn is upstream of http.StripPrefix, which itself is upstream of http.FileServer, which finally is upstream of http.Dir.

As another illustration, the following two example functions are equivalent, and both invoke handlerA to perform some setup then invoke handlerB, which performs its setup work, and finally invokes handlerC. Both do the same thing, but source code looks vastly different. In both cases, handlerA is considered upstream from handlerB, which is considered upstream of handlerC. Similarly, handlerC is downstream of handlerB, which is likewise downstream of handlerA.

func example1() {
    h := handlerA(handlerB(handlerC))
}

func example2() {
    h := handlerC
    h = handlerB(h)
    h = handlerA(h)
}

Helper Functions

Error

Error formats and emits the specified error message text and status code information to the http.ResponseWriter, to be consumed by the client of the service. This particular helper function has nothing to do with emitting log messages on the server side, and only creates a response for the client. However, if a handler that invokes gohm.Error is wrapped with logging functionality by gohm.New, then gohm will also emit a sensible log message based on the specified status code and message text. Typically handlers will call this method prior to invoking return to return to whichever handler invoked it.

// example function which guards downstream handlers to ensure only HTTP GET method used to
// access resource.
func onlyGet(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method != "GET" {
            gohm.Error(w, r.Method, http.StatusMethodNotAllowed)
            // 405 Method Not Allowed: POST
            return
        }
        next.ServeHTTP(w, r)
    })
}

HTTP Handler Middleware Functions

New

New returns a new http.Handler that calls the specified next http.Handler, and performs the requested operations before and after the downstream handler as specified by the gohm.Config structure passed to it.

It receives a gohm.Config instance rather than a pointer to one, to discourage modification after creating the http.Handler. With the exception of handler timeout control, all of the configuration options have sensible defaults, so an empty gohm.Config{} object may be used to initialize the http.Handler wrapper to start, and further customization is possible down the road. Using the default handler timeout elides timeout protection, so it's recommended that timeouts are always created for production code. Documentation of the gohm.Config structure provides additional details for the supported configuration fields.

Configuration Parameters
AllowPanics

AllowPanics, when set to true, causes panics to propagate from downstream handlers. When set to false, also the default value, panics will be converted into Internal Server Errors (status code 500). You cannot change this setting after creating the http.Handler.

Counters

Counters, if not nil, tracks counts of handler response status codes.

LogBitmask

The LogBitmask parameter is used to specify which HTTP requests ought to be logged based on the HTTP status code returned by the downstream http.Handler.

LogFormat

The following format directives are supported. All times provided are converted to UTC before formatting.

begin-epoch:     time request received (epoch)
begin-iso8601:   time request received (ISO-8601 time format)
begin:           time request received (apache log time format)
bytes:           response size
client-ip:       client IP address
client-port:     client port
client:          client-ip:client-port
duration:        duration of request from beginning to end, (seconds with millisecond precision)
end-epoch:       time request completed (epoch)
end-iso8601:     time request completed (ISO-8601 time format)
end:             time request completed (apache log time format)
error:           context timeout, context closed, or panic error message
method:          request method, e.g., GET or POST
proto:           request protocol, e.g., HTTP/1.1
status:          response status code
status-text:     response status text
uri:             request URI

In addition, values from HTTP request headers can also be included in the log by prefixing the HTTP header name with http-. In the below example, each log line will begin with the value of the HTTP request header CLIENT-IP. If the specified request header is not present, a hyphen will be used in place of the non-existant value.

format := "{http-CLIENT-IP} {http-USER} [{end}] \"{method} {uri} {proto}\" {status} {bytes} {duration}"
LogWriter

LogWriter, if not nil, specifies that log lines ought to be written to the specified io.Writer. You cannot change the io.Writer to which logs are written after creating the http.Handler.

Timeout

Timeout, when not 0, specifies the amount of time allotted to wait for downstream http.Handler response. You cannot change the handler timeout after creating the http.Handler. The zero value for Timeout elides timeout protection, and gohm will wait forever for a downstream http.Handler to return. It is recommended that a sensible timeout always be chosen for all production servers.

WithGzip

WithGzip returns a new http.Handler that optionally compresses the response text using the gzip compression algorithm when the HTTP request's Accept-Encoding header includes the string gzip.

    mux := http.NewServeMux()
    mux.Handle("/example/path", gohm.WithGzip(someHandler))

Documentation

Overview

Package gohm is a tiny Go library with HTTP middleware functions.

gohm provides a small collection of middleware functions to be used when creating a HTTP micro service written in Go.

One function in particular, gohm.Error, is not used as HTTP middleware, but as a helper for emitting a sensible error message back to the HTTP client when the HTTP request could not be fulfilled. It emits a text response beginning with the status code, the human friendly status message, followed by an optional text message. It is meant to be a drop in replacement for http.Error message, that formats the error message in a more conventional way to include the status code and message.

// Example function which guards downstream handlers to ensure only HTTP GET method used
// to access resource.
func onlyGet(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			gohm.Error(w, r.Method, http.StatusMethodNotAllowed)
			// 405 Method Not Allowed: POST
			return
		}
		next.ServeHTTP(w, r)
	})
}

All the other gohm functions are HTTP middleware functions, designed to wrap any HTTP handler, composing some functionality around it. They can be interchanged and used with other HTTP middleware functions providing those functions adhere to the http.Handler interface and have a ServeHTTP(http.ResponseHandler, *http.Request) method.

mux := http.NewServeMux()
var h http.HandlerFunc = someHandler
h = gohm.WithGzip(h)
h = gohm.ConvertPanicsToErrors(h)
h = gohm.WithTimeout(globalTimeout, h)
h = gohm.LogErrors(os.Stderr, h)
mux.Handle("/static/", h)

*NOTE:* When both the WithTimeout and the ConvertPanicsToErrors are used, the WithTimeout ought to wrap the ConvertPanicsToErrors. This is because timeout handlers in Go are generally implemented using a separate goroutine, and the panic could occur in an alternate goroutine and not get caught by the ConvertPanicsToErrors.

Index

Constants

View Source
const ApacheCommonLogFormat = "{client-ip} - - [{begin}] \"{method} {uri} {proto}\" {status} {bytes}"

ApacheCommonLogFormat (CLF) is the default log line format for Apache Web Server. It is included here for users of this library that would like to easily specify log lines out to be emitted using the Apache Common Log Format (CLR), by setting `LogFormat` to `gohm.ApackeCommonLogFormat`.

View Source
const DefaultLogFormat = "{client-ip} [{begin-iso8601}] \"{method} {uri} {proto}\" {status} {bytes} {duration} {error}"

DefaultLogFormat is the default log line format used by this library.

View Source
const LogStatus1xx uint32 = 1

LogStatus1xx used to log HTTP requests which have a 1xx response

View Source
const LogStatus2xx uint32 = 2

LogStatus2xx used to log HTTP requests which have a 2xx response

View Source
const LogStatus3xx uint32 = 4

LogStatus3xx used to log HTTP requests which have a 3xx response

View Source
const LogStatus4xx uint32 = 8

LogStatus4xx used to log HTTP requests which have a 4xx response

View Source
const LogStatus5xx uint32 = 16

LogStatus5xx used to log HTTP requests which have a 5xx response

View Source
const LogStatusAll uint32 = 1 | 2 | 4 | 8 | 16

LogStatusAll used to log all HTTP requests

View Source
const LogStatusErrors uint32 = 8 | 16

LogStatusErrors used to log HTTP requests which have 4xx or 5xx response

Variables

This section is empty.

Functions

func AllowedMethodsHandler added in v2.3.0

func AllowedMethodsHandler(sortedAllowedMethods []string, next http.Handler) http.Handler

AllowedMethodsHandler returns a handler that only permits specified request methods, and responds with an error message when request method is not a member of the sorted list of allowed methods.

func CORSHandler added in v2.3.0

func CORSHandler(config CORSConfig, next http.Handler) http.Handler

CORSHandler returns a handler that responds to OPTIONS request so that CORS requests from an origin that matches the specified allowed origins regular expression are permitted, while other origins are denied. If a request origin matches the specified regular expression, the handler responds with the specified allowOriginResponse value in the "Access-Control-Allow-Origin" HTTP response header.

func Error

func Error(w http.ResponseWriter, text string, code int)

Error formats and emits the specified error message text and status code information to the http.ResponseWriter, to be consumed by the client of the service. This particular helper function has nothing to do with emitting log messages on the server side, and only creates a response for the client. However, if a handler that invokes gohm.Error is wrapped with logging functionality by gohm.New, then gohm will also emit a sensible log message based on the specified status code and message text. Typically handlers will call this method prior to invoking return to return to whichever handler invoked it.

// example function which guards downstream handlers to ensure only HTTP GET method used
// to access resource.
func onlyGet(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Method != "GET" {
				gohm.Error(w, r.Method, http.StatusMethodNotAllowed)
				// 405 Method Not Allowed: POST
				return
			}
			next.ServeHTTP(w, r)
		})
}

func New

func New(next http.Handler, config Config) http.Handler

New returns a new http.Handler that calls the specified next http.Handler, and performs the requested operations before and after the downstream handler as specified by the gohm.Config structure passed to it.

It receives a gohm.Config struct rather than a pointer to one, so users less likely to consider modification after creating the http.Handler.

 // Used to control how long it takes to serve a static file.
	const staticTimeout = time.Second

	var (
		// Will store statistics counters for status codes 1xx, 2xx, 3xx, 4xx,
		// 5xx, as well as a counter for all responses
		counters gohm.Counters

		// Used to dynamically control log level of HTTP logging. After handler
     // created, this must be accessed using the sync/atomic package.
		logBitmask = gohm.LogStatusErrors

		// Determines HTTP log format
		logFormat = "{http-CLIENT-IP} {client-ip} [{end}] \"{method} {uri} {proto}\" {status} {bytes} {duration} {message}"
	)

	func main() {

		h := http.StripPrefix("/static/", http.FileServer(http.Dir("static")))

		h = gohm.WithGzip(h) // gzip response if client accepts gzip encoding

		// gohm was designed to wrap other http.Handler functions.
		h = gohm.New(h, gohm.Config{
			Counters:   &counters,   // pointer given so counters can be collected and optionally reset
			LogBitmask: &logBitmask, // pointer given so bitmask can be updated using sync/atomic
			LogFormat:  logFormat,
			LogWriter:  os.Stderr,
			Timeout:    staticTimeout,
		})

		http.Handle("/static/", h)
		log.Fatal(http.ListenAndServe(":8080", nil))
	}

func WithCompression added in v2.4.0

func WithCompression(next http.Handler) http.Handler

WithCompression returns a new http.Handler that optionally compresses the response text using either the gzip or deflate compression algorithm when the HTTP request's `Accept-Encoding` header includes the string `gzip` or `deflate`. To prevent the specified next http.Handler from also seeing the `Accept-Encoding` request header, and possibly also compressing the data a second time, this function removes that header from the request.

NOTE: The specified next http.Handler ought not set `Content-Length` header, or the value reported will be wrong.

mux := http.NewServeMux()
mux.Handle("/example/path", gohm.WithCompression(someHandler))

func WithGzip

func WithGzip(next http.Handler) http.Handler

WithGzip returns a new http.Handler that optionally compresses the response text using the gzip compression algorithm when the HTTP request's `Accept-Encoding` header includes the string `gzip`.

NOTE: The specified next http.Handler ought not set `Content-Length` header, or the value reported will be wrong.

mux := http.NewServeMux()
mux.Handle("/example/path", gohm.WithGzip(someHandler))

func WithRequestDumper added in v2.6.0

func WithRequestDumper(flag *uint32, next http.Handler) http.Handler

WithRequestDumper wraps http.Handler and optionally dumps the request when the specified flag is non-zero. It uses atomic.LoadUnit32 to read the flag. When 0, requests will not be dumped. When 1, all but the body will be dumped. When 2, the entire request including the body will be dumped.

Types

type CORSConfig added in v2.3.0

type CORSConfig struct {
	// OriginsFilter is a regular expression that acts as a filter against the
	// "Origin" header value for pre-flight checks.
	OriginsFilter *regexp.Regexp

	// AllowHeaders is a list of HTTP header names which are allowed to be sent
	// to this handler.
	AllowHeaders []string

	// AllowMethods is a list of HTTP method names which are allowed for this
	// handler.
	AllowMethods []string

	// MaxAgeSeconds is the number of seconds used to fill the
	// "Access-Control-Max-Age" header in pre-flight check responses.
	MaxAgeSeconds int
}

CORSConfig holds parameters for configuring a CORSHandler.

type Config

type Config struct {
	// AllowPanics, when set to true, causes panics to propagate from downstream
	// handlers.  When set to false, also the default value, panics will be
	// converted into Internal Server Errors (status code 500).  You cannot
	// change this setting after creating the http.Handler.
	AllowPanics bool

	// Counters, if not nil, tracks counts of handler response status codes.
	Counters *Counters

	// LogBitmask, if not nil, specifies a bitmask to use to determine which
	// HTTP status classes ought to be logged.  If not set, all HTTP requests
	// will be logged.  This value may be changed using sync/atomic package even
	// after creating the http.Handler.
	//
	// The following bitmask values are supported:
	//
	//	LogStatus1xx    : LogStatus1xx used to log HTTP requests which have a 1xx response
	//	LogStatus2xx    : LogStatus2xx used to log HTTP requests which have a 2xx response
	//	LogStatus3xx    : LogStatus3xx used to log HTTP requests which have a 3xx response
	//	LogStatus4xx    : LogStatus4xx used to log HTTP requests which have a 4xx response
	//	LogStatus5xx    : LogStatus5xx used to log HTTP requests which have a 5xx response
	//	LogStatusAll    : LogStatusAll used to log all HTTP requests
	//	LogStatusErrors : LogStatusAll used to log HTTP requests which have 4xx or 5xx response
	LogBitmask *uint32

	// LogFormat specifies the format for log lines.  When left empty,
	// gohm.DefaultLogFormat is used.  You cannot change the log format after
	// creating the http.Handler.
	//
	// The following format directives are supported:
	//
	//	begin-epoch     : time request received (epoch)
	//	begin-iso8601   : time request received (ISO-8601 time format)
	//	begin           : time request received (apache log time format)
	//	bytes           : response size
	//	client-ip       : client IP address
	//	client-port     : client port
	//	client          : client-ip:client-port
	//	duration        : duration of request from beginning to end, (seconds with millisecond precision)
	//	end-epoch       : time request completed (epoch)
	//	end-iso8601     : time request completed (ISO-8601 time format)
	//	end             : time request completed (apache log time format)
	//	error           : error message associated with attempting to serve the query
	//	method          : request method, e.g., GET or POST
	//	proto           : request protocol, e.g., HTTP/1.1
	//	status          : response status code
	//	status-text     : response status text
	//	uri             : request URI
	LogFormat string

	// LogWriter, if not nil, specifies that log lines ought to be written to
	// the specified io.Writer.  You cannot change the io.Writer to which logs
	// are written after creating the http.Handler.
	LogWriter io.Writer

	// `Timeout`, when not 0, specifies the amount of time allotted to wait for
	// downstream `http.Handler` response.  You cannot change the handler
	// timeout after creating the `http.Handler`.  The zero value for Timeout
	// elides timeout protection, and `gohm` will wait forever for a downstream
	// `http.Handler` to return.  It is recommended that a sensible timeout
	// always be chosen for all production servers.
	Timeout time.Duration
}

Config specifies the parameters used for the wrapping the downstream http.Handler.

type Counters

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

Counters structure store status counters used to track number of HTTP responses resulted in various status classes.

var counters gohm.Counters
mux := http.NewServeMux()
mux.Handle("/example/path", gohm.New(someHandler, gohm.Config{Counters: &counters}))
// later on...
countOf1xx := counters.Get1xx()
countOf2xx := counters.Get2xx()
countOf3xx := counters.Get3xx()
countOf4xx := counters.Get4xx()
countOf5xx := counters.Get5xx()
countTotal := counters.GetAll()

func (Counters) Get1xx

func (c Counters) Get1xx() uint64

Get1xx returns number of HTTP responses resulting in a 1xx status code.

func (Counters) Get2xx

func (c Counters) Get2xx() uint64

Get2xx returns number of HTTP responses resulting in a 2xx status code.

func (Counters) Get3xx

func (c Counters) Get3xx() uint64

Get3xx returns number of HTTP responses resulting in a 3xx status code.

func (Counters) Get4xx

func (c Counters) Get4xx() uint64

Get4xx returns number of HTTP responses resulting in a 4xx status code.

func (Counters) Get5xx

func (c Counters) Get5xx() uint64

Get5xx returns number of HTTP responses resulting in a 5xx status code.

func (Counters) GetAll

func (c Counters) GetAll() uint64

GetAll returns total number of HTTP responses, regardless of status code.

func (Counters) GetAndReset1xx

func (c Counters) GetAndReset1xx() uint64

GetAndReset1xx returns number of HTTP responses resulting in a 1xx status code, and resets the counter to 0.

func (Counters) GetAndReset2xx

func (c Counters) GetAndReset2xx() uint64

GetAndReset2xx returns number of HTTP responses resulting in a 2xx status code, and resets the counter to 0.

func (Counters) GetAndReset3xx

func (c Counters) GetAndReset3xx() uint64

GetAndReset3xx returns number of HTTP responses resulting in a 3xx status code, and resets the counter to 0.

func (Counters) GetAndReset4xx

func (c Counters) GetAndReset4xx() uint64

GetAndReset4xx returns number of HTTP responses resulting in a 4xx status code, and resets the counter to 0.

func (Counters) GetAndReset5xx

func (c Counters) GetAndReset5xx() uint64

GetAndReset5xx returns number of HTTP responses resulting in a 5xx status code, and resets the counter to 0.

func (Counters) GetAndResetAll

func (c Counters) GetAndResetAll() uint64

GetAndResetAll returns number of HTTP responses resulting in a All status code, and resets the counter to 0.

Jump to

Keyboard shortcuts

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