wrap

package module
v0.0.0-...-d1ac8ae Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2018 License: MIT Imports: 10 Imported by: 14

README

wrap

Package wrap creates a fast and flexible middleware stack for http.Handlers.

Build Status Coverage Status GoDoc Total views

Features

  • small; core is only 13 LOC
  • based on http.Handler interface; integrates fine with net/http
  • middleware stacks are http.Handlers too and may be embedded
  • has a solution for per request context sharing
  • freely mix middleware with and without context (same interface)
  • has debugging helper
  • low memory footprint
  • fast
  • easy to create adapters / wrappers for 3rd party middleware

How does it work

Import it via gopkg.in: import "gopkg.in/go-on/wrap.v2".

wrap.New(w ...Wrapper) creates a stack of middlewares. Wrapper is defined as

type Wrapper interface {
    Wrap(next http.Handler) (previous http.Handler)
}

Each wrapper wraps the the http.Handler that comes further down the middleware stack and returns a http.Handler that handles the request previously.

Examples

See example_test.go for a simple example without context and example_context_test.go for an example with context sharing.

Also look into the repository of blessed middleware github.com/go-on/wrap-contrib/wraps.

Middleware

more examples and middleware and can be found at github.com/go-on/wrap-contrib

Router

A router that is also tested but may change, can be found at github.com/go-on/router

Benchmarks (go 1.3)

// The overhead of n writes to http.ResponseWriter via n wrappers
// vs n writes in a loop within a single http.Handler

BenchmarkServing2Simple     1000000 1067    ns/op   1,00x
BenchmarkServing2Wrappers   1000000 1121    ns/op   1,05x

BenchmarkServing50Simple    100000  26041   ns/op   1,00x
BenchmarkServing50Wrappers  100000  27053   ns/op   1,04x

BenchmarkServing100Simple   50000   52074   ns/op   1,00x
BenchmarkServing100Wrappers 50000   53450   ns/op   1,03x

Credits

Initial inspiration came from Christian Neukirchen's rack for ruby some years ago.

Documentation

Overview

Package wrap creates a fast and flexible middleware stack for http.Handlers.

Each middleware is a wrapper for another middleware and implements the Wrapper interface.

Features

  • small; core is only 13 LOC
  • based on http.Handler interface; nicely integrates with net/http
  • middleware stacks are http.Handlers too and may be embedded
  • has a solution for per request context sharing
  • has debugging helper
  • low memory footprint
  • fast
  • no dependency apart from the standard library
  • freely mix middleware with and without context (same interface)
  • easy to create adapters / wrappers for 3rd party middleware

Wrappers can be found at http://godoc.org/github.com/go-on/wrap-contrib/wraps.

A (mountable) router that plays fine with wrappers can be found at http://godoc.org/github.com/go-on/router.

Benchmarks (Go 1.3)

	// The overhead of n writes to http.ResponseWriter via n wrappers
	// vs n writes in a loop within a single http.Handler

  BenchmarkServing2Simple     1000000 1067    ns/op   1,00x
  BenchmarkServing2Wrappers   1000000 1121    ns/op   1,05x

  BenchmarkServing50Simple    100000  26041   ns/op   1,00x
  BenchmarkServing50Wrappers  100000  27053   ns/op   1,04x

  BenchmarkServing100Simple   50000   52074   ns/op   1,00x
  BenchmarkServing100Wrappers 50000   53450   ns/op   1,03x

Credits

Initial inspiration came from Christian Neukirchen's rack for ruby some years ago.

Content of the package

The core of this package is the New function that constructs a stack of middlewares that implement the Wrapper interface.

If the global DEBUG flag is set before calling New then each middleware call will result in calling the Debug method of the global DEBUGGER (defaults to a logger).

To help constructing middleware there are some adapters like WrapperFunc, Handler, HandlerFunc, NextHandler and NextHandlerFunc each of them adapting to the Wrapper interface.

To help sharing per request context there is a Contexter interface that must be implemented by the ResponseWriter. That can easily be done be providing a middleware that injects a context that wraps the current ResponseWriter and implements the Contexter interface. It must a least support the extraction of the wrapped ResponseWriter.

Then there are functions to validate implementations of Contexter (ValidateContextInjecter) and to validate them against wrappers that store and retrieve the context data (ValidateWrapperContexts).

An complete example for shared contexts can be found in the file example_context_test.go.

Furthermore this package provides some ResponseWriter wrappers that also implement Contexter and that help with development of middleware.

These are Buffer, Peek and EscapeHTML.

