mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	Begin implementing error handling and re-handling
This commit is contained in:
		
							parent
							
								
									d42529348f
								
							
						
					
					
						commit
						545f28008e
					
				
							
								
								
									
										6
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								caddy.go
									
									
									
									
									
								
							@ -161,10 +161,8 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalJSON satisfies json.Marshaler.
 | 
			
		||||
func (d Duration) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	return []byte(fmt.Sprintf(`"%s"`, time.Duration(d).String())), nil
 | 
			
		||||
}
 | 
			
		||||
// CtxKey is a value type for use with context.WithValue.
 | 
			
		||||
type CtxKey string
 | 
			
		||||
 | 
			
		||||
// currentCfg is the currently-loaded configuration.
 | 
			
		||||
var (
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@ package caddyhttp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	mathrand "math/rand"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@ -22,6 +22,8 @@ func init() {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mathrand.Seed(time.Now().UnixNano())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type httpModuleConfig struct {
 | 
			
		||||
@ -32,36 +34,14 @@ type httpModuleConfig struct {
 | 
			
		||||
 | 
			
		||||
func (hc *httpModuleConfig) Run() error {
 | 
			
		||||
	// TODO: Either prevent overlapping listeners on different servers, or combine them into one
 | 
			
		||||
	// TODO: A way to loop requests back through, so have them start the matching over again, but keeping any mutations
 | 
			
		||||
	for _, srv := range hc.Servers {
 | 
			
		||||
		// set up the routes
 | 
			
		||||
		for i, route := range srv.Routes {
 | 
			
		||||
			// matchers
 | 
			
		||||
			for modName, rawMsg := range route.Matchers {
 | 
			
		||||
				val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
 | 
			
		||||
		err := srv.Routes.setup()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
					return fmt.Errorf("loading matcher module '%s': %v", modName, err)
 | 
			
		||||
			return fmt.Errorf("setting up server routes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
				srv.Routes[i].matchers = append(srv.Routes[i].matchers, val.(RouteMatcher))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// middleware
 | 
			
		||||
			for j, rawMsg := range route.Apply {
 | 
			
		||||
				mid, err := caddy2.LoadModuleInlineName("http.middleware", rawMsg)
 | 
			
		||||
		err = srv.Errors.Routes.setup()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
					return fmt.Errorf("loading middleware module in position %d: %v", j, err)
 | 
			
		||||
				}
 | 
			
		||||
				srv.Routes[i].middleware = append(srv.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)
 | 
			
		||||
				}
 | 
			
		||||
				srv.Routes[i].responder = resp.(Handler)
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("setting up server error handling routes: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := &http.Server{
 | 
			
		||||
@ -104,65 +84,56 @@ type httpServerConfig struct {
 | 
			
		||||
	ReadTimeout       caddy2.Duration `json:"read_timeout"`
 | 
			
		||||
	ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
 | 
			
		||||
	HiddenFiles       []string        `json:"hidden_files"` // TODO:... experimenting with shared/common state
 | 
			
		||||
	Routes            []serverRoute   `json:"routes"`
 | 
			
		||||
	Routes            routeList       `json:"routes"`
 | 
			
		||||
	Errors            httpErrorConfig `json:"errors"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	var mid []Middleware // TODO: see about using make() for performance reasons
 | 
			
		||||
	var responder Handler
 | 
			
		||||
	mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}}
 | 
			
		||||
type httpErrorConfig struct {
 | 
			
		||||
	Routes routeList `json:"routes"`
 | 
			
		||||
	// TODO: some way to configure the logging of errors, probably? standardize the logging configuration first.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	for _, route := range s.Routes {
 | 
			
		||||
		matched := len(route.matchers) == 0
 | 
			
		||||
		for _, m := range route.matchers {
 | 
			
		||||
			if m.Match(r) {
 | 
			
		||||
				matched = true
 | 
			
		||||
// ServeHTTP is the entry point for all HTTP requests.
 | 
			
		||||
func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	stack := s.Routes.buildMiddlewareChain(w, r)
 | 
			
		||||
	err := executeMiddlewareChain(w, r, stack)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// add the error value to the request context so
 | 
			
		||||
		// it can be accessed by error handlers
 | 
			
		||||
		c := context.WithValue(r.Context(), ErrorCtxKey, err)
 | 
			
		||||
		r = r.WithContext(c)
 | 
			
		||||
 | 
			
		||||
		if len(s.Errors.Routes) == 0 {
 | 
			
		||||
			// TODO: implement a default error handler?
 | 
			
		||||
			log.Printf("[ERROR] %s", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			errStack := s.Errors.Routes.buildMiddlewareChain(w, r)
 | 
			
		||||
			err := executeMiddlewareChain(w, r, errStack)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// TODO: what should we do if the error handler has an error?
 | 
			
		||||
				log.Printf("[ERROR] handling error: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// executeMiddlewareChain executes stack with w and r. This function handles
 | 
			
		||||
// the special ErrRehandle error value, which reprocesses requests through
 | 
			
		||||
// the stack again. Any error value returned from this function would be an
 | 
			
		||||
// actual error that needs to be handled.
 | 
			
		||||
func executeMiddlewareChain(w http.ResponseWriter, r *http.Request, stack Handler) error {
 | 
			
		||||
	const maxRehandles = 3
 | 
			
		||||
	var err error
 | 
			
		||||
	for i := 0; i < maxRehandles; i++ {
 | 
			
		||||
		err = stack.ServeHTTP(w, r)
 | 
			
		||||
		if err != ErrRehandle {
 | 
			
		||||
			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 i == maxRehandles-1 {
 | 
			
		||||
			return fmt.Errorf("too many rehandles")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := stack.ServeHTTP(w, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// TODO: error handling
 | 
			
		||||
		log.Printf("[ERROR] TODO: error handling: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type serverRoute struct {
 | 
			
		||||
	Matchers map[string]json.RawMessage `json:"match"`
 | 
			
		||||
	Apply    []json.RawMessage          `json:"apply"`
 | 
			
		||||
	Respond  json.RawMessage            `json:"respond"`
 | 
			
		||||
 | 
			
		||||
	// decoded values
 | 
			
		||||
	matchers   []RouteMatcher
 | 
			
		||||
	middleware []MiddlewareHandler
 | 
			
		||||
	responder  Handler
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	return f(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// emptyHandler is used as a no-op handler, which is
 | 
			
		||||
// sometimes better than a nil Handler pointer.
 | 
			
		||||
var emptyHandler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { return nil }
 | 
			
		||||
 | 
			
		||||
func parseListenAddr(a string) (network string, addrs []string, err error) {
 | 
			
		||||
	network = "tcp"
 | 
			
		||||
	if idx := strings.Index(a, "/"); idx >= 0 {
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ func init() {
 | 
			
		||||
	caddy2.RegisterModule(caddy2.Module{
 | 
			
		||||
		Name: "http.middleware.log",
 | 
			
		||||
		New:  func() (interface{}, error) { return new(Log), nil },
 | 
			
		||||
		// TODO: Examples of OnLoad and OnUnload.
 | 
			
		||||
		OnLoad: func(instances []interface{}, priorState interface{}) (interface{}, error) {
 | 
			
		||||
			var counter int
 | 
			
		||||
			if priorState != nil {
 | 
			
		||||
@ -42,6 +43,17 @@ type Log struct {
 | 
			
		||||
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// TODO: An example of returning errors
 | 
			
		||||
	// return caddyhttp.Error(http.StatusBadRequest, fmt.Errorf("this is a basic error"))
 | 
			
		||||
	// return caddyhttp.Error(http.StatusBadGateway, caddyhttp.HandlerError{
 | 
			
		||||
	// 	Err:     fmt.Errorf("this is a detailed error"),
 | 
			
		||||
	// 	Message: "We had trouble doing the thing.",
 | 
			
		||||
	// 	Recommendations: []string{
 | 
			
		||||
	// 		"Try reconnecting the gizbop.",
 | 
			
		||||
	// 		"Turn off the Internet.",
 | 
			
		||||
	// 	},
 | 
			
		||||
	// })
 | 
			
		||||
 | 
			
		||||
	if err := next.ServeHTTP(w, r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								modules/caddyhttp/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								modules/caddyhttp/errors.go
									
									
									
									
									
										Normal file
									
								
							@ -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")
 | 
			
		||||
@ -137,6 +137,7 @@ func (m matchHeader) Match(r *http.Request) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Interface guards
 | 
			
		||||
var (
 | 
			
		||||
	_ RouteMatcher = matchHost{}
 | 
			
		||||
	_ RouteMatcher = matchPath{}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										106
									
								
								modules/caddyhttp/routes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								modules/caddyhttp/routes.go
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user