aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--modules/caddyhttp/caddyhttp.go4
-rw-r--r--modules/caddyhttp/fileserver/matcher.go177
-rw-r--r--modules/caddyhttp/fileserver/staticfiles.go164
-rw-r--r--modules/caddyhttp/routes.go2
-rw-r--r--modules/caddyhttp/server.go4
-rw-r--r--modules/caddyhttp/staticerror.go95
-rw-r--r--modules/caddyhttp/staticresp.go23
-rw-r--r--modules/caddyhttp/staticresp_test.go4
-rw-r--r--modules/caddyhttp/subroute.go60
-rw-r--r--modules/caddyhttp/templates/templates.go10
10 files changed, 331 insertions, 212 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 330e135fa..ae73c9820 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -323,8 +323,8 @@ func (app *App) automaticHTTPS() error {
},
},
handlers: []MiddlewareHandler{
- Static{
- StatusCode: strconv.Itoa(http.StatusTemporaryRedirect), // TODO: use permanent redirect instead
+ StaticResponse{
+ StatusCode: weakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
Headers: http.Header{
"Location": []string{redirTo},
"Connection": []string{"close"},
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index da36eaa9d..023a69b8f 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -15,55 +15,166 @@
package fileserver
import (
+ "fmt"
"net/http"
"os"
+ "time"
+ "github.com/caddyserver/caddy/modules/caddyhttp"
"github.com/caddyserver/caddy/v2"
- "github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.file",
- New: func() interface{} { return new(FileMatcher) },
+ New: func() interface{} { return new(MatchFile) },
})
}
-// FileMatcher is a matcher that can match requests
-// based on the local file system.
-// TODO: Not sure how to do this well; we'd need the ability to
-// hide files, etc...
-// TODO: Also consider a feature to match directory that
-// contains a certain filename (use filepath.Glob), useful
-// if wanting to map directory-URI requests where the dir
-// has index.php to PHP backends, for example (although this
-// can effectively be done with rehandling already)
-type FileMatcher struct {
- Root string `json:"root"`
- Path string `json:"path"`
- Flags []string `json:"flags"`
+// MatchFile is an HTTP request matcher that can match
+// requests based upon file existence.
+type MatchFile struct {
+ // The root directory, used for creating absolute
+ // file paths, and required when working with
+ // relative paths; if not specified, the current
+ // directory is assumed. Accepts placeholders.
+ Root string `json:"root,omitempty"`
+
+ // The list of files to try. Each path here is
+ // considered relatice to Root. If nil, the
+ // request URL's path will be assumed. Accepts
+ // placeholders.
+ TryFiles []string `json:"try_files,omitempty"`
+
+ // How to choose a file in TryFiles.
+ // Default is first_exist.
+ TryPolicy string `json:"try_policy,omitempty"`
+}
+
+// Validate ensures m has a valid configuration.
+func (m MatchFile) Validate() error {
+ switch m.TryPolicy {
+ case "",
+ tryPolicyFirstExist,
+ tryPolicyLargestSize,
+ tryPolicySmallestSize,
+ tryPolicyMostRecentMod:
+ default:
+ return fmt.Errorf("unknown try policy %s", m.TryPolicy)
+ }
+ return nil
+}
+
+// Match returns true if r matches m. Returns true
+// if a file was matched. If so, two placeholders
+// will be available:
+// - http.matchers.file.relative
+// - http.matchers.file.absolute
+func (m MatchFile) Match(r *http.Request) bool {
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+ rel, abs, matched := m.selectFile(r)
+ if matched {
+ repl.Set("http.matchers.file.relative", rel)
+ repl.Set("http.matchers.file.absolute", abs)
+ return true
+ }
+ return false
}
-// Match matches the request r against m.
-func (m FileMatcher) Match(r *http.Request) bool {
- fullPath := sanitizedPathJoin(m.Root, m.Path)
- var match bool
- if len(m.Flags) > 0 {
- match = true
- fi, err := os.Stat(fullPath)
- for _, f := range m.Flags {
- switch f {
- case "EXIST":
- match = match && os.IsNotExist(err)
- case "DIR":
- match = match && err == nil && fi.IsDir()
- default:
- match = false
+// selectFile chooses a file according to m.TryPolicy by appending
+// the paths in m.TryFiles to m.Root, with placeholder replacements.
+// It returns the root-relative path to the matched file, the full
+// or absolute path, and whether a match was made.
+func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+
+ root := repl.ReplaceAll(m.Root, "")
+
+ // if list of files to try was omitted entirely,
+ // assume URL path
+ if m.TryFiles == nil {
+ // m is not a pointer, so this is safe
+ m.TryFiles = []string{r.URL.Path}
+ }
+
+ switch m.TryPolicy {
+ case "", tryPolicyFirstExist:
+ for _, f := range m.TryFiles {
+ suffix := repl.ReplaceAll(f, "")
+ fullpath := sanitizedPathJoin(root, suffix)
+ if fileExists(fullpath) {
+ return suffix, fullpath, true
}
}
+
+ case tryPolicyLargestSize:
+ var largestSize int64
+ var largestFilename string
+ var largestSuffix string
+ for _, f := range m.TryFiles {
+ suffix := repl.ReplaceAll(f, "")
+ fullpath := sanitizedPathJoin(root, suffix)
+ info, err := os.Stat(fullpath)
+ if err == nil && info.Size() > largestSize {
+ largestSize = info.Size()
+ largestFilename = fullpath
+ largestSuffix = suffix
+ }
+ }
+ return largestSuffix, largestFilename, true
+
+ case tryPolicySmallestSize:
+ var smallestSize int64
+ var smallestFilename string
+ var smallestSuffix string
+ for _, f := range m.TryFiles {
+ suffix := repl.ReplaceAll(f, "")
+ fullpath := sanitizedPathJoin(root, suffix)
+ info, err := os.Stat(fullpath)
+ if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
+ smallestSize = info.Size()
+ smallestFilename = fullpath
+ smallestSuffix = suffix
+ }
+ }
+ return smallestSuffix, smallestFilename, true
+
+ case tryPolicyMostRecentMod:
+ var recentDate time.Time
+ var recentFilename string
+ var recentSuffix string
+ for _, f := range m.TryFiles {
+ suffix := repl.ReplaceAll(f, "")
+ fullpath := sanitizedPathJoin(root, suffix)
+ info, err := os.Stat(fullpath)
+ if err == nil &&
+ (recentDate.IsZero() || info.ModTime().After(recentDate)) {
+ recentDate = info.ModTime()
+ recentFilename = fullpath
+ recentSuffix = suffix
+ }
+ }
+ return recentSuffix, recentFilename, true
}
- return match
+
+ return
+}
+
+// fileExists returns true if file exists.
+func fileExists(file string) bool {
+ _, err := os.Stat(file)
+ return !os.IsNotExist(err)
}
-// Interface guard
-var _ caddyhttp.RequestMatcher = (*FileMatcher)(nil)
+const (
+ tryPolicyFirstExist = "first_exist"
+ tryPolicyLargestSize = "largest_size"
+ tryPolicySmallestSize = "smallest_size"
+ tryPolicyMostRecentMod = "most_recent_modified"
+)
+
+// Interface guards
+var (
+ _ caddy.Validator = (*MatchFile)(nil)
+ _ caddyhttp.RequestMatcher = (*MatchFile)(nil)
+)
diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go
index a7c72c9de..761dfc3cb 100644
--- a/modules/caddyhttp/fileserver/staticfiles.go
+++ b/modules/caddyhttp/fileserver/staticfiles.go
@@ -42,27 +42,15 @@ func init() {
// FileServer implements a static file server responder for Caddy.
type FileServer struct {
- Root string `json:"root,omitempty"` // default is current directory
- Hide []string `json:"hide,omitempty"`
- IndexNames []string `json:"index_names,omitempty"`
- Files []string `json:"files,omitempty"` // all relative to the root; default is request URI path
- SelectionPolicy string `json:"selection_policy,omitempty"`
- Rehandle bool `json:"rehandle,omitempty"` // issue a rehandle (internal redirect) if request is rewritten
- Fallback caddyhttp.RouteList `json:"fallback,omitempty"`
- Browse *Browse `json:"browse,omitempty"`
- // TODO: Etag
+ Root string `json:"root,omitempty"` // default is current directory
+ Hide []string `json:"hide,omitempty"`
+ IndexNames []string `json:"index_names,omitempty"`
+ Browse *Browse `json:"browse,omitempty"`
// TODO: Content negotiation
}
// Provision sets up the static files responder.
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
- if fsrv.Fallback != nil {
- err := fsrv.Fallback.Provision(ctx)
- if err != nil {
- return fmt.Errorf("setting up fallback routes: %v", err)
- }
- }
-
if fsrv.IndexNames == nil {
fsrv.IndexNames = defaultIndexNames
}
@@ -87,50 +75,14 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
return nil
}
-const (
- selectionPolicyFirstExisting = "first_existing"
- selectionPolicyLargestSize = "largest_size"
- selectionPolicySmallestSize = "smallest_size"
- selectionPolicyRecentlyMod = "most_recently_modified"
-)
-
-// Validate ensures that sf has a valid configuration.
-func (fsrv *FileServer) Validate() error {
- switch fsrv.SelectionPolicy {
- case "",
- selectionPolicyFirstExisting,
- selectionPolicyLargestSize,
- selectionPolicySmallestSize,
- selectionPolicyRecentlyMod:
- default:
- return fmt.Errorf("unknown selection policy %s", fsrv.SelectionPolicy)
- }
- return nil
-}
-
func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
filesToHide := fsrv.transformHidePaths(repl)
- // map the request to a filename
- pathBefore := r.URL.Path
- filename := fsrv.selectFile(r, repl, filesToHide)
- if filename == "" {
- // no files worked, so resort to fallback
- if fsrv.Fallback != nil {
- fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
- return fallback.ServeHTTP(w, r)
- }
- return caddyhttp.Error(http.StatusNotFound, nil)
- }
-
- // if the ultimate destination has changed, submit
- // this request for a rehandling (internal redirect)
- // if configured to do so
- if r.URL.Path != pathBefore && fsrv.Rehandle {
- return caddyhttp.ErrRehandle
- }
+ root := repl.ReplaceAll(fsrv.Root, "")
+ suffix := repl.ReplaceAll(r.URL.Path, "")
+ filename := sanitizedPathJoin(root, suffix)
// get information about the file
info, err := os.Stat(filename)
@@ -161,12 +113,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
}
// we found an index file that might work,
- // so rewrite the request path and, if
- // configured, do an internal redirect
+ // so rewrite the request path
r.URL.Path = path.Join(r.URL.Path, indexPage)
- if fsrv.Rehandle {
- return caddyhttp.ErrRehandle
- }
info = indexInfo
filename = indexPath
@@ -308,107 +256,12 @@ func sanitizedPathJoin(root, reqPath string) string {
return filepath.Join(root, filepath.FromSlash(path.Clean("/"+reqPath)))
}
-// selectFile uses the specified selection policy (or first_existing
-// by default) to map the request r to a filename. The full path to
-// the file is returned if one is found; otherwise, an empty string
-// is returned.
-func (fsrv *FileServer) selectFile(r *http.Request, repl caddy.Replacer, filesToHide []string) string {
- root := repl.ReplaceAll(fsrv.Root, "")
-
- if fsrv.Files == nil {
- return sanitizedPathJoin(root, r.URL.Path)
- }
-
- switch fsrv.SelectionPolicy {
- case "", selectionPolicyFirstExisting:
- filesToHide := fsrv.transformHidePaths(repl)
- for _, f := range fsrv.Files {
- suffix := repl.ReplaceAll(f, "")
- fullpath := sanitizedPathJoin(root, suffix)
- if !fileHidden(fullpath, filesToHide) && fileExists(fullpath) {
- r.URL.Path = suffix
- return fullpath
- }
- }
-
- case selectionPolicyLargestSize:
- var largestSize int64
- var largestFilename string
- var largestSuffix string
- for _, f := range fsrv.Files {
- suffix := repl.ReplaceAll(f, "")
- fullpath := sanitizedPathJoin(root, suffix)
- if fileHidden(fullpath, filesToHide) {
- continue
- }
- info, err := os.Stat(fullpath)
- if err == nil && info.Size() > largestSize {
- largestSize = info.Size()
- largestFilename = fullpath
- largestSuffix = suffix
- }
- }
- r.URL.Path = largestSuffix
- return largestFilename
-
- case selectionPolicySmallestSize:
- var smallestSize int64
- var smallestFilename string
- var smallestSuffix string
- for _, f := range fsrv.Files {
- suffix := repl.ReplaceAll(f, "")
- fullpath := sanitizedPathJoin(root, suffix)
- if fileHidden(fullpath, filesToHide) {
- continue
- }
- info, err := os.Stat(fullpath)
- if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
- smallestSize = info.Size()
- smallestFilename = fullpath
- smallestSuffix = suffix
- }
- }
- r.URL.Path = smallestSuffix
- return smallestFilename
-
- case selectionPolicyRecentlyMod:
- var recentDate time.Time
- var recentFilename string
- var recentSuffix string
- for _, f := range fsrv.Files {
- suffix := repl.ReplaceAll(f, "")
- fullpath := sanitizedPathJoin(root, suffix)
- if fileHidden(fullpath, filesToHide) {
- continue
- }
- info, err := os.Stat(fullpath)
- if err == nil &&
- (recentDate.IsZero() || info.ModTime().After(recentDate)) {
- recentDate = info.ModTime()
- recentFilename = fullpath
- recentSuffix = suffix
- }
- }
- r.URL.Path = recentSuffix
- return recentFilename
- }
-
- return ""
-}
-
-// fileExists returns true if file exists.
-func fileExists(file string) bool {
- _, err := os.Stat(file)
- return !os.IsNotExist(err)
-}
-
// fileHidden returns true if filename is hidden
// according to the hide list.
func fileHidden(filename string, hide []string) bool {
nameOnly := filepath.Base(filename)
sep := string(filepath.Separator)
- // see if file is hidden
for _, h := range hide {
// assuming h is a glob/shell-like pattern,
// use it to compare the whole file path;
@@ -453,6 +306,5 @@ const minBackoff, maxBackoff = 2, 5
// Interface guards
var (
_ caddy.Provisioner = (*FileServer)(nil)
- _ caddy.Validator = (*FileServer)(nil)
_ caddyhttp.MiddlewareHandler = (*FileServer)(nil)
)
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go
index e010bb6c1..b0672b118 100644
--- a/modules/caddyhttp/routes.go
+++ b/modules/caddyhttp/routes.go
@@ -107,7 +107,7 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
// BuildCompositeRoute creates a chain of handlers by
// applying all of the matching routes.
-func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) Handler {
+func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
if len(routes) == 0 {
return emptyHandler
}
diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go
index 8bc3a5a8c..a24bbac1c 100644
--- a/modules/caddyhttp/server.go
+++ b/modules/caddyhttp/server.go
@@ -65,7 +65,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
addHTTPVarsToReplacer(repl, r, w)
// build and execute the main handler chain
- stack := s.Routes.BuildCompositeRoute(w, r)
+ stack := s.Routes.BuildCompositeRoute(r)
stack = s.wrapPrimaryRoute(stack)
err := s.executeCompositeRoute(w, r, stack)
if err != nil {
@@ -85,7 +85,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if s.Errors != nil && len(s.Errors.Routes) > 0 {
- errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
+ errStack := s.Errors.Routes.BuildCompositeRoute(r)
err := s.executeCompositeRoute(w, r, errStack)
if err != nil {
// TODO: what should we do if the error handler has an error?
diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go
new file mode 100644
index 000000000..3a8e8bc53
--- /dev/null
+++ b/modules/caddyhttp/staticerror.go
@@ -0,0 +1,95 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package caddyhttp
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func init() {
+ caddy.RegisterModule(caddy.Module{
+ Name: "http.handlers.error",
+ New: func() interface{} { return new(StaticError) },
+ })
+}
+
+// StaticError implements a simple handler that returns an error.
+type StaticError struct {
+ Error string `json:"error,omitempty"`
+ StatusCode weakString `json:"status_code,omitempty"`
+}
+
+func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
+
+ statusCode := http.StatusInternalServerError
+ if codeStr := e.StatusCode.String(); codeStr != "" {
+ intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
+ if err != nil {
+ return Error(http.StatusInternalServerError, err)
+ }
+ statusCode = intVal
+ }
+
+ return Error(statusCode, fmt.Errorf("%s", e.Error))
+}
+
+// Interface guard
+var _ MiddlewareHandler = (*StaticError)(nil)
+
+// weakString is a type that unmarshals any JSON value
+// as a string literal, and provides methods for
+// getting the value as different primitive types.
+// However, using this type removes any type safety
+// as far as deserializing JSON is concerned.
+type weakString string
+
+// UnmarshalJSON satisfies json.Unmarshaler. It
+// unmarshals b by always interpreting it as a
+// string literal.
+func (ws *weakString) UnmarshalJSON(b []byte) error {
+ *ws = weakString(strings.Trim(string(b), `"`))
+ return nil
+}
+
+// Int returns ws as an integer. If ws is not an
+// integer, 0 is returned.
+func (ws weakString) Int() int {
+ num, _ := strconv.Atoi(string(ws))
+ return num
+}
+
+// Float64 returns ws as a float64. If ws is not a
+// float value, the zero value is returned.
+func (ws weakString) Float64() float64 {
+ num, _ := strconv.ParseFloat(string(ws), 64)
+ return num
+}
+
+// Bool returns ws as a boolean. If ws is not a
+// boolean, false is returned.
+func (ws weakString) Bool() bool {
+ return string(ws) == "true"
+}
+
+// String returns ws as a string.
+func (ws weakString) String() string {
+ return string(ws)
+}
diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go
index 8e4a3df15..291d99241 100644
--- a/modules/caddyhttp/staticresp.go
+++ b/modules/caddyhttp/staticresp.go
@@ -24,20 +24,20 @@ import (
func init() {
caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.static",
- New: func() interface{} { return new(Static) },
+ Name: "http.handlers.static_response",
+ New: func() interface{} { return new(StaticResponse) },
})
}
-// Static implements a simple responder for static responses.
-type Static struct {
- StatusCode string `json:"status_code"`
+// StaticResponse implements a simple responder for static responses.
+type StaticResponse struct {
+ StatusCode weakString `json:"status_code"`
Headers http.Header `json:"headers"`
Body string `json:"body"`
Close bool `json:"close"`
}
-func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
+func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
// close the connection after responding
@@ -60,11 +60,12 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) err
// get the status code
statusCode := http.StatusOK
- if s.StatusCode != "" {
- intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCode, ""))
- if err == nil {
- statusCode = intVal
+ if codeStr := s.StatusCode.String(); codeStr != "" {
+ intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
+ if err != nil {
+ return Error(http.StatusInternalServerError, err)
}
+ statusCode = intVal
}
// write headers
@@ -79,4 +80,4 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) err
}
// Interface guard
-var _ MiddlewareHandler = (*Static)(nil)
+var _ MiddlewareHandler = (*StaticResponse)(nil)
diff --git a/modules/caddyhttp/staticresp_test.go b/modules/caddyhttp/staticresp_test.go
index 111e4f374..49adedd0c 100644
--- a/modules/caddyhttp/staticresp_test.go
+++ b/modules/caddyhttp/staticresp_test.go
@@ -29,8 +29,8 @@ func TestStaticResponseHandler(t *testing.T) {
r := fakeRequest()
w := httptest.NewRecorder()
- s := Static{
- StatusCode: strconv.Itoa(http.StatusNotFound),
+ s := StaticResponse{
+ StatusCode: weakString(strconv.Itoa(http.StatusNotFound)),
Headers: http.Header{
"X-Test": []string{"Testing"},
},
diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go
new file mode 100644
index 000000000..917214655
--- /dev/null
+++ b/modules/caddyhttp/subroute.go
@@ -0,0 +1,60 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package caddyhttp
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func init() {
+ caddy.RegisterModule(caddy.Module{
+ Name: "http.handlers.subroute",
+ New: func() interface{} { return new(Subroute) },
+ })
+}
+
+// Subroute implements a handler that compiles and executes routes.
+// This is useful for a batch of routes that all inherit the same
+// matchers, or for routes with matchers that must be have deferred
+// evaluation (e.g. if they depend on placeholders created by other
+// matchers that need to be evaluated first).
+type Subroute struct {
+ Routes RouteList `json:"routes,omitempty"`
+}
+
+// Provision sets up subrouting.
+func (sr *Subroute) Provision(ctx caddy.Context) error {
+ if sr.Routes != nil {
+ err := sr.Routes.Provision(ctx)
+ if err != nil {
+ return fmt.Errorf("setting up routes: %v", err)
+ }
+ }
+ return nil
+}
+
+func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
+ subroute := sr.Routes.BuildCompositeRoute(r)
+ return subroute.ServeHTTP(w, r)
+}
+
+// Interface guards
+var (
+ _ caddy.Provisioner = (*Subroute)(nil)
+ _ MiddlewareHandler = (*Subroute)(nil)
+)
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index 85b0bc296..9a41b6d7c 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -35,9 +35,9 @@ func init() {
// Templates is a middleware which execute response bodies as templates.
type Templates struct {
- FileRoot string `json:"file_root,omitempty"`
- MIMETypes []string `json:"mime_types,omitempty"`
- Delimiters []string `json:"delimiters,omitempty"`
+ IncludeRoot string `json:"include_root,omitempty"`
+ MIMETypes []string `json:"mime_types,omitempty"`
+ Delimiters []string `json:"delimiters,omitempty"`
}
// Provision provisions t.
@@ -107,8 +107,8 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
// executeTemplate executes the template contained in wb.buf and replaces it with the results.
func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error {
var fs http.FileSystem
- if t.FileRoot != "" {
- fs = http.Dir(t.FileRoot)
+ if t.IncludeRoot != "" {
+ fs = http.Dir(t.IncludeRoot)
}
ctx := &templateContext{