mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
Flatten HTTP handler config (#2662)
Differentiating middleware and responders has one benefit, namely that it's clear which module provides the response, but even then it's not a great advantage. Linear handler config makes a little more sense, giving greater flexibility and simplifying the core a bit, even though it's slightly awkward that handlers which are responders may not use the 'next' handler that is passed in at all.
This commit is contained in:
parent
6dfba5fda8
commit
4a3a418156
9 changed files with 99 additions and 148 deletions
|
@ -322,13 +322,15 @@ func (app *App) automaticHTTPS() error {
|
||||||
MatchHost(domains),
|
MatchHost(domains),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
responder: Static{
|
handlers: []MiddlewareHandler{
|
||||||
StatusCode: http.StatusTemporaryRedirect, // TODO: use permanent redirect instead
|
Static{
|
||||||
Headers: http.Header{
|
StatusCode: strconv.Itoa(http.StatusTemporaryRedirect), // TODO: use permanent redirect instead
|
||||||
"Location": []string{redirTo},
|
Headers: http.Header{
|
||||||
"Connection": []string{"close"},
|
"Location": []string{redirTo},
|
||||||
|
"Connection": []string{"close"},
|
||||||
|
},
|
||||||
|
Close: true,
|
||||||
},
|
},
|
||||||
Close: true,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -381,34 +383,18 @@ func (app *App) listenerTaken(network, address string) bool {
|
||||||
var defaultALPN = []string{"h2", "http/1.1"}
|
var defaultALPN = []string{"h2", "http/1.1"}
|
||||||
|
|
||||||
// RequestMatcher is a type that can match to a request.
|
// RequestMatcher is a type that can match to a request.
|
||||||
// A route matcher MUST NOT modify the request.
|
// A route matcher MUST NOT modify the request, with the
|
||||||
|
// only exception being its context.
|
||||||
type RequestMatcher interface {
|
type RequestMatcher interface {
|
||||||
Match(*http.Request) bool
|
Match(*http.Request) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middleware chains one Handler to the next by being passed
|
|
||||||
// the next Handler in the chain.
|
|
||||||
type Middleware func(HandlerFunc) HandlerFunc
|
|
||||||
|
|
||||||
// MiddlewareHandler is a Handler that includes a reference
|
|
||||||
// to the next middleware handler in the chain. Middleware
|
|
||||||
// handlers MUST NOT call Write() or WriteHeader() on the
|
|
||||||
// response writer; doing so will panic. See Handler godoc
|
|
||||||
// for more information.
|
|
||||||
type MiddlewareHandler interface {
|
|
||||||
ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler is like http.Handler except ServeHTTP may return an error.
|
// Handler is like http.Handler except ServeHTTP may return an error.
|
||||||
//
|
//
|
||||||
// Middleware and responder handlers both implement this method.
|
|
||||||
// Middleware must not call Write or WriteHeader on the ResponseWriter;
|
|
||||||
// doing so will cause a panic. Responders should write to the response
|
|
||||||
// if there was not an error.
|
|
||||||
//
|
|
||||||
// If any handler encounters an error, it should be returned for proper
|
// If any handler encounters an error, it should be returned for proper
|
||||||
// handling. Return values should be propagated down the middleware chain
|
// handling. Return values should be propagated down the middleware chain
|
||||||
// by returning it unchanged. Returned errors should not be re-wrapped.
|
// by returning it unchanged. Returned errors should not be re-wrapped
|
||||||
|
// if they are already HandlerError values.
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
ServeHTTP(http.ResponseWriter, *http.Request) error
|
ServeHTTP(http.ResponseWriter, *http.Request) error
|
||||||
}
|
}
|
||||||
|
@ -421,9 +407,25 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||||
return f(w, r)
|
return f(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// emptyHandler is used as a no-op handler, which is
|
// Middleware chains one Handler to the next by being passed
|
||||||
// sometimes better than a nil Handler pointer.
|
// the next Handler in the chain.
|
||||||
var emptyHandler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { return nil }
|
type Middleware func(HandlerFunc) HandlerFunc
|
||||||
|
|
||||||
|
// MiddlewareHandler is like Handler except it takes as a third
|
||||||
|
// argument the next handler in the chain. The next handler will
|
||||||
|
// never be nil, but may be a no-op handler if this is the last
|
||||||
|
// handler in the chain. Handlers which act as middleware should
|
||||||
|
// call the next handler's ServeHTTP method so as to propagate
|
||||||
|
// the request down the chain properly. Handlers which act as
|
||||||
|
// responders (content origins) need not invoke the next handler,
|
||||||
|
// since the last handler in the chain should be the first to
|
||||||
|
// write the response.
|
||||||
|
type MiddlewareHandler interface {
|
||||||
|
ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// emptyHandler is used as a no-op handler.
|
||||||
|
var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { return nil }
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultHTTPPort is the default port for HTTP.
|
// DefaultHTTPPort is the default port for HTTP.
|
||||||
|
|
|
@ -36,7 +36,7 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(caddy.Module{
|
||||||
Name: "http.middleware.encode",
|
Name: "http.handlers.encode",
|
||||||
New: func() interface{} { return new(Encode) },
|
New: func() interface{} { return new(Encode) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func init() {
|
||||||
weakrand.Seed(time.Now().UnixNano())
|
weakrand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(caddy.Module{
|
||||||
Name: "http.responders.file_server",
|
Name: "http.handlers.file_server",
|
||||||
New: func() interface{} { return new(FileServer) },
|
New: func() interface{} { return new(FileServer) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ func (fsrv *FileServer) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
|
||||||
filesToHide := fsrv.transformHidePaths(repl)
|
filesToHide := fsrv.transformHidePaths(repl)
|
||||||
|
@ -119,7 +119,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
// no files worked, so resort to fallback
|
// no files worked, so resort to fallback
|
||||||
if fsrv.Fallback != nil {
|
if fsrv.Fallback != nil {
|
||||||
fallback, w := fsrv.Fallback.BuildCompositeRoute(w, r)
|
fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
|
||||||
return fallback.ServeHTTP(w, r)
|
return fallback.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
return caddyhttp.Error(http.StatusNotFound, nil)
|
return caddyhttp.Error(http.StatusNotFound, nil)
|
||||||
|
@ -452,7 +452,7 @@ const minBackoff, maxBackoff = 2, 5
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*FileServer)(nil)
|
_ caddy.Provisioner = (*FileServer)(nil)
|
||||||
_ caddy.Validator = (*FileServer)(nil)
|
_ caddy.Validator = (*FileServer)(nil)
|
||||||
_ caddyhttp.Handler = (*FileServer)(nil)
|
_ caddyhttp.MiddlewareHandler = (*FileServer)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,7 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(caddy.Module{
|
||||||
Name: "http.middleware.rewrite",
|
Name: "http.handlers.rewrite",
|
||||||
New: func() interface{} { return new(Rewrite) },
|
New: func() interface{} { return new(Rewrite) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,25 +28,19 @@ import (
|
||||||
type ServerRoute struct {
|
type ServerRoute struct {
|
||||||
Group string `json:"group,omitempty"`
|
Group string `json:"group,omitempty"`
|
||||||
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
|
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
|
||||||
Apply []json.RawMessage `json:"apply,omitempty"`
|
Handle []json.RawMessage `json:"handle,omitempty"`
|
||||||
Respond json.RawMessage `json:"respond,omitempty"`
|
Terminal bool `json:"terminal,omitempty"`
|
||||||
|
|
||||||
Terminal bool `json:"terminal,omitempty"`
|
|
||||||
|
|
||||||
// decoded values
|
// decoded values
|
||||||
matcherSets []MatcherSet
|
matcherSets []MatcherSet
|
||||||
middleware []MiddlewareHandler
|
handlers []MiddlewareHandler
|
||||||
responder Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if the route has all zero/default values.
|
// Empty returns true if the route has all zero/default values.
|
||||||
func (sr ServerRoute) Empty() bool {
|
func (sr ServerRoute) Empty() bool {
|
||||||
return len(sr.MatcherSets) == 0 &&
|
return len(sr.MatcherSets) == 0 &&
|
||||||
len(sr.Apply) == 0 &&
|
len(sr.Handle) == 0 &&
|
||||||
len(sr.Respond) == 0 &&
|
len(sr.handlers) == 0 &&
|
||||||
len(sr.matcherSets) == 0 &&
|
|
||||||
len(sr.middleware) == 0 &&
|
|
||||||
sr.responder == nil &&
|
|
||||||
!sr.Terminal &&
|
!sr.Terminal &&
|
||||||
sr.Group == ""
|
sr.Group == ""
|
||||||
}
|
}
|
||||||
|
@ -98,40 +92,27 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
|
routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
|
|
||||||
// middleware
|
// handlers
|
||||||
for j, rawMsg := range route.Apply {
|
for j, rawMsg := range route.Handle {
|
||||||
mid, err := ctx.LoadModuleInline("middleware", "http.middleware", rawMsg)
|
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
|
return fmt.Errorf("loading handler module in position %d: %v", j, err)
|
||||||
}
|
}
|
||||||
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
|
routes[i].handlers = append(routes[i].handlers, mh.(MiddlewareHandler))
|
||||||
}
|
}
|
||||||
routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
|
routes[i].Handle = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
|
|
||||||
// responder
|
|
||||||
if route.Respond != nil {
|
|
||||||
resp, err := ctx.LoadModuleInline("responder", "http.responders", route.Respond)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading responder module: %v", err)
|
|
||||||
}
|
|
||||||
routes[i].responder = resp.(Handler)
|
|
||||||
}
|
|
||||||
routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildCompositeRoute creates a chain of handlers by applying all the matching
|
// BuildCompositeRoute creates a chain of handlers by
|
||||||
// routes. The returned ResponseWriter should be used instead of rw.
|
// applying all of the matching routes.
|
||||||
func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) (Handler, http.ResponseWriter) {
|
func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) Handler {
|
||||||
mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{rw}}
|
|
||||||
|
|
||||||
if len(routes) == 0 {
|
if len(routes) == 0 {
|
||||||
return emptyHandler, mrw
|
return emptyHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
var mid []Middleware
|
var mid []Middleware
|
||||||
var responder Handler
|
|
||||||
groups := make(map[string]struct{})
|
groups := make(map[string]struct{})
|
||||||
|
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
|
@ -140,9 +121,8 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if route is part of a group, ensure only
|
// if route is part of a group, ensure only the
|
||||||
// the first matching route in the group is
|
// first matching route in the group is applied
|
||||||
// applied
|
|
||||||
if route.Group != "" {
|
if route.Group != "" {
|
||||||
_, ok := groups[route.Group]
|
_, ok := groups[route.Group]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -155,78 +135,48 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the rest of the route
|
// apply the rest of the route
|
||||||
for _, m := range route.middleware {
|
for _, mh := range route.handlers {
|
||||||
// we have to be sure to wrap m outside
|
// we have to be sure to wrap mh outside
|
||||||
// of our current scope so that the
|
// of our current stack frame so that the
|
||||||
// reference to this m isn't overwritten
|
// reference to this mh isn't overwritten
|
||||||
// on the next iteration, leaving only
|
// on the next iteration, leaving the last
|
||||||
// the last middleware in the chain as
|
// middleware in the chain as the ONLY
|
||||||
// the ONLY middleware in the chain!
|
// middleware in the chain!
|
||||||
mid = append(mid, wrapMiddleware(m))
|
mid = append(mid, wrapMiddleware(mh))
|
||||||
}
|
|
||||||
if responder == nil {
|
|
||||||
responder = route.responder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this route is supposed to be last, don't
|
||||||
|
// compile any more into the chain
|
||||||
if route.Terminal {
|
if route.Terminal {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the middleware stack, with the responder at the end
|
// build the middleware chain, with the responder at the end
|
||||||
stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
stack := emptyHandler
|
||||||
if responder == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
mrw.allowWrites = true
|
|
||||||
return responder.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
for i := len(mid) - 1; i >= 0; i-- {
|
for i := len(mid) - 1; i >= 0; i-- {
|
||||||
stack = mid[i](stack)
|
stack = mid[i](stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack, mrw
|
return stack
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapMiddleware wraps m such that it can be correctly
|
// wrapMiddleware wraps m such that it can be correctly
|
||||||
// appended to a list of middleware. This separate closure
|
// appended to a list of middleware. We can't do this
|
||||||
// is necessary so that only the last middleware in a loop
|
// directly in a loop because it relies on a reference
|
||||||
// does not become the only middleware of the stack,
|
// to mh not changing until the execution of its handler,
|
||||||
// repeatedly executed (i.e. it is necessary to keep a
|
// which is deferred by multiple func closures. In other
|
||||||
// reference to this m outside of the scope of a loop)!
|
// words, we need to pull this particular MiddlewareHandler
|
||||||
func wrapMiddleware(m MiddlewareHandler) Middleware {
|
// pointer into its own stack frame to preserve it so it
|
||||||
|
// won't be overwritten in future loop iterations.
|
||||||
|
func wrapMiddleware(mh MiddlewareHandler) Middleware {
|
||||||
return func(next HandlerFunc) HandlerFunc {
|
return func(next HandlerFunc) HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) error {
|
return func(w http.ResponseWriter, r *http.Request) error {
|
||||||
// TODO: This is where request tracing could be implemented; also
|
// TODO: This is where request tracing could be implemented; also
|
||||||
// see below to trace the responder as well
|
// see below to trace the responder as well
|
||||||
// TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
|
// TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
|
||||||
// TODO: see what the std lib gives us in terms of stack tracing too
|
// TODO: see what the std lib gives us in terms of stack tracing too
|
||||||
return m.ServeHTTP(w, r, next)
|
return mh.ServeHTTP(w, r, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type middlewareResponseWriter struct {
|
|
||||||
*ResponseWriterWrapper
|
|
||||||
allowWrites bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrw middlewareResponseWriter) WriteHeader(statusCode int) {
|
|
||||||
if !mrw.allowWrites {
|
|
||||||
// technically, this is not true: middleware can write headers,
|
|
||||||
// but only after the responder handler has returned; either the
|
|
||||||
// responder did nothing with the response (sad face), or the
|
|
||||||
// middleware wrapped the response and deferred the write
|
|
||||||
panic("WriteHeader: middleware cannot write response headers")
|
|
||||||
}
|
|
||||||
mrw.ResponseWriterWrapper.WriteHeader(statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
|
|
||||||
if !mrw.allowWrites {
|
|
||||||
panic("Write: middleware cannot write to the response before responder")
|
|
||||||
}
|
|
||||||
return mrw.ResponseWriterWrapper.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ HTTPInterfaces = (*middlewareResponseWriter)(nil)
|
|
||||||
|
|
|
@ -65,9 +65,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
addHTTPVarsToReplacer(repl, r, w)
|
addHTTPVarsToReplacer(repl, r, w)
|
||||||
|
|
||||||
// build and execute the main handler chain
|
// build and execute the main handler chain
|
||||||
stack, wrappedWriter := s.Routes.BuildCompositeRoute(w, r)
|
stack := s.Routes.BuildCompositeRoute(w, r)
|
||||||
stack = s.wrapPrimaryRoute(stack)
|
stack = s.wrapPrimaryRoute(stack)
|
||||||
err := s.executeCompositeRoute(wrappedWriter, r, stack)
|
err := s.executeCompositeRoute(w, r, stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// add the raw error value to the request context
|
// add the raw error value to the request context
|
||||||
// so it can be accessed by error handlers
|
// so it can be accessed by error handlers
|
||||||
|
@ -85,8 +85,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
||||||
errStack, wrappedWriter := s.Errors.Routes.BuildCompositeRoute(w, r)
|
errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
|
||||||
err := s.executeCompositeRoute(wrappedWriter, r, errStack)
|
err := s.executeCompositeRoute(w, r, errStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: what should we do if the error handler has an error?
|
// TODO: what should we do if the error handler has an error?
|
||||||
log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
|
log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
|
||||||
|
@ -154,6 +154,8 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next
|
||||||
return next.ServeHTTP(w, r)
|
return next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listenersUseAnyPortOtherThan returns true if there are any
|
||||||
|
// listeners in s that use a port which is not otherPort.
|
||||||
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
||||||
for _, lnAddr := range s.Listen {
|
for _, lnAddr := range s.Listen {
|
||||||
_, addrs, err := caddy.ParseListenAddr(lnAddr)
|
_, addrs, err := caddy.ParseListenAddr(lnAddr)
|
||||||
|
|
|
@ -24,21 +24,20 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(caddy.Module{
|
||||||
Name: "http.responders.static",
|
Name: "http.handlers.static",
|
||||||
New: func() interface{} { return new(Static) },
|
New: func() interface{} { return new(Static) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static implements a simple responder for static responses.
|
// Static implements a simple responder for static responses.
|
||||||
type Static struct {
|
type Static struct {
|
||||||
StatusCode int `json:"status_code"` // TODO: should we turn this into a string so that only one field is needed? (string allows replacements)
|
StatusCode string `json:"status_code"`
|
||||||
StatusCodeStr string `json:"status_code_str"`
|
Headers http.Header `json:"headers"`
|
||||||
Headers http.Header `json:"headers"`
|
Body string `json:"body"`
|
||||||
Body string `json:"body"`
|
Close bool `json:"close"`
|
||||||
Close bool `json:"close"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
|
||||||
// close the connection after responding
|
// close the connection after responding
|
||||||
|
@ -60,16 +59,13 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the status code
|
// get the status code
|
||||||
statusCode := s.StatusCode
|
statusCode := http.StatusOK
|
||||||
if statusCode == 0 && s.StatusCodeStr != "" {
|
if s.StatusCode != "" {
|
||||||
intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCodeStr, ""))
|
intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCode, ""))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
statusCode = intVal
|
statusCode = intVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if statusCode == 0 {
|
|
||||||
statusCode = http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
// write headers
|
// write headers
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
|
@ -83,4 +79,4 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
var _ Handler = (*Static)(nil)
|
var _ MiddlewareHandler = (*Static)(nil)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
@ -29,7 +30,7 @@ func TestStaticResponseHandler(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
s := Static{
|
s := Static{
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: strconv.Itoa(http.StatusNotFound),
|
||||||
Headers: http.Header{
|
Headers: http.Header{
|
||||||
"X-Test": []string{"Testing"},
|
"X-Test": []string{"Testing"},
|
||||||
},
|
},
|
||||||
|
@ -37,7 +38,7 @@ func TestStaticResponseHandler(t *testing.T) {
|
||||||
Close: true,
|
Close: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.ServeHTTP(w, r)
|
err := s.ServeHTTP(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("did not expect an error, but got: %v", err)
|
t.Errorf("did not expect an error, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(caddy.Module{
|
||||||
Name: "http.middleware.templates",
|
Name: "http.handlers.templates",
|
||||||
New: func() interface{} { return new(Templates) },
|
New: func() interface{} { return new(Templates) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue