@marcel91200 / caddy Mirror · read-only / commits / eb8625f7
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

Add error & subroute handlers; weakString; other minor handler changes

Matthew Holt committed Jul 11, 2019 at 17:02 UTC eb8625f7744ba5e72b51549adc086e45313267cb
10 files changed +331 -212
modules/caddyhttp/caddyhttp.go
+2 -2
@@ -323,8 +323,8 @@ func (app *App) automaticHTTPS() error {
323 },
324 },
325 handlers: []MiddlewareHandler{
326 - Static{
327 - StatusCode: strconv.Itoa(http.StatusTemporaryRedirect), // TODO: use permanent redirect instead
326 + StaticResponse{
327 + StatusCode: weakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
328 Headers: http.Header{
329 "Location": []string{redirTo},
330 "Connection": []string{"close"},
modules/caddyhttp/fileserver/matcher.go
+144 -33
@@ -15,55 +15,166 @@
15 package fileserver
16
17 import (
18 + "fmt"
19 "net/http"
20 "os"
21 + "time"
22
23 + "github.com/caddyserver/caddy/modules/caddyhttp"
24 "github.com/caddyserver/caddy/v2"
22 - "github.com/caddyserver/caddy/v2/modules/caddyhttp"
25 )
26
27 func init() {
28 caddy.RegisterModule(caddy.Module{
29 Name: "http.matchers.file",
28 - New: func() interface{} { return new(FileMatcher) },
30 + New: func() interface{} { return new(MatchFile) },
31 })
32 }
33
32 -// FileMatcher is a matcher that can match requests
33 -// based on the local file system.
34 -// TODO: Not sure how to do this well; we'd need the ability to
35 -// hide files, etc...
36 -// TODO: Also consider a feature to match directory that
37 -// contains a certain filename (use filepath.Glob), useful
38 -// if wanting to map directory-URI requests where the dir
39 -// has index.php to PHP backends, for example (although this
40 -// can effectively be done with rehandling already)
41 -type FileMatcher struct {
42 - Root string `json:"root"`
43 - Path string `json:"path"`
44 - Flags []string `json:"flags"`
34 +// MatchFile is an HTTP request matcher that can match
35 +// requests based upon file existence.
36 +type MatchFile struct {
37 + // The root directory, used for creating absolute
38 + // file paths, and required when working with
39 + // relative paths; if not specified, the current
40 + // directory is assumed. Accepts placeholders.
41 + Root string `json:"root,omitempty"`
42 +
43 + // The list of files to try. Each path here is
44 + // considered relatice to Root. If nil, the
45 + // request URL's path will be assumed. Accepts
46 + // placeholders.
47 + TryFiles []string `json:"try_files,omitempty"`
48 +
49 + // How to choose a file in TryFiles.
50 + // Default is first_exist.
51 + TryPolicy string `json:"try_policy,omitempty"`
52 +}
53 +
54 +// Validate ensures m has a valid configuration.
55 +func (m MatchFile) Validate() error {
56 + switch m.TryPolicy {
57 + case "",
58 + tryPolicyFirstExist,
59 + tryPolicyLargestSize,
60 + tryPolicySmallestSize,
61 + tryPolicyMostRecentMod:
62 + default:
63 + return fmt.Errorf("unknown try policy %s", m.TryPolicy)
64 + }
65 + return nil
66 +}
67 +
68 +// Match returns true if r matches m. Returns true
69 +// if a file was matched. If so, two placeholders
70 +// will be available:
71 +// - http.matchers.file.relative
72 +// - http.matchers.file.absolute
73 +func (m MatchFile) Match(r *http.Request) bool {
74 + repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
75 + rel, abs, matched := m.selectFile(r)
76 + if matched {
77 + repl.Set("http.matchers.file.relative", rel)
78 + repl.Set("http.matchers.file.absolute", abs)
79 + return true
80 + }
81 + return false
82 }
83
47 -// Match matches the request r against m.
48 -func (m FileMatcher) Match(r *http.Request) bool {
49 - fullPath := sanitizedPathJoin(m.Root, m.Path)
50 - var match bool
51 - if len(m.Flags) > 0 {
52 - match = true
53 - fi, err := os.Stat(fullPath)
54 - for _, f := range m.Flags {
55 - switch f {
56 - case "EXIST":
57 - match = match && os.IsNotExist(err)
58 - case "DIR":
59 - match = match && err == nil && fi.IsDir()
60 - default:
61 - match = false
84 +// selectFile chooses a file according to m.TryPolicy by appending
85 +// the paths in m.TryFiles to m.Root, with placeholder replacements.
86 +// It returns the root-relative path to the matched file, the full
87 +// or absolute path, and whether a match was made.
88 +func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
89 + repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
90 +
91 + root := repl.ReplaceAll(m.Root, "")
92 +
93 + // if list of files to try was omitted entirely,
94 + // assume URL path
95 + if m.TryFiles == nil {
96 + // m is not a pointer, so this is safe
97 + m.TryFiles = []string{r.URL.Path}
98 + }
99 +
100 + switch m.TryPolicy {
101 + case "", tryPolicyFirstExist:
102 + for _, f := range m.TryFiles {
103 + suffix := repl.ReplaceAll(f, "")
104 + fullpath := sanitizedPathJoin(root, suffix)
105 + if fileExists(fullpath) {
106 + return suffix, fullpath, true
107 }
108 }
109 +
110 + case tryPolicyLargestSize:
111 + var largestSize int64
112 + var largestFilename string
113 + var largestSuffix string
114 + for _, f := range m.TryFiles {
115 + suffix := repl.ReplaceAll(f, "")
116 + fullpath := sanitizedPathJoin(root, suffix)
117 + info, err := os.Stat(fullpath)
118 + if err == nil && info.Size() > largestSize {
119 + largestSize = info.Size()
120 + largestFilename = fullpath
121 + largestSuffix = suffix
122 + }
123 + }
124 + return largestSuffix, largestFilename, true
125 +
126 + case tryPolicySmallestSize:
127 + var smallestSize int64
128 + var smallestFilename string
129 + var smallestSuffix string
130 + for _, f := range m.TryFiles {
131 + suffix := repl.ReplaceAll(f, "")
132 + fullpath := sanitizedPathJoin(root, suffix)
133 + info, err := os.Stat(fullpath)
134 + if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
135 + smallestSize = info.Size()
136 + smallestFilename = fullpath
137 + smallestSuffix = suffix
138 + }
139 + }
140 + return smallestSuffix, smallestFilename, true
141 +
142 + case tryPolicyMostRecentMod:
143 + var recentDate time.Time
144 + var recentFilename string
145 + var recentSuffix string
146 + for _, f := range m.TryFiles {
147 + suffix := repl.ReplaceAll(f, "")
148 + fullpath := sanitizedPathJoin(root, suffix)
149 + info, err := os.Stat(fullpath)
150 + if err == nil &&
151 + (recentDate.IsZero() || info.ModTime().After(recentDate)) {
152 + recentDate = info.ModTime()
153 + recentFilename = fullpath
154 + recentSuffix = suffix
155 + }
156 + }
157 + return recentSuffix, recentFilename, true
158 }
65 - return match
159 +
160 + return
161 +}
162 +
163 +// fileExists returns true if file exists.
164 +func fileExists(file string) bool {
165 + _, err := os.Stat(file)
166 + return !os.IsNotExist(err)
167 }
168
68 -// Interface guard
69 -var _ caddyhttp.RequestMatcher = (*FileMatcher)(nil)
169 +const (
170 + tryPolicyFirstExist = "first_exist"
171 + tryPolicyLargestSize = "largest_size"
172 + tryPolicySmallestSize = "smallest_size"
173 + tryPolicyMostRecentMod = "most_recent_modified"
174 +)
175 +
176 +// Interface guards
177 +var (
178 + _ caddy.Validator = (*MatchFile)(nil)
179 + _ caddyhttp.RequestMatcher = (*MatchFile)(nil)
180 +)
modules/caddyhttp/fileserver/staticfiles.go
+8 -156
@@ -42,27 +42,15 @@ func init() {
42
43 // FileServer implements a static file server responder for Caddy.
44 type FileServer struct {
45 - Root string `json:"root,omitempty"` // default is current directory
46 - Hide []string `json:"hide,omitempty"`
47 - IndexNames []string `json:"index_names,omitempty"`
48 - Files []string `json:"files,omitempty"` // all relative to the root; default is request URI path
49 - SelectionPolicy string `json:"selection_policy,omitempty"`
50 - Rehandle bool `json:"rehandle,omitempty"` // issue a rehandle (internal redirect) if request is rewritten
51 - Fallback caddyhttp.RouteList `json:"fallback,omitempty"`
52 - Browse *Browse `json:"browse,omitempty"`
53 - // TODO: Etag
45 + Root string `json:"root,omitempty"` // default is current directory
46 + Hide []string `json:"hide,omitempty"`
47 + IndexNames []string `json:"index_names,omitempty"`
48 + Browse *Browse `json:"browse,omitempty"`
49 // TODO: Content negotiation
50 }
51
52 // Provision sets up the static files responder.
53 func (fsrv *FileServer) Provision(ctx caddy.Context) error {
59 - if fsrv.Fallback != nil {
60 - err := fsrv.Fallback.Provision(ctx)
61 - if err != nil {
62 - return fmt.Errorf("setting up fallback routes: %v", err)
63 - }
64 - }
65 -
54 if fsrv.IndexNames == nil {
55 fsrv.IndexNames = defaultIndexNames
56 }
@@ -87,50 +75,14 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
75 return nil
76 }
77
90 -const (
91 - selectionPolicyFirstExisting = "first_existing"
92 - selectionPolicyLargestSize = "largest_size"
93 - selectionPolicySmallestSize = "smallest_size"
94 - selectionPolicyRecentlyMod = "most_recently_modified"
95 -)
96 -
97 -// Validate ensures that sf has a valid configuration.
98 -func (fsrv *FileServer) Validate() error {
99 - switch fsrv.SelectionPolicy {
100 - case "",
101 - selectionPolicyFirstExisting,
102 - selectionPolicyLargestSize,
103 - selectionPolicySmallestSize,
104 - selectionPolicyRecentlyMod:
105 - default:
106 - return fmt.Errorf("unknown selection policy %s", fsrv.SelectionPolicy)
107 - }
108 - return nil
109 -}
110 -
78 func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
79 repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
80
81 filesToHide := fsrv.transformHidePaths(repl)
82
116 - // map the request to a filename
117 - pathBefore := r.URL.Path
118 - filename := fsrv.selectFile(r, repl, filesToHide)
119 - if filename == "" {
120 - // no files worked, so resort to fallback
121 - if fsrv.Fallback != nil {
122 - fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
123 - return fallback.ServeHTTP(w, r)
124 - }
125 - return caddyhttp.Error(http.StatusNotFound, nil)
126 - }
127 -
128 - // if the ultimate destination has changed, submit
129 - // this request for a rehandling (internal redirect)
130 - // if configured to do so
131 - if r.URL.Path != pathBefore && fsrv.Rehandle {
132 - return caddyhttp.ErrRehandle
133 - }
83 + root := repl.ReplaceAll(fsrv.Root, "")
84 + suffix := repl.ReplaceAll(r.URL.Path, "")
85 + filename := sanitizedPathJoin(root, suffix)
86
87 // get information about the file
88 info, err := os.Stat(filename)
@@ -161,12 +113,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd
113 }
114
115 // we found an index file that might work,
164 - // so rewrite the request path and, if
165 - // configured, do an internal redirect
116 + // so rewrite the request path
117 r.URL.Path = path.Join(r.URL.Path, indexPage)
167 - if fsrv.Rehandle {
168 - return caddyhttp.ErrRehandle
169 - }
118
119 info = indexInfo
120 filename = indexPath
@@ -308,107 +256,12 @@ func sanitizedPathJoin(root, reqPath string) string {
256 return filepath.Join(root, filepath.FromSlash(path.Clean("/"+reqPath)))
257 }
258
311 -// selectFile uses the specified selection policy (or first_existing
312 -// by default) to map the request r to a filename. The full path to
313 -// the file is returned if one is found; otherwise, an empty string
314 -// is returned.
315 -func (fsrv *FileServer) selectFile(r *http.Request, repl caddy.Replacer, filesToHide []string) string {
316 - root := repl.ReplaceAll(fsrv.Root, "")
317 -
318 - if fsrv.Files == nil {
319 - return sanitizedPathJoin(root, r.URL.Path)
320 - }
321 -
322 - switch fsrv.SelectionPolicy {
323 - case "", selectionPolicyFirstExisting:
324 - filesToHide := fsrv.transformHidePaths(repl)
325 - for _, f := range fsrv.Files {
326 - suffix := repl.ReplaceAll(f, "")
327 - fullpath := sanitizedPathJoin(root, suffix)
328 - if !fileHidden(fullpath, filesToHide) && fileExists(fullpath) {
329 - r.URL.Path = suffix
330 - return fullpath
331 - }
332 - }
333 -
334 - case selectionPolicyLargestSize:
335 - var largestSize int64
336 - var largestFilename string
337 - var largestSuffix string
338 - for _, f := range fsrv.Files {
339 - suffix := repl.ReplaceAll(f, "")
340 - fullpath := sanitizedPathJoin(root, suffix)
341 - if fileHidden(fullpath, filesToHide) {
342 - continue
343 - }
344 - info, err := os.Stat(fullpath)
345 - if err == nil && info.Size() > largestSize {
346 - largestSize = info.Size()
347 - largestFilename = fullpath
348 - largestSuffix = suffix
349 - }
350 - }
351 - r.URL.Path = largestSuffix
352 - return largestFilename
353 -
354 - case selectionPolicySmallestSize:
355 - var smallestSize int64
356 - var smallestFilename string
357 - var smallestSuffix string
358 - for _, f := range fsrv.Files {
359 - suffix := repl.ReplaceAll(f, "")
360 - fullpath := sanitizedPathJoin(root, suffix)
361 - if fileHidden(fullpath, filesToHide) {
362 - continue
363 - }
364 - info, err := os.Stat(fullpath)
365 - if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
366 - smallestSize = info.Size()
367 - smallestFilename = fullpath
368 - smallestSuffix = suffix
369 - }
370 - }
371 - r.URL.Path = smallestSuffix
372 - return smallestFilename
373 -
374 - case selectionPolicyRecentlyMod:
375 - var recentDate time.Time
376 - var recentFilename string
377 - var recentSuffix string
378 - for _, f := range fsrv.Files {
379 - suffix := repl.ReplaceAll(f, "")
380 - fullpath := sanitizedPathJoin(root, suffix)
381 - if fileHidden(fullpath, filesToHide) {
382 - continue
383 - }
384 - info, err := os.Stat(fullpath)
385 - if err == nil &&
386 - (recentDate.IsZero() || info.ModTime().After(recentDate)) {
387 - recentDate = info.ModTime()
388 - recentFilename = fullpath
389 - recentSuffix = suffix
390 - }
391 - }
392 - r.URL.Path = recentSuffix
393 - return recentFilename
394 - }
395 -
396 - return ""
397 -}
398 -
399 -// fileExists returns true if file exists.
400 -func fileExists(file string) bool {
401 - _, err := os.Stat(file)
402 - return !os.IsNotExist(err)
403 -}
404 -
259 // fileHidden returns true if filename is hidden
260 // according to the hide list.
261 func fileHidden(filename string, hide []string) bool {
262 nameOnly := filepath.Base(filename)
263 sep := string(filepath.Separator)
264
411 - // see if file is hidden
265 for _, h := range hide {
266 // assuming h is a glob/shell-like pattern,
267 // use it to compare the whole file path;
@@ -453,6 +306,5 @@ const minBackoff, maxBackoff = 2, 5
306 // Interface guards
307 var (
308 _ caddy.Provisioner = (*FileServer)(nil)
456 - _ caddy.Validator = (*FileServer)(nil)
309 _ caddyhttp.MiddlewareHandler = (*FileServer)(nil)
310 )
modules/caddyhttp/routes.go
+1 -1
@@ -107,7 +107,7 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
107
108 // BuildCompositeRoute creates a chain of handlers by
109 // applying all of the matching routes.
110 -func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) Handler {
110 +func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
111 if len(routes) == 0 {
112 return emptyHandler
113 }
modules/caddyhttp/server.go
+2 -2
@@ -65,7 +65,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65 addHTTPVarsToReplacer(repl, r, w)
66
67 // build and execute the main handler chain
68 - stack := s.Routes.BuildCompositeRoute(w, r)
68 + stack := s.Routes.BuildCompositeRoute(r)
69 stack = s.wrapPrimaryRoute(stack)
70 err := s.executeCompositeRoute(w, r, stack)
71 if err != nil {
@@ -85,7 +85,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
85 }
86
87 if s.Errors != nil && len(s.Errors.Routes) > 0 {
88 - errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
88 + errStack := s.Errors.Routes.BuildCompositeRoute(r)
89 err := s.executeCompositeRoute(w, r, errStack)
90 if err != nil {
91 // TODO: what should we do if the error handler has an error?
modules/caddyhttp/staticerror.go new
+95
@@ -0,0 +1,95 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyhttp
16 +
17 +import (
18 + "fmt"
19 + "net/http"
20 + "strconv"
21 + "strings"
22 +
23 + "github.com/caddyserver/caddy/v2"
24 +)
25 +
26 +func init() {
27 + caddy.RegisterModule(caddy.Module{
28 + Name: "http.handlers.error",
29 + New: func() interface{} { return new(StaticError) },
30 + })
31 +}
32 +
33 +// StaticError implements a simple handler that returns an error.
34 +type StaticError struct {
35 + Error string `json:"error,omitempty"`
36 + StatusCode weakString `json:"status_code,omitempty"`
37 +}
38 +
39 +func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
40 + repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
41 +
42 + statusCode := http.StatusInternalServerError
43 + if codeStr := e.StatusCode.String(); codeStr != "" {
44 + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
45 + if err != nil {
46 + return Error(http.StatusInternalServerError, err)
47 + }
48 + statusCode = intVal
49 + }
50 +
51 + return Error(statusCode, fmt.Errorf("%s", e.Error))
52 +}
53 +
54 +// Interface guard
55 +var _ MiddlewareHandler = (*StaticError)(nil)
56 +
57 +// weakString is a type that unmarshals any JSON value
58 +// as a string literal, and provides methods for
59 +// getting the value as different primitive types.
60 +// However, using this type removes any type safety
61 +// as far as deserializing JSON is concerned.
62 +type weakString string
63 +
64 +// UnmarshalJSON satisfies json.Unmarshaler. It
65 +// unmarshals b by always interpreting it as a
66 +// string literal.
67 +func (ws *weakString) UnmarshalJSON(b []byte) error {
68 + *ws = weakString(strings.Trim(string(b), `"`))
69 + return nil
70 +}
71 +
72 +// Int returns ws as an integer. If ws is not an
73 +// integer, 0 is returned.
74 +func (ws weakString) Int() int {
75 + num, _ := strconv.Atoi(string(ws))
76 + return num
77 +}
78 +
79 +// Float64 returns ws as a float64. If ws is not a
80 +// float value, the zero value is returned.
81 +func (ws weakString) Float64() float64 {
82 + num, _ := strconv.ParseFloat(string(ws), 64)
83 + return num
84 +}
85 +
86 +// Bool returns ws as a boolean. If ws is not a
87 +// boolean, false is returned.
88 +func (ws weakString) Bool() bool {
89 + return string(ws) == "true"
90 +}
91 +
92 +// String returns ws as a string.
93 +func (ws weakString) String() string {
94 + return string(ws)
95 +}
modules/caddyhttp/staticresp.go
+12 -11
@@ -24,20 +24,20 @@ import (
24
25 func init() {
26 caddy.RegisterModule(caddy.Module{
27 - Name: "http.handlers.static",
28 - New: func() interface{} { return new(Static) },
27 + Name: "http.handlers.static_response",
28 + New: func() interface{} { return new(StaticResponse) },
29 })
30 }
31
32 -// Static implements a simple responder for static responses.
33 -type Static struct {
34 - StatusCode string `json:"status_code"`
32 +// StaticResponse implements a simple responder for static responses.
33 +type StaticResponse struct {
34 + StatusCode weakString `json:"status_code"`
35 Headers http.Header `json:"headers"`
36 Body string `json:"body"`
37 Close bool `json:"close"`
38 }
39
40 -func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
40 +func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
41 repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
42
43 // close the connection after responding
@@ -60,11 +60,12 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) err
60
61 // get the status code
62 statusCode := http.StatusOK
63 - if s.StatusCode != "" {
64 - intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCode, ""))
65 - if err == nil {
66 - statusCode = intVal
63 + if codeStr := s.StatusCode.String(); codeStr != "" {
64 + intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
65 + if err != nil {
66 + return Error(http.StatusInternalServerError, err)
67 }
68 + statusCode = intVal
69 }
70
71 // write headers
@@ -79,4 +80,4 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) err
80 }
81
82 // Interface guard
82 -var _ MiddlewareHandler = (*Static)(nil)
83 +var _ MiddlewareHandler = (*StaticResponse)(nil)
modules/caddyhttp/staticresp_test.go
+2 -2
@@ -29,8 +29,8 @@ func TestStaticResponseHandler(t *testing.T) {
29 r := fakeRequest()
30 w := httptest.NewRecorder()
31
32 - s := Static{
33 - StatusCode: strconv.Itoa(http.StatusNotFound),
32 + s := StaticResponse{
33 + StatusCode: weakString(strconv.Itoa(http.StatusNotFound)),
34 Headers: http.Header{
35 "X-Test": []string{"Testing"},
36 },
modules/caddyhttp/subroute.go new
+60
@@ -0,0 +1,60 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyhttp
16 +
17 +import (
18 + "fmt"
19 + "net/http"
20 +
21 + "github.com/caddyserver/caddy/v2"
22 +)
23 +
24 +func init() {
25 + caddy.RegisterModule(caddy.Module{
26 + Name: "http.handlers.subroute",
27 + New: func() interface{} { return new(Subroute) },
28 + })
29 +}
30 +
31 +// Subroute implements a handler that compiles and executes routes.
32 +// This is useful for a batch of routes that all inherit the same
33 +// matchers, or for routes with matchers that must be have deferred
34 +// evaluation (e.g. if they depend on placeholders created by other
35 +// matchers that need to be evaluated first).
36 +type Subroute struct {
37 + Routes RouteList `json:"routes,omitempty"`
38 +}
39 +
40 +// Provision sets up subrouting.
41 +func (sr *Subroute) Provision(ctx caddy.Context) error {
42 + if sr.Routes != nil {
43 + err := sr.Routes.Provision(ctx)
44 + if err != nil {
45 + return fmt.Errorf("setting up routes: %v", err)
46 + }
47 + }
48 + return nil
49 +}
50 +
51 +func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
52 + subroute := sr.Routes.BuildCompositeRoute(r)
53 + return subroute.ServeHTTP(w, r)
54 +}
55 +
56 +// Interface guards
57 +var (
58 + _ caddy.Provisioner = (*Subroute)(nil)
59 + _ MiddlewareHandler = (*Subroute)(nil)
60 +)
modules/caddyhttp/templates/templates.go
+5 -5
@@ -35,9 +35,9 @@ func init() {
35
36 // Templates is a middleware which execute response bodies as templates.
37 type Templates struct {
38 - FileRoot string `json:"file_root,omitempty"`
39 - MIMETypes []string `json:"mime_types,omitempty"`
40 - Delimiters []string `json:"delimiters,omitempty"`
38 + IncludeRoot string `json:"include_root,omitempty"`
39 + MIMETypes []string `json:"mime_types,omitempty"`
40 + Delimiters []string `json:"delimiters,omitempty"`
41 }
42
43 // Provision provisions t.
@@ -107,8 +107,8 @@ func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
107 // executeTemplate executes the template contained in wb.buf and replaces it with the results.
108 func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Request) error {
109 var fs http.FileSystem
110 - if t.FileRoot != "" {
111 - fs = http.Dir(t.FileRoot)
110 + if t.IncludeRoot != "" {
111 + fs = http.Dir(t.IncludeRoot)
112 }
113
114 ctx := &templateContext{