Buffer is a simple buffer. A middleware may pass it to the next handlers ServeHTTP method as a drop in replacement for the response writer. After the ServeHTTP method is run the middleware may examine what has been written to the Buffer and decide what to write to the "original" ResponseWriter (that may well be another buffer passed from another middleware).

The downside is the body being written two times and the complete caching of the body in the memory which will be inacceptable for large bodies.

Therefor Peek is an alternative response writer wrapper that only caching headers and status code but allowing to intercept calls of the Write method. All middleware without the need to read the whole response body should use Peek or provide their own ResponseWriter wrapper (then do not forget to implement the Contexter interface).

Finally EscapeHTML provides a response writer wrapper that allows on the fly html escaping of the bytes written to the wrapped response writer.

How to write a middleware

It is pretty easy to write your custom middleware. You should start with a new struct type - that allows you to add options as fields later on.

Then you could use the following template to implement the Wrapper interface

    type MyMiddleware struct {
	     // add your options
    }

    // make sure it conforms to the Wrapper interface
    var _ wrap.Wrapper = MyMiddleware{}

    // implement the wrapper interface
    func (m MyMiddleware) Wrap( next http.Handler) http.Handler {
	     var f http.HandlerFunc
	     f = func (rw http.ResponseWriter, req *http.Request) {
	        // do stuff

	        // at some point you might want to run the next handler
	        // if not, your middleware ends the stack chain
	        next.ServeHTTP(rw, req)
	     }
	     return f
    }

If you need to run the next handler in order to inspect what it did, replace the response writer with a Peek (see NewPeek) or if you need full access to the written body with a Buffer.

How to use middleware

To form a middleware stack, simply use New() and pass it the middlewares. They get the request top-down. There are some adapters to let for example a http.Handler be a middleware (Wrapper) that does not call the next handler and stops the chain.

stack := wrap.New(
    MyMiddleware{},
    OtherMiddleware{},
    wrap.Handler(aHandler),
)

// stack is now a http.Handler

How to write a middleware that uses per request context

To use per request context a custom type is needed that carries the context data and the user is expected to create and inject a Contexter supporting this type.

Here is a template for a middleware that you could use to write middleware that wants to use / share context.

// MyMiddleware expects the ResponseWriter to implement wrap.Contexter and
// to support storing and retrieving the MyContextData type.
type MyMiddleware struct {
   // add your options
}

// define whatever type you like, but define a type
// for each kind of context data you will want to store/retrieve
type MyContextData string

// make sure it conforms to the ContextWrapper interface
var _ wrap.ContextWrapper = MyMiddleware{}

// implements ContextWrapper; panics if Contexter does not support
// the needed type
func (m MyMiddleware) ValidateContext( ctx wrap.Contexter ) {
  var m MyContextData
  // try the getter and setter, they will panic if they don't support the type
  ctx.Context(&m); ctx.SetContext(&m)
  // do this for every type you need
}

// implement the wrapper interface
func (m MyMiddleware) Wrap( next http.Handler) http.Handler {
   var f http.HandlerFunc
   f = func (rw http.ResponseWriter, req *http.Request) {

      ctx := rw.(wrap.Contexter)
      m := MyContextData("Hello World")
      ctx.SetContext(&m) // always pass the pointer

      var n MyContextData
      ctx.Context(&n)

      // n now is MyContextData("Hello World")

      ... do stuff
      next.ServeHTTP(rw, req)
   }
   return f
}

How to use middleware that uses per request context

For context sharing the user has to implement the Contexter interface in a way that supports all types the used middlewares expect.

Here is a template for an implementation of the Contexter interface

type MyContext struct {
  http.ResponseWriter // you always need this
  myContextData *myMiddleware.MyContextData // a property for each supported type
}

// make sure it is a valid context, i.e. http.ResponseWriter is supported by Context
// method, the correct types are returned and the panic types are correct
var _ = wrap.ValidateContextInjecter(&MyContext{})

// retrieves the value of the type to which ctxPtr is a pointer to
func (c *MyContext) Context(ctxPtr interface{}) (found bool) {
  found = true // save work
  switch ty := ctxPtr.(type) {
  // always support http.ResponseWriter in Context method
  case *http.ResponseWriter:
    *ty = c.ResponseWriter
  // add such a case for each supported type
  case *myMiddleware.MyContextData:
    if c.myContextData == nil {
      return false
    }
    *ty = *c.myContextData
  default:
    // always panic with wrap.ErrUnsupportedContextGetter in Context method on default
    panic(&wrap.ErrUnsupportedContextGetter{ctxPtr})
  }
  return
}

