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