Matthew Holt
6 years ago
6 changed files with 281 additions and 84 deletions
@ -0,0 +1,105 @@ |
|||
package caddyhttp |
|||
|
|||
import ( |
|||
"fmt" |
|||
mathrand "math/rand" |
|||
"path" |
|||
"runtime" |
|||
"strings" |
|||
|
|||
"bitbucket.org/lightcodelabs/caddy2" |
|||
) |
|||
|
|||
// Error is a convenient way for a Handler to populate the
|
|||
// essential fields of a HandlerError. If err is itself a
|
|||
// HandlerError, then any essential fields that are not
|
|||
// set will be populated.
|
|||
func Error(statusCode int, err error) HandlerError { |
|||
const idLen = 9 |
|||
if he, ok := err.(HandlerError); ok { |
|||
if he.ID == "" { |
|||
he.ID = randString(idLen, true) |
|||
} |
|||
if he.Trace == "" { |
|||
he.Trace = trace() |
|||
} |
|||
if he.StatusCode == 0 { |
|||
he.StatusCode = statusCode |
|||
} |
|||
return he |
|||
} |
|||
return HandlerError{ |
|||
ID: randString(idLen, true), |
|||
StatusCode: statusCode, |
|||
Err: err, |
|||
Trace: trace(), |
|||
} |
|||
} |
|||
|
|||
// HandlerError is a serializable representation of
|
|||
// an error from within an HTTP handler.
|
|||
type HandlerError struct { |
|||
Err error // the original error value and message
|
|||
StatusCode int // the HTTP status code to associate with this error
|
|||
Message string // an optional message that can be shown to the user
|
|||
Recommendations []string // an optional list of things to try to resolve the error
|
|||
|
|||
ID string // generated; for identifying this error in logs
|
|||
Trace string // produced from call stack
|
|||
} |
|||
|
|||
func (e HandlerError) Error() string { |
|||
var s string |
|||
if e.ID != "" { |
|||
s += fmt.Sprintf("{id=%s}", e.ID) |
|||
} |
|||
if e.Trace != "" { |
|||
s += " " + e.Trace |
|||
} |
|||
if e.StatusCode != 0 { |
|||
s += fmt.Sprintf(": HTTP %d", e.StatusCode) |
|||
} |
|||
if e.Err != nil { |
|||
s += ": " + e.Err.Error() |
|||
} |
|||
return strings.TrimSpace(s) |
|||
} |
|||
|
|||
// randString returns a string of n random characters.
|
|||
// It is not even remotely secure OR a proper distribution.
|
|||
// But it's good enough for some things. It excludes certain
|
|||
// confusing characters like I, l, 1, 0, O, etc. If sameCase
|
|||
// is true, then uppercase letters are excluded.
|
|||
func randString(n int, sameCase bool) string { |
|||
if n <= 0 { |
|||
return "" |
|||
} |
|||
dict := []byte("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY23456789") |
|||
if sameCase { |
|||
dict = []byte("abcdefghijkmnpqrstuvwxyz0123456789") |
|||
} |
|||
b := make([]byte, n) |
|||
for i := range b { |
|||
b[i] = dict[mathrand.Int63()%int64(len(dict))] |
|||
} |
|||
return string(b) |
|||
} |
|||
|
|||
func trace() string { |
|||
if pc, file, line, ok := runtime.Caller(2); ok { |
|||
filename := path.Base(file) |
|||
pkgAndFuncName := path.Base(runtime.FuncForPC(pc).Name()) |
|||
return fmt.Sprintf("%s (%s:%d)", pkgAndFuncName, filename, line) |
|||
} |
|||
return "" |
|||
} |
|||
|
|||
// ErrRehandle is a special error value that Handlers should return
|
|||
// from their ServeHTTP() method if the request is to be re-processed.
|
|||
// This error value is a sentinel value that should not be wrapped or
|
|||
// modified.
|
|||
var ErrRehandle = fmt.Errorf("rehandling request") |
|||
|
|||
// ErrorCtxKey is the context key to use when storing
|
|||
// an error (for use with context.Context).
|
|||
const ErrorCtxKey = caddy2.CtxKey("handler_chain_error") |
@ -0,0 +1,106 @@ |
|||
package caddyhttp |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"net/http" |
|||
|
|||
"bitbucket.org/lightcodelabs/caddy2" |
|||
) |
|||
|
|||
type serverRoute struct { |
|||
Matchers map[string]json.RawMessage `json:"match"` |
|||
Apply []json.RawMessage `json:"apply"` |
|||
Respond json.RawMessage `json:"respond"` |
|||
|
|||
Exclusive bool `json:"exclusive"` |
|||
|
|||
// decoded values
|
|||
matchers []RouteMatcher |
|||
middleware []MiddlewareHandler |
|||
responder Handler |
|||
} |
|||
|
|||
type routeList []serverRoute |
|||
|
|||
func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Request) Handler { |
|||
if len(routes) == 0 { |
|||
return emptyHandler |
|||
} |
|||
|
|||
var mid []Middleware |
|||
var responder Handler |
|||
mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}} |
|||
|
|||
for _, route := range routes { |
|||
matched := len(route.matchers) == 0 |
|||
for _, m := range route.matchers { |
|||
if m.Match(r) { |
|||
matched = true |
|||
break |
|||
} |
|||
} |
|||
if !matched { |
|||
continue |
|||
} |
|||
for _, m := range route.middleware { |
|||
mid = append(mid, func(next HandlerFunc) HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) error { |
|||
return m.ServeHTTP(mrw, r, next) |
|||
} |
|||
}) |
|||
} |
|||
if responder == nil { |
|||
responder = route.responder |
|||
} |
|||
if route.Exclusive { |
|||
break |
|||
} |
|||
} |
|||
|
|||
// build the middleware stack, with the responder at the end
|
|||
stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { |
|||
if responder == nil { |
|||
return nil |
|||
} |
|||
mrw.allowWrites = true |
|||
return responder.ServeHTTP(w, r) |
|||
}) |
|||
for i := len(mid) - 1; i >= 0; i-- { |
|||
stack = mid[i](stack) |
|||
} |
|||
|
|||
return stack |
|||
} |
|||
|
|||
func (routes routeList) setup() error { |
|||
for i, route := range routes { |
|||
// matchers
|
|||
for modName, rawMsg := range route.Matchers { |
|||
val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg) |
|||
if err != nil { |
|||
return fmt.Errorf("loading matcher module '%s': %v", modName, err) |
|||
} |
|||
routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher)) |
|||
} |
|||
|
|||
// middleware
|
|||
for j, rawMsg := range route.Apply { |
|||
mid, err := caddy2.LoadModuleInlineName("http.middleware", rawMsg) |
|||
if err != nil { |
|||
return fmt.Errorf("loading middleware module in position %d: %v", j, err) |
|||
} |
|||
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler)) |
|||
} |
|||
|
|||
// responder
|
|||
if route.Respond != nil { |
|||
resp, err := caddy2.LoadModuleInlineName("http.responders", route.Respond) |
|||
if err != nil { |
|||
return fmt.Errorf("loading responder module: %v", err) |
|||
} |
|||
routes[i].responder = resp.(Handler) |
|||
} |
|||
} |
|||
return nil |
|||
} |
Loading…
Reference in new issue