// sets the context of the given type
func (c *MyContext) SetContext(ctxPtr interface{}) {
  switch ty := ctxPtr.(type) {
  case *myMiddleware.MyContextData:
    c.myContextData = ty
  default:
    // always panic with wrap.ErrUnsupportedContextSetter in SetContext method on default
    panic(&wrap.ErrUnsupportedContextSetter{ctxPtr})
  }
}

// Wrap implements the wrap.Wrapper interface by wrapping a ResponseWriter inside a new
// &MyContext and injecting it into the middleware chain.
func (c MyContext) Wrap(next http.Handler) http.Handler {
  var f http.HandlerFunc
  f = func(rw http.ResponseWriter, req *http.Request) {
    next.ServeHTTP(&MyContext{ResponseWriter: rw}, req)
  }
  return f
}

At any time there must be only one Contexter in the whole middleware stack and its the best to let it be the first middleware. Then you don't have to worry if its there or not (the Stack function might help you).

The corresponding middleware stack would look like this

// first check if the Contexter supports all context types needed by the middlewares
// this uses the ValidateContext() method of the middlewares that uses context.
// It panics on errors.
wrap.ValidateWrapperContexts(&MyContext{}, MyMiddleware{}, OtherMiddleware{})

stack := wrap.New(
    MyContext{}, // injects the &MyContext{} wrapper, should be done at the beginning
    MyMiddleware{},
    OtherMiddleware{},
    wrap.Handler(aHandler),
)

// stack is now a http.Handler

If your application / handler also uses context data, it is a good idea to implement it as ContextWrapper as if it were a middleware and pass it ValidateWrapperContexts(). So if your context is wrong, you will get nice panics before your server even starts. And this is always in sync with your app / middleware.

If for some reason the original ResponseWriter is needed (to type assert it to a http.Flusher for example), it may be reclaimed with the help of ReclaimResponseWriter().

You might want to look at existing middlewares to get some ideas: http://godoc.org/github.com/go-on/wrap-contrib/wraps

FAQ

1. Should the context not better be an argument to a middleware function, to make this dependency visible in documentation and tools?

Answer: A unified interface gives great freedom when organizing and ordering middleware as the middleware in between does not have to know about a certain context type if does not care about it. It simply passes the ResponseWriter that happens to have context down the middleware stack chain.

On the other hand, exposing the context type by making it a parameter has the effect that every middleware will need to pass this parameter to the next one if the next one needs it. Every middleware is then tightly coupled to the next one and reordering or mixing of middleware from different sources becomes impossible.

However with the ContextHandler interface and the ValidateWrapperContexts function we have a way to guarantee that the requirements are met. And this solution is also type safe.

2. A ResponseWriter is an interface, because it may implement other interfaces from the http libary, e.g. http.Flusher. If it is wrapped that underlying implementation is not accessible anymore

Answer: When the Contexter is validated, it is checked, that the Context method supports http.ResponseWriter as well (and that it returns the underlying ResponseWriter). Since only one Contexter may be used within a stack, it is always possible to ask the Contexter for the underlying ResponseWriter. This is what helper functions like ReclaimResponseWriter(), Flush(), CloseNotify() and Hijack() do.

3. Why is the recommended way to use the Contexter interface to make a type assertion from the ResponseWriter to the Contexter interface without error handling?

Answer: Middleware stacks should be created before the server is handling requests and then not change anymore. And you should run tests. Then the assertion will blow up and that is correct because there is no reasonable way to handle such an error. It means that either you have no context in your stack or you inject the context to late or the context does not handle the kind of type the middleware expects. In each case you should fix it early and the panic forces you to do. Use the DEBUG flag to see what's going on.

4. What happens if my context is wrapped inside another context or response writer?

Answer: You should only have one Contexter per application and inject it as first wrapper into your middleware stack. All context specific data belongs there. Having multiple Contexter in a stack is considered a bug.

The good news is that you now can be sure to be able to access all of your context data everywhere inside your stack.

Never should a context wrap another one. There also should be no need for another context wrapping ResponseWriter because every type can be saved by a Contexter with few code.

All response writer wrappers of this package implement the Contexter interface and every response writer you use should.

5. Why isn't there any default context object? Why do I have to write it on my own?

Answer: To write your own context and context injection has several benefits:

  • no magic or reflection necessary
  • all your context data is managed at one place
  • context data may be generated/updated based on other context data
  • your context management is independant from the middleware

6. Why is the context data accessed and stored via type switch and not by string keys?

Answer: Type based context allows a very simple implementation that namespaces across packages needs no extra memory allocation and is, well, type safe. If you need to store multiple context data of the same type, simple defined an alias type for each key.

7. Is there an example how to integrate with 3rd party middleware libraries that expect context?

Answer: Yes, have a look at http://godoc.org/github.com/go-on/wrap-contrib/third-party.

