...

Source file src/github.com/bluekeyes/hatpear/hatpear.go

Documentation: github.com/bluekeyes/hatpear

     1  // Package hatpear provides a way to aggregate errors from HTTP handlers so
     2  // they can be processed by middleware. Errors are stored in the context of the
     3  // current request either manually, when using standard library handler types,
     4  // or automatically, when using this package's handler types.
     5  //
     6  // Using the middleware returned by the Catch function is required for this
     7  // package to work; usage of all other functions and types is optional.
     8  package hatpear
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"runtime"
    16  )
    17  
    18  type contextKey int
    19  
    20  const (
    21  	errorKey contextKey = iota
    22  )
    23  
    24  // Store stores an error into the request's context. It panics if the request
    25  // was not configured to store errors.
    26  func Store(r *http.Request, err error) {
    27  	errptr, ok := r.Context().Value(errorKey).(*error)
    28  	if !ok {
    29  		panic("hatpear: request not configured to store errors")
    30  	}
    31  	// check err after checking context to fail fast if unconfigured
    32  	if err != nil {
    33  		*errptr = err
    34  	}
    35  }
    36  
    37  // Get retrieves an error from the request's context. It returns nil if the
    38  // request was not configured to store errors.
    39  func Get(r *http.Request) error {
    40  	errptr, ok := r.Context().Value(errorKey).(*error)
    41  	if !ok {
    42  		return nil
    43  	}
    44  	return *errptr
    45  }
    46  
    47  // Middleware adds additional functionality to an existing handler.
    48  type Middleware func(http.Handler) http.Handler
    49  
    50  // Catch creates middleware that processes errors stored while serving a
    51  // request. Errors are passed to the callback, which should write them to the
    52  // response in an appropriate format. This is usually the outermost middleware
    53  // in a chain.
    54  func Catch(h func(w http.ResponseWriter, r *http.Request, err error)) Middleware {
    55  	return func(next http.Handler) http.Handler {
    56  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    57  			var err error
    58  			ctx := context.WithValue(r.Context(), errorKey, &err)
    59  
    60  			next.ServeHTTP(w, r.WithContext(ctx))
    61  			if err != nil {
    62  				h(w, r, err)
    63  			}
    64  		})
    65  	}
    66  }
    67  
    68  // Handler is a variant on http.Handler that can return an error.
    69  type Handler interface {
    70  	ServeHTTP(w http.ResponseWriter, r *http.Request) error
    71  }
    72  
    73  // HandlerFunc is a variant on http.HandlerFunc that can return an error.
    74  type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
    75  
    76  func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
    77  	return f(w, r)
    78  }
    79  
    80  // Try converts a handler to a standard http.Handler, storing any error in the
    81  // request's context.
    82  func Try(h Handler) http.Handler {
    83  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    84  		err := h.ServeHTTP(w, r)
    85  		Store(r, err)
    86  	})
    87  }
    88  
    89  // TryFunc converts a handler function to a standard http.Handler, storing any
    90  // error in the request's context.
    91  func TryFunc(h func(http.ResponseWriter, *http.Request) error) http.Handler {
    92  	return Try(HandlerFunc(h))
    93  }
    94  
    95  var (
    96  	// RecoverStackDepth is the max depth of stack trace to recover on panic.
    97  	RecoverStackDepth = 32
    98  )
    99  
   100  // Recover creates middleware that can recover from a panic in a handler,
   101  // storing a PanicError for future handling.
   102  func Recover() Middleware {
   103  	return func(next http.Handler) http.Handler {
   104  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   105  			defer func() {
   106  				if v := recover(); v != nil {
   107  					Store(r, PanicError{
   108  						value: v,
   109  						stack: stack(1),
   110  					})
   111  				}
   112  			}()
   113  			next.ServeHTTP(w, r)
   114  		})
   115  	}
   116  }
   117  
   118  func stack(skip int) []runtime.Frame {
   119  	rpc := make([]uintptr, RecoverStackDepth)
   120  
   121  	n := runtime.Callers(skip+2, rpc)
   122  	frames := runtime.CallersFrames(rpc[0:n])
   123  
   124  	var stack []runtime.Frame
   125  	for {
   126  		f, more := frames.Next()
   127  		if !more {
   128  			break
   129  		}
   130  		stack = append(stack, f)
   131  	}
   132  	return stack
   133  }
   134  
   135  // PanicError is an Error created from a recovered panic.
   136  type PanicError struct {
   137  	value interface{}
   138  	stack []runtime.Frame
   139  }
   140  
   141  // Value returns the exact value with which panic() was called.
   142  func (e PanicError) Value() interface{} {
   143  	return e.value
   144  }
   145  
   146  // StackTrace returns the stack of the panicking goroutine.
   147  func (e PanicError) StackTrace() []runtime.Frame {
   148  	return e.stack
   149  }
   150  
   151  // Format formats the error optionally including the stack trace.
   152  //
   153  //   %s    the error message
   154  //   %v    the error message and the source file and line number for each stack frame
   155  //
   156  // Format accepts the following flags:
   157  //
   158  //   %+v   the error message, and the function, file, and line for each stack frame
   159  func (e PanicError) Format(s fmt.State, verb rune) {
   160  	switch verb {
   161  	case 's':
   162  		io.WriteString(s, e.Error())
   163  	case 'v':
   164  		io.WriteString(s, e.Error())
   165  		for _, f := range e.stack {
   166  			io.WriteString(s, "\n")
   167  			if s.Flag('+') {
   168  				fmt.Fprintf(s, "%s\n\t", f.Function)
   169  			}
   170  			fmt.Fprintf(s, "%s:%d", f.File, f.Line)
   171  		}
   172  	}
   173  }
   174  
   175  func (e PanicError) Error() string {
   176  	v := e.value
   177  	if err, ok := v.(error); ok {
   178  		v = err.Error()
   179  	}
   180  	return fmt.Sprintf("panic: %v", v)
   181  }
   182  

View as plain text