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