8. What about spawning goroutines / how does it relate to code.google.com/p/go.net/context?

If you need your request to be handled by different goroutines and middlewares you might use your Contexter to store and provide access to a code.google.com/p/go.net/context Context just like any other context data. The good news is that you can write you middleware like before and extract your Context everywhere you need it. And if you have middleware in between that does not know about about Context or other context data you don't have build wrappers around that have Context as parameter. Instead the ResponseWriter that happens to provide you a Context will be passed down the chain by the middleware.

"At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests."

(Sameer Ajmani, http://blog.golang.org/context)

This is not neccessary anymore. And it is not neccessary for any type of contextual data because that does not have to be in the type signature anymore.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DEBUG = false

DEBUG indicates if any stack should be debugged. Set it before any call to New.

View Source
var DEBUGGER = Debugger(&logDebugger{log.New(os.Stdout, "[go-on/wrap debugger]", log.LstdFlags)})

DEBUGGER is the Debugger used for debugging middleware stacks. It defaults to a logging debugger that logs to os.Stdout

NoOp is a http.Handler doing nothing

Functions

func CloseNotify

func CloseNotify(rw http.ResponseWriter) (ch <-chan bool, ok bool)

CloseNotify is the same for http.CloseNotifier as Flush is for http.Flusher ok tells if it was a CloseNotifier

func Flush

func Flush(rw http.ResponseWriter) (ok bool)

Flush is a helper that flushes the buffer in the underlying response writer if it is a http.Flusher. The http.ResponseWriter might also be a Contexter if it allows the retrieval of the underlying ResponseWriter. Ok returns if the underlying ResponseWriter was a http.Flusher

func Hijack

func Hijack(rw http.ResponseWriter) (c net.Conn, brw *bufio.ReadWriter, err error, ok bool)

Hijack is the same for http.Hijacker as Flush is for http.Flusher ok tells if it was a Hijacker

func New

func New(wrapper ...Wrapper) (h http.Handler)

New returns the wrapping http.Handler that returned from calling the Wrap method on the first given wrapper that received the returned handler of returning from the second wrappers Wrap method and so on.

The last wrapper begins the loop receiving the NoOp handler.

When the ServeHTTP method of the returned handler is called each wrapping handler may call is next until the NoOp handler is run.

Or some wrapper decides not to call next.ServeHTTP.

If DEBUG is set, each handler is wrapped with a Debug struct that calls DEBUGGER.Debug before running the handler.

Example
package main

import (
	"fmt"
	"net/http"
)

/*
This example illustrates 3 ways to write and use middleware.

1. use a http.Handler as wrapper (needs adapter in the middleware stack)
2. use a ServeHTTPNext method (nice to write but needs adapter in the middleware stack)
3. use a Wrapper (needs no adapter)

For sharing context, look at example_context_test.go.
*/

type print1 string

func (p print1) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
	fmt.Println(p)
}

func (p print1) ServeHTTPNext(next http.Handler, wr http.ResponseWriter, req *http.Request) {
	fmt.Print(p)
	next.ServeHTTP(wr, req)
}

type print2 string

func (p print2) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		fmt.Print(p)
		next.ServeHTTP(rw, req)
	}
	return f
}

func main() {
	h := New(
		NextHandler(print1("ready...")), // make use of ServeHTTPNext method
		print2("steady..."),             // print2 directly fulfills Wrapper interface
		Handler(print1("go!")),          // make use of ServeHTTP method, this stopps the chain
		// if there should be a handler after this, you will need the Before wrapper from go-on/wrap-contrib/wraps
	)
	r, _ := http.NewRequest("GET", "/", nil)
	h.ServeHTTP(nil, r)

}
Output:

ready...steady...go!

func NewLogDebugger

func NewLogDebugger(out io.Writer, flag int)

NewLogDebugger sets the DEBUGGER to a logger that logs to the given io.Writer. Flag is a flag from the log standard library that is passed to log.New

func ReclaimResponseWriter

func ReclaimResponseWriter(rw http.ResponseWriter) http.ResponseWriter

ReclaimResponseWriter is a helper that expects the given ResponseWriter to either be the original ResponseWriter or a Contexter which supports getting the original response writer via *http.ResponseWriter. In either case it returns the underlying response writer

func SetDebug

func SetDebug() bool

SetDebug provides a way to set DEBUG=true in a var declaration, like

var _ = wrap.SetDebug()

This is an easy way to ensure DEBUG is set to true before the init functions run

func Stack

func Stack(inject ContextInjecter, wrapper ...Wrapper) (h http.Handler)

