@marcel91200 / caddy Mirror · read-only / commits / 545f2800
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

Begin implementing error handling and re-handling

Matthew Holt committed Apr 11, 2019 at 20:42 UTC 545f28008e0175491af030f8689cab2112fda9ed
6 files changed +281 -84
caddy.go
+2 -4
@@ -161,10 +161,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
161 return nil
162 }
163
164 -// MarshalJSON satisfies json.Marshaler.
165 -func (d Duration) MarshalJSON() ([]byte, error) {
166 - return []byte(fmt.Sprintf(`"%s"`, time.Duration(d).String())), nil
167 -}
164 +// CtxKey is a value type for use with context.WithValue.
165 +type CtxKey string
166
167 // currentCfg is the currently-loaded configuration.
168 var (
modules/caddyhttp/caddyhttp.go
+55 -80
@@ -2,9 +2,9 @@ package caddyhttp
2
3 import (
4 "context"
5 - "encoding/json"
5 "fmt"
6 "log"
7 + mathrand "math/rand"
8 "net"
9 "net/http"
10 "strconv"
@@ -22,6 +22,8 @@ func init() {
22 if err != nil {
23 log.Fatal(err)
24 }
25 +
26 + mathrand.Seed(time.Now().UnixNano())
27 }
28
29 type httpModuleConfig struct {
@@ -32,36 +34,14 @@ type httpModuleConfig struct {
34
35 func (hc *httpModuleConfig) Run() error {
36 // TODO: Either prevent overlapping listeners on different servers, or combine them into one
35 - // TODO: A way to loop requests back through, so have them start the matching over again, but keeping any mutations
37 for _, srv := range hc.Servers {
37 - // set up the routes
38 - for i, route := range srv.Routes {
39 - // matchers
40 - for modName, rawMsg := range route.Matchers {
41 - val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
42 - if err != nil {
43 - return fmt.Errorf("loading matcher module '%s': %v", modName, err)
44 - }
45 - srv.Routes[i].matchers = append(srv.Routes[i].matchers, val.(RouteMatcher))
46 - }
47 -
48 - // middleware
49 - for j, rawMsg := range route.Apply {
50 - mid, err := caddy2.LoadModuleInlineName("http.middleware", rawMsg)
51 - if err != nil {
52 - return fmt.Errorf("loading middleware module in position %d: %v", j, err)
53 - }
54 - srv.Routes[i].middleware = append(srv.Routes[i].middleware, mid.(MiddlewareHandler))
55 - }
56 -
57 - // responder
58 - if route.Respond != nil {
59 - resp, err := caddy2.LoadModuleInlineName("http.responders", route.Respond)
60 - if err != nil {
61 - return fmt.Errorf("loading responder module: %v", err)
62 - }
63 - srv.Routes[i].responder = resp.(Handler)
64 - }
38 + err := srv.Routes.setup()
39 + if err != nil {
40 + return fmt.Errorf("setting up server routes: %v", err)
41 + }
42 + err = srv.Errors.Routes.setup()
43 + if err != nil {
44 + return fmt.Errorf("setting up server error handling routes: %v", err)
45 }
46
47 s := &http.Server{
@@ -104,65 +84,56 @@ type httpServerConfig struct {
84 ReadTimeout caddy2.Duration `json:"read_timeout"`
85 ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
86 HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
107 - Routes []serverRoute `json:"routes"`
87 + Routes routeList `json:"routes"`
88 + Errors httpErrorConfig `json:"errors"`
89 }
90
110 -func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
111 - var mid []Middleware // TODO: see about using make() for performance reasons
112 - var responder Handler
113 - mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}}
91 +type httpErrorConfig struct {
92 + Routes routeList `json:"routes"`
93 + // TODO: some way to configure the logging of errors, probably? standardize the logging configuration first.
94 +}
95
115 - for _, route := range s.Routes {
116 - matched := len(route.matchers) == 0
117 - for _, m := range route.matchers {
118 - if m.Match(r) {
119 - matched = true
120 - break
96 +// ServeHTTP is the entry point for all HTTP requests.
97 +func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
98 + stack := s.Routes.buildMiddlewareChain(w, r)
99 + err := executeMiddlewareChain(w, r, stack)
100 + if err != nil {
101 + // add the error value to the request context so
102 + // it can be accessed by error handlers
103 + c := context.WithValue(r.Context(), ErrorCtxKey, err)
104 + r = r.WithContext(c)
105 +
106 + if len(s.Errors.Routes) == 0 {
107 + // TODO: implement a default error handler?
108 + log.Printf("[ERROR] %s", err)
109 + } else {
110 + errStack := s.Errors.Routes.buildMiddlewareChain(w, r)
111 + err := executeMiddlewareChain(w, r, errStack)
112 + if err != nil {
113 + // TODO: what should we do if the error handler has an error?
114 + log.Printf("[ERROR] handling error: %v", err)
115 }
116 }
123 - if !matched {
124 - continue
125 - }
126 - for _, m := range route.middleware {
127 - mid = append(mid, func(next HandlerFunc) HandlerFunc {
128 - return func(w http.ResponseWriter, r *http.Request) error {
129 - return m.ServeHTTP(mrw, r, next)
130 - }
131 - })
132 - }
133 - if responder == nil {
134 - responder = route.responder
135 - }
117 }
118 +}
119
138 - // build the middleware stack, with the responder at the end
139 - stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
140 - if responder == nil {
141 - return nil
120 +// executeMiddlewareChain executes stack with w and r. This function handles
121 +// the special ErrRehandle error value, which reprocesses requests through
122 +// the stack again. Any error value returned from this function would be an
123 +// actual error that needs to be handled.
124 +func executeMiddlewareChain(w http.ResponseWriter, r *http.Request, stack Handler) error {
125 + const maxRehandles = 3
126 + var err error
127 + for i := 0; i < maxRehandles; i++ {
128 + err = stack.ServeHTTP(w, r)
129 + if err != ErrRehandle {
130 + break
131 + }
132 + if i == maxRehandles-1 {
133 + return fmt.Errorf("too many rehandles")
134 }
143 - mrw.allowWrites = true
144 - return responder.ServeHTTP(w, r)
145 - })
146 - for i := len(mid) - 1; i >= 0; i-- {
147 - stack = mid[i](stack)
148 - }
149 -
150 - err := stack.ServeHTTP(w, r)
151 - if err != nil {
152 - // TODO: error handling
153 - log.Printf("[ERROR] TODO: error handling: %v", err)
135 }
155 -}
156 -
157 -type serverRoute struct {
158 - Matchers map[string]json.RawMessage `json:"match"`
159 - Apply []json.RawMessage `json:"apply"`
160 - Respond json.RawMessage `json:"respond"`
161 -
162 - // decoded values
163 - matchers []RouteMatcher
164 - middleware []MiddlewareHandler
165 - responder Handler
136 + return err
137 }
138
139 // RouteMatcher is a type that can match to a request.
@@ -206,6 +177,10 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
177 return f(w, r)
178 }
179
180 +// emptyHandler is used as a no-op handler, which is
181 +// sometimes better than a nil Handler pointer.
182 +var emptyHandler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { return nil }
183 +
184 func parseListenAddr(a string) (network string, addrs []string, err error) {
185 network = "tcp"
186 if idx := strings.Index(a, "/"); idx >= 0 {
modules/caddyhttp/caddylog/log.go
+12
@@ -13,6 +13,7 @@ func init() {
13 caddy2.RegisterModule(caddy2.Module{
14 Name: "http.middleware.log",
15 New: func() (interface{}, error) { return new(Log), nil },
16 + // TODO: Examples of OnLoad and OnUnload.
17 OnLoad: func(instances []interface{}, priorState interface{}) (interface{}, error) {
18 var counter int
19 if priorState != nil {
@@ -42,6 +43,17 @@ type Log struct {
43 func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
44 start := time.Now()
45
46 + // TODO: An example of returning errors
47 + // return caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("this is a basic error"))
48 + // return caddyhttp.Error(http.StatusBadGateway, caddyhttp.HandlerError{
49 + // Err: fmt.Errorf("this is a detailed error"),
50 + // Message: "We had trouble doing the thing.",
51 + // Recommendations: []string{
52 + // "Try reconnecting the gizbop.",
53 + // "Turn off the Internet.",
54 + // },
55 + // })
56 +
57 if err := next.ServeHTTP(w, r); err != nil {
58 return err
59 }
modules/caddyhttp/errors.go new
+105
@@ -0,0 +1,105 @@
1 +package caddyhttp
2 +
3 +import (
4 + "fmt"
5 + mathrand "math/rand"
6 + "path"
7 + "runtime"
8 + "strings"
9 +
10 + "bitbucket.org/lightcodelabs/caddy2"
11 +)
12 +
13 +// Error is a convenient way for a Handler to populate the
14 +// essential fields of a HandlerError. If err is itself a
15 +// HandlerError, then any essential fields that are not
16 +// set will be populated.
17 +func Error(statusCode int, err error) HandlerError {
18 + const idLen = 9
19 + if he, ok := err.(HandlerError); ok {
20 + if he.ID == "" {
21 + he.ID = randString(idLen, true)
22 + }
23 + if he.Trace == "" {
24 + he.Trace = trace()
25 + }
26 + if he.StatusCode == 0 {
27 + he.StatusCode = statusCode
28 + }
29 + return he
30 + }
31 + return HandlerError{
32 + ID: randString(idLen, true),
33 + StatusCode: statusCode,
34 + Err: err,
35 + Trace: trace(),
36 + }
37 +}
38 +
39 +// HandlerError is a serializable representation of
40 +// an error from within an HTTP handler.
41 +type HandlerError struct {
42 + Err error // the original error value and message
43 + StatusCode int // the HTTP status code to associate with this error
44 + Message string // an optional message that can be shown to the user
45 + Recommendations []string // an optional list of things to try to resolve the error
46 +
47 + ID string // generated; for identifying this error in logs
48 + Trace string // produced from call stack
49 +}
50 +
51 +func (e HandlerError) Error() string {
52 + var s string
53 + if e.ID != "" {
54 + s += fmt.Sprintf("{id=%s}", e.ID)
55 + }
56 + if e.Trace != "" {
57 + s += " " + e.Trace
58 + }
59 + if e.StatusCode != 0 {
60 + s += fmt.Sprintf(": HTTP %d", e.StatusCode)
61 + }
62 + if e.Err != nil {
63 + s += ": " + e.Err.Error()
64 + }
65 + return strings.TrimSpace(s)
66 +}
67 +
68 +// randString returns a string of n random characters.
69 +// It is not even remotely secure OR a proper distribution.
70 +// But it's good enough for some things. It excludes certain
71 +// confusing characters like I, l, 1, 0, O, etc. If sameCase
72 +// is true, then uppercase letters are excluded.
73 +func randString(n int, sameCase bool) string {
74 + if n <= 0 {
75 + return ""
76 + }
77 + dict := []byte("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY23456789")
78 + if sameCase {
79 + dict = []byte("abcdefghijkmnpqrstuvwxyz0123456789")
80 + }
81 + b := make([]byte, n)
82 + for i := range b {
83 + b[i] = dict[mathrand.Int63()%int64(len(dict))]
84 + }
85 + return string(b)
86 +}
87 +
88 +func trace() string {
89 + if pc, file, line, ok := runtime.Caller(2); ok {
90 + filename := path.Base(file)
91 + pkgAndFuncName := path.Base(runtime.FuncForPC(pc).Name())
92 + return fmt.Sprintf("%s (%s:%d)", pkgAndFuncName, filename, line)
93 + }
94 + return ""
95 +}
96 +
97 +// ErrRehandle is a special error value that Handlers should return
98 +// from their ServeHTTP() method if the request is to be re-processed.
99 +// This error value is a sentinel value that should not be wrapped or
100 +// modified.
101 +var ErrRehandle = fmt.Errorf("rehandling request")
102 +
103 +// ErrorCtxKey is the context key to use when storing
104 +// an error (for use with context.Context).
105 +const ErrorCtxKey = caddy2.CtxKey("handler_chain_error")
modules/caddyhttp/matchers.go
+1
@@ -137,6 +137,7 @@ func (m matchHeader) Match(r *http.Request) bool {
137 return false
138 }
139
140 +// Interface guards
141 var (
142 _ RouteMatcher = matchHost{}
143 _ RouteMatcher = matchPath{}
modules/caddyhttp/routes.go new
+106
@@ -0,0 +1,106 @@
1 +package caddyhttp
2 +
3 +import (
4 + "encoding/json"
5 + "fmt"
6 + "net/http"
7 +
8 + "bitbucket.org/lightcodelabs/caddy2"
9 +)
10 +
11 +type serverRoute struct {
12 + Matchers map[string]json.RawMessage `json:"match"`
13 + Apply []json.RawMessage `json:"apply"`
14 + Respond json.RawMessage `json:"respond"`
15 +
16 + Exclusive bool `json:"exclusive"`
17 +
18 + // decoded values
19 + matchers []RouteMatcher
20 + middleware []MiddlewareHandler
21 + responder Handler
22 +}
23 +
24 +type routeList []serverRoute
25 +
26 +func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Request) Handler {
27 + if len(routes) == 0 {
28 + return emptyHandler
29 + }
30 +
31 + var mid []Middleware
32 + var responder Handler
33 + mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}}
34 +
35 + for _, route := range routes {
36 + matched := len(route.matchers) == 0
37 + for _, m := range route.matchers {
38 + if m.Match(r) {
39 + matched = true
40 + break
41 + }
42 + }
43 + if !matched {
44 + continue
45 + }
46 + for _, m := range route.middleware {
47 + mid = append(mid, func(next HandlerFunc) HandlerFunc {
48 + return func(w http.ResponseWriter, r *http.Request) error {
49 + return m.ServeHTTP(mrw, r, next)
50 + }
51 + })
52 + }
53 + if responder == nil {
54 + responder = route.responder
55 + }
56 + if route.Exclusive {
57 + break
58 + }
59 + }
60 +
61 + // build the middleware stack, with the responder at the end
62 + stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
63 + if responder == nil {
64 + return nil
65 + }
66 + mrw.allowWrites = true
67 + return responder.ServeHTTP(w, r)
68 + })
69 + for i := len(mid) - 1; i >= 0; i-- {
70 + stack = mid[i](stack)
71 + }
72 +
73 + return stack
74 +}
75 +
76 +func (routes routeList) setup() error {
77 + for i, route := range routes {
78 + // matchers
79 + for modName, rawMsg := range route.Matchers {
80 + val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
81 + if err != nil {
82 + return fmt.Errorf("loading matcher module '%s': %v", modName, err)
83 + }
84 + routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
85 + }
86 +
87 + // middleware
88 + for j, rawMsg := range route.Apply {
89 + mid, err := caddy2.LoadModuleInlineName("http.middleware", rawMsg)
90 + if err != nil {
91 + return fmt.Errorf("loading middleware module in position %d: %v", j, err)
92 + }
93 + routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
94 + }
95 +
96 + // responder
97 + if route.Respond != nil {
98 + resp, err := caddy2.LoadModuleInlineName("http.responders", route.Respond)
99 + if err != nil {
100 + return fmt.Errorf("loading responder module: %v", err)
101 + }
102 + routes[i].responder = resp.(Handler)
103 + }
104 + }
105 + return nil
106 +}