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{