Stack creates a stack of middlewares with a context that is injected via inject. After validating the ContextInjecter it adds it at first middleware into the stack and returns the stack built by New This has the effect that the context is injected into the middleware chain at the beginning and every middleware may type assert the ResponseWriter to a Contexter in order to get and set context. Stack panics if inject is not valid. Stack should only be called once per application and must not be embedded into other stacks

func ValidateContextInjecter

func ValidateContextInjecter(inject ContextInjecter) bool

ValidateContextInjecter panics if inject does not inject a Contexter that supports http.ResponseWriter,otherwise it returns true, so you may use it in var declarations that are executed before the init functions

func ValidateWrapperContexts

func ValidateWrapperContexts(ctx Contexter, wrapper ...Wrapper)

ValidateWrapperContexts validates the given Contexter against all of the given wrappers that implement the ContextWrapper interface. If every middleware that requires context implements the ContextWrapper interface and is passed to this function, then any missing support for a context type needed by a Wrapper would be uncovered. If then this function is called early it would save many headaches.

Types

type Buffer

type Buffer struct {

	// ResponseWriter is the underlying response writer that is wrapped by Buffer
	http.ResponseWriter

	// Buffer is the underlying io.Writer that buffers the response body
	Buffer bytes.Buffer

	// Code is the cached status code
	Code int
	// contains filtered or unexported fields
}

Buffer is a ResponseWriter wrapper that may be used as buffer.

func NewBuffer

func NewBuffer(w http.ResponseWriter) (bf *Buffer)

NewBuffer creates a new Buffer by wrapping the given response writer.

func (*Buffer) Body

func (bf *Buffer) Body() []byte

Body returns the bytes of the underlying buffer (that is meant to be the body of the response)

func (*Buffer) BodyString

func (bf *Buffer) BodyString() string

BodyString returns the string of the underlying buffer (that is meant to be the body of the response)

func (*Buffer) Context

func (bf *Buffer) Context(ctxPtr interface{}) bool

Context gets the context of the underlying response writer. It panics if the underlying response writer does no implement Contexter

func (*Buffer) FlushAll

func (bf *Buffer) FlushAll()

FlushAll flushes headers, status code and body to the underlying ResponseWriter, if something changed

func (*Buffer) FlushCode

func (bf *Buffer) FlushCode()

FlushCode flushes the status code to the underlying responsewriter if it was set.

func (*Buffer) FlushHeaders

func (bf *Buffer) FlushHeaders()

FlushHeaders adds the headers to the underlying ResponseWriter, removing them from Buffer.

func (*Buffer) HasChanged

func (bf *Buffer) HasChanged() bool

HasChanged returns true if Header, WriteHeader or Write has been called

func (*Buffer) Header

func (bf *Buffer) Header() http.Header

Header returns the cached http.Header and tracks this call as change

func (*Buffer) IsOk

func (bf *Buffer) IsOk() bool

IsOk returns true if the cached status code is not set or in the 2xx range.

func (*Buffer) Reset

func (bf *Buffer) Reset()

Reset set the Buffer to the defaults

func (*Buffer) SetContext

func (bf *Buffer) SetContext(ctxPtr interface{})

SetContext sets the Context of the underlying response writer. It panics if the underlying response writer does no implement Contexter

func (*Buffer) Write

func (bf *Buffer) Write(b []byte) (int, error)

Write writes to the underlying buffer and tracks this call as change

func (*Buffer) WriteHeader

func (bf *Buffer) WriteHeader(i int)

WriteHeader writes the cached status code and tracks this call as change

type ContextInjecter

type ContextInjecter interface {

	// Contexter interface must be implemented on a pointer receiver of the struct
	Contexter

	// Wrapper interface might be implemented on the struct itself
	Wrapper
}

ContextInjecter injects itself as Contexter into a middleware stack via its Wrapper interface

type ContextWrapper

type ContextWrapper interface {
	Wrapper

	// ValidateContext should panic if the given Contexter does not support the required
	// types
	ValidateContext(Contexter)
}

ContextWrapper is a Wrapper that uses some kind of context It has a Validate() method that panics if the contexts does not support the types that the Wrapper wants to store/retrieve inside the context

type Contexter

type Contexter interface {

	// since implementations of Context should be a wrapper around a responsewriter
	// they must implement the http.ResponseWriter interface.
	http.ResponseWriter

	// Context lets the given pointer point to the saved context of the same type
	// Returns if it has found something
	// It should always support *http.ResponseWriter and set it to the underlying
	// http.ResponseWriter in order to allow middleware to type assert to Flusher et. al.
	Context(ctxPtr interface{}) (found bool)

	// SetContext saves the given context pointer via type switch
	SetContext(ctxPtr interface{})
}

Contexter is a http.ResponseWriter that can set and get contexts. It allows plain http.Handlers to share per request context data without global state.

Implementations of Context should be structs wrapping a ResponseWriter.

Example
package main

import (
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"strings"
)

// userIP represents the IP address of a http.Request
type userIP net.IP

// context implements Contexter, providing a userIP and a error
// also implements ContextInjecter to inject itself into the middleware chain
type context struct {
	http.ResponseWriter
	userIP userIP
	err    error
}

// make sure to fulfill the ContextInjecter interface
var _ ContextInjecter = &context{}
var _ = ValidateContextInjecter(&context{})

// context is an implementation for the Contexter interface.
//
// It receives a pointer to a value that is already stored inside the context.
// Values are distiguished by their type.
// Context sets the value of the given pointer to the value of the same type
// that is stored inside of the context.
// A pointer type that is not supported results in a panic.
// *http.ResponseWriter should always be supported in order to get the underlying ResponseWriter
// Context returns if the pointer is no nil pointer when returning.
func (c *context) Context(ctxPtr interface{}) (found bool) {
	found = true // save work
	switch ty := ctxPtr.(type) {
	case *http.ResponseWriter:
		*ty = c.ResponseWriter
	case *userIP:
		if c.userIP == nil {
			return false
		}
		*ty = c.userIP
	case *error:
		if c.err == nil {
			return false
		}
		*ty = c.err
	default:
		panic(&ErrUnsupportedContextGetter{ctxPtr})
	}
	return
}

// SetContext is an implementation for the Contexter interface.
//
// It receives a pointer to a value that will be stored inside the context.
// Values are distiguished by their type, that means that SetContext replaces
// and stored value of the same type.
// A pointer type that is not supported results in a panic.
// Supporting the replacement of the underlying response writer is not recommended.
func (c *context) SetContext(ctxPtr interface{}) {
	switch ty := ctxPtr.(type) {
	case *userIP:
		c.userIP = *ty
	case *error:
		c.err = *ty
	default:
		panic(&ErrUnsupportedContextSetter{ctxPtr})
	}
}

// Wrap implements the wrap.Wrapper interface.
//
// When the request is served, the response writer is wrapped by a
// new *context which is passed to the next handlers ServeHTTP method.
func (c context) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		next.ServeHTTP(&context{ResponseWriter: rw}, req)
	}
	return f
}

// setUserIP is a middleware that requires a context supporting the userIP and the error type
type setUserIP struct{}

var _ ContextWrapper = setUserIP{}

// ValidateContext makes sure that ctx supports the needed types
func (setUserIP) ValidateContext(ctx Contexter) {
	var userIP userIP
	var err error
	// since SetContext should panic for unsupported types,
	// this should be enough
	ctx.SetContext(&userIP)
	ctx.SetContext(&err)
}

func (setUserIP) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		ip, err := ipfromRequest(req)
		if err != nil {
			rw.(Contexter).SetContext(&err)
		} else {
			uIP := userIP(ip)
			rw.(Contexter).SetContext(&uIP)
		}
		next.ServeHTTP(rw, req)
	}
	return f
}

// ipfromRequest extracts the user IP address from req, if present.
// taken from http://blog.golang.org/context/userip/userip.go (FromRequest)
func ipfromRequest(req *http.Request) (net.IP, error) {
	s := strings.SplitN(req.RemoteAddr, ":", 2)
	userIP := net.ParseIP(s[0])
	if userIP == nil {
		return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
	}
	return userIP, nil
}

// handleError is a middleware for handling errors.
// it requires a context supporting the error type.
type handleError struct{}

var _ ContextWrapper = handleError{}

// Validate makes sure that ctx supports the needed types
func (handleError) ValidateContext(ctx Contexter) {
	var err error
	// since Context should panic for unsupported types,
	// this should be enough
	ctx.Context(&err)
}

// Wrap implements the wrap.Wrapper interface and checks for an error context.
// If it finds one, the status 500 is set and the error is written to the response writer.
// If no error is inside the context, the next handler is called.
func (handleError) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		var err error
		rw.(Contexter).Context(&err)
		if err != nil {
			rw.WriteHeader(http.StatusInternalServerError)
			rw.Write([]byte(err.Error()))
			return
		}
		next.ServeHTTP(rw, req)
	}
	return f
}

// app gets the userIP and writes it to the responsewriter. it requires  a context supporting the userIP
type app struct{}

var _ ContextWrapper = app{}

// Validate makes sure that ctx supports the needed types
func (app) ValidateContext(ctx Contexter) {
	var uIP userIP
	// since Context should panic for unsupported types,
	// this should be enough
	ctx.Context(&uIP)
}

// Wrap implements the wrap.Wrapper interface and writes a userIP from a context to the response writer, flushes
// it and prints DONE
func (app) Wrap(next http.Handler) http.Handler {
	var f http.HandlerFunc
	f = func(rw http.ResponseWriter, req *http.Request) {
		var uIP userIP
		rw.(Contexter).Context(&uIP)
		fmt.Fprintln(rw, net.IP(uIP).String())
		Flush(rw)
		fmt.Fprint(rw, "DONE")
	}
	return f
}

func main() {
	ctx := &context{}

	// make sure, the context supports all types required by the used middleware
	ValidateWrapperContexts(ctx, setUserIP{}, handleError{}, app{})

	// Stack checks if context is valid (support http.ResponseWriter)
	// and creates a top level middleware stack (for embedded ones use New())
	h := Stack(
		ctx, // context must always be the first one
		setUserIP{},
		handleError{},
		app{},
	)
	rec := httptest.NewRecorder()
	r, _ := http.NewRequest("GET", "/", nil)
	r.RemoteAddr = "garbage"
	h.ServeHTTP(rec, r)
	fmt.Println(rec.Body.String())

	rec.Body.Reset()
	r.RemoteAddr = "127.0.0.1:45643"
	h.ServeHTTP(rec, r)
	fmt.Println(rec.Body.String())

}
Output:

userip: "garbage" is not IP:port
127.0.0.1
DONE

type Debugger

type Debugger interface {
	// Debug receives the current request, the object that wraps and the role in which
	// the object acts. Role is a string representing the interface in which obj
	// is used, e.g. "Wrapper", "http.Handler" and so on
	Debug(req *http.Request, obj interface{}, role string)
}

Debugger has a Debug method to debug middleware stacks

type ErrBodyFlushedBeforeCode

type ErrBodyFlushedBeforeCode struct{}

ErrBodyFlushedBeforeCode is the error returned if a body flushed to an underlying response writer before the status code has been flushed. It should help to sort out errors in middleware that uses responsewriter wrappers from this package.

func (ErrBodyFlushedBeforeCode) Error

func (e ErrBodyFlushedBeforeCode) Error() string

Error returns the error message

type ErrCodeFlushedBeforeHeaders

type ErrCodeFlushedBeforeHeaders struct{}

ErrCodeFlushedBeforeHeaders is the error returned if a status code flushed to an underlying response writer before the headers have been flushed. It should help to sort out errors in middleware that uses responsewriter wrappers from this package.

func (ErrCodeFlushedBeforeHeaders) Error

Error returns the error message

type ErrUnsupportedContextGetter

type ErrUnsupportedContextGetter struct {
	Type interface{}
}

ErrUnsupportedContextGetter is the error returned if the context type is not supported by the Context() method of a Contexter

func (*ErrUnsupportedContextGetter) Error

type ErrUnsupportedContextSetter

type ErrUnsupportedContextSetter struct {
	Type interface{}
}

ErrUnsupportedContextSetter is the error returned if the context type is not supported by the SetContext() method of a Contexter

func (*ErrUnsupportedContextSetter) Error

type EscapeHTML

type EscapeHTML struct {
	http.ResponseWriter
}

EscapeHTML wraps an http.ResponseWriter in order to override its Write method so that it escape html special chars while writing

func (*EscapeHTML) Context

func (e *EscapeHTML) Context(ctxPtr interface{}) bool

Context gets the Context of the underlying response writer. It panics if the underlying response writer does no implement Contexter

func (*EscapeHTML) SetContext

func (e *EscapeHTML) SetContext(ctxPtr interface{})

SetContext sets the Context of the underlying response writer. It panics if the underlying response writer does no implement Contexter

func (*EscapeHTML) Write

func (e *EscapeHTML) Write(b []byte) (num int, err error)

Write writes to the inner *http.ResponseWriter escaping html special chars on the fly Since there is nothing useful to do with the number of bytes written returned from the inner responsewriter, the returned int is always 0. Since there is nothing useful to do in case of a failed write to the response writer, writing errors are silently dropped. the method is modelled after EscapeText from encoding/xml

type NextHandlerFunc

type NextHandlerFunc func(next http.Handler, rw http.ResponseWriter, req *http.Request)

NextHandlerFunc is a Wrapper that is a function handling the request with the aid of the given handler

func (NextHandlerFunc) Wrap

func (f NextHandlerFunc) Wrap(next http.Handler) http.Handler

Wrap implements the Wrapper interface by calling the function.

type Peek

type Peek struct {
	// the cached status code
	Code int

	// the underlying response writer
	http.ResponseWriter
	// contains filtered or unexported fields
}

Peek is a ResponseWriter wrapper that intercepts the writing of the body, allowing to check headers and status code that has been set to prevent the body writing and to write a modified body.

Peek is more efficient than Buffer, since it does not write to a buffer first before determining if the body will be flushed to the underlying response writer.

func NewPeek

func NewPeek(rw http.ResponseWriter, proceed func(*Peek) bool) *Peek

NewPeek creates a new Peek for the given response writer using the given proceed function.

The proceed function is called when the Write method is run for the first time. It receives the Peek and may check the cached headers and the cached status code.

If the cached headers and the cached status code should be flushed to the underlying response writer, the proceed function must do so (e.g. by calling FlushMissing). This also allows to write other headers status codes or not write them at all.

If the proceed function returns true, the body will be written to the underlying response write. That also holds for all following calls of Write when proceed is not run anymore.

To write some other body or no body at all, proceed must return false, Then after the Write method has been run the Peek might be checked again and the underlying ResponseWriter that is exposed by Peek might be used to write a custom body.

However if the http.Handler that receives the Peek does not write to the body, proceed will not be called at all.

To ensure that any cached headers and status code will be flushed, the FlushMissing method can be called after the serving http.Handler is run.

If proceed is nil, Write behaves as if proceed would have returned true.

func (*Peek) Context

func (p *Peek) Context(ctxPtr interface{}) bool

Context gets the Context of the underlying response writer. It panics if the underlying response writer does no implement Context

func (*Peek) FlushCode

func (p *Peek) FlushCode()

FlushCode writes the status code to the underlying responsewriter if it was set

func (*Peek) FlushHeaders

func (p *Peek) FlushHeaders()

FlushHeaders adds the headers to the underlying ResponseWriter, removing them from Peek

func (*Peek) FlushMissing

func (p *Peek) FlushMissing()

FlushMissing ensures that the Headers and Code are written to the underlying ResponseWriter if they are not written yet (and nothing has been written to the body)

func (*Peek) HasChanged

func (p *Peek) HasChanged() bool

HasChanged returns true if Header or WriteHeader method have been called or if Write has been called and did write to the underlying response writer.

func (*Peek) Header

func (p *Peek) Header() http.Header

Header returns the cached http.Header, tracking the call as change

func (*Peek) IsOk

func (p *Peek) IsOk() bool

IsOk returns true if the returned status code is not set or in the 2xx range

func (*Peek) Reset

func (p *Peek) Reset()

Reset set the Peek to the defaults, so it will act as if it was freshly initialized.

func (*Peek) SetContext

func (p *Peek) SetContext(ctxPtr interface{})

SetContext sets the Context of the underlying response writer. It panics if the underlying response writer does no implement Context

func (*Peek) Write

func (p *Peek) Write(b []byte) (int, error)

Write writes to the underlying response writer, if the proceed function returns true. Otherwise it returns 0, io.EOF. If the data is written, the call is tracked as change.

The proceed function is only called the first time, Write has been called. If proceed is nil, it behaves as if proceed would have returned true.

See NewPeek for more informations about the usage of the proceed function.

func (*Peek) WriteHeader

func (p *Peek) WriteHeader(i int)

WriteHeader writes the cached status code, tracking the call as change

type Wrapper

type Wrapper interface {
	// Wrap wraps the next `http.Handler` of the stack and returns a wrapping `http.Handler`.
	// If it does not call `next.ServeHTTP`, nobody will.
	Wrap(next http.Handler) http.Handler
}

Wrapper can wrap a http.Handler with another one

func Handler

func Handler(h http.Handler) Wrapper

Handler returns a Wrapper for a http.Handler. The returned Wrapper simply runs the given handler and ignores the next handler in the stack.

func HandlerFunc

func HandlerFunc(fn func(http.ResponseWriter, *http.Request)) Wrapper

HandlerFunc is like Handler but for a function with the type signature of http.HandlerFunc

func NextHandler

func NextHandler(sh interface {
	ServeHTTPNext(next http.Handler, rw http.ResponseWriter, req *http.Request)
}) Wrapper

NextHandler returns a Wrapper for an interface with a ServeHTTPNext method

type WrapperFunc

type WrapperFunc func(http.Handler) http.Handler

WrapperFunc is an adapter for a function that acts as Wrapper

func (WrapperFunc) Wrap

func (wf WrapperFunc) Wrap(next http.Handler) http.Handler

Wrap makes the WrapperFunc fulfill the Wrapper interface by calling itself.

Jump to

Keyboard shortcuts

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