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

httpcaddyfile: New `handle_path` directive (#3281)

* caddyconfig: WIP implementation of handle_path * caddyconfig: Complete the implementation - h.NewRoute was key * caddyconfig: Add handle_path integration test * caddyhttp: Use the path matcher as-is, strip the trailing *, update test

Francis Lavoie committed May 26, 2020 at 17:27 UTC 8c5d00b2bc815c182e1a510be6dddc128949bf23
4 files changed +186 -59
caddyconfig/httpcaddyfile/builtins.go
+2 -2
@@ -442,11 +442,11 @@ func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
442 }
443
444 func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
445 - return parseSegmentAsSubroute(h)
445 + return ParseSegmentAsSubroute(h)
446 }
447
448 func parseHandleErrors(h Helper) ([]ConfigValue, error) {
449 - subroute, err := parseSegmentAsSubroute(h)
449 + subroute, err := ParseSegmentAsSubroute(h)
450 if err != nil {
451 return nil, err
452 }
caddyconfig/httpcaddyfile/directives.go
+58 -57
@@ -57,6 +57,7 @@ var directiveOrder = []string{
57 // special routing directives
58 "handle",
59 "route",
60 + "handle_path",
61
62 // handlers that typically respond to requests
63 "respond",
@@ -261,6 +262,63 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
262 return []ConfigValue{{Class: "bind", Value: addrs}}
263 }
264
265 +// ParseSegmentAsSubroute parses the segment such that its subdirectives
266 +// are themselves treated as directives, from which a subroute is built
267 +// and returned.
268 +func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
269 + var allResults []ConfigValue
270 +
271 + for h.Next() {
272 + // slice the linear list of tokens into top-level segments
273 + var segments []caddyfile.Segment
274 + for nesting := h.Nesting(); h.NextBlock(nesting); {
275 + segments = append(segments, h.NextSegment())
276 + }
277 +
278 + // copy existing matcher definitions so we can augment
279 + // new ones that are defined only in this scope
280 + matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
281 + for key, val := range h.matcherDefs {
282 + matcherDefs[key] = val
283 + }
284 +
285 + // find and extract any embedded matcher definitions in this scope
286 + for i, seg := range segments {
287 + if strings.HasPrefix(seg.Directive(), matcherPrefix) {
288 + err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
289 + if err != nil {
290 + return nil, err
291 + }
292 + segments = append(segments[:i], segments[i+1:]...)
293 + }
294 + }
295 +
296 + // with matchers ready to go, evaluate each directive's segment
297 + for _, seg := range segments {
298 + dir := seg.Directive()
299 + dirFunc, ok := registeredDirectives[dir]
300 + if !ok {
301 + return nil, h.Errf("unrecognized directive: %s", dir)
302 + }
303 +
304 + subHelper := h
305 + subHelper.Dispenser = caddyfile.NewDispenser(seg)
306 + subHelper.matcherDefs = matcherDefs
307 +
308 + results, err := dirFunc(subHelper)
309 + if err != nil {
310 + return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
311 + }
312 + for _, result := range results {
313 + result.directive = dir
314 + allResults = append(allResults, result)
315 + }
316 + }
317 + }
318 +
319 + return buildSubroute(allResults, h.groupCounter)
320 +}
321 +
322 // ConfigValue represents a value to be added to the final
323 // configuration, or a value to be consulted when building
324 // the final configuration.
@@ -329,63 +387,6 @@ func sortRoutes(routes []ConfigValue) {
387 })
388 }
389
332 -// parseSegmentAsSubroute parses the segment such that its subdirectives
333 -// are themselves treated as directives, from which a subroute is built
334 -// and returned.
335 -func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
336 - var allResults []ConfigValue
337 -
338 - for h.Next() {
339 - // slice the linear list of tokens into top-level segments
340 - var segments []caddyfile.Segment
341 - for nesting := h.Nesting(); h.NextBlock(nesting); {
342 - segments = append(segments, h.NextSegment())
343 - }
344 -
345 - // copy existing matcher definitions so we can augment
346 - // new ones that are defined only in this scope
347 - matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
348 - for key, val := range h.matcherDefs {
349 - matcherDefs[key] = val
350 - }
351 -
352 - // find and extract any embedded matcher definitions in this scope
353 - for i, seg := range segments {
354 - if strings.HasPrefix(seg.Directive(), matcherPrefix) {
355 - err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
356 - if err != nil {
357 - return nil, err
358 - }
359 - segments = append(segments[:i], segments[i+1:]...)
360 - }
361 - }
362 -
363 - // with matchers ready to go, evaluate each directive's segment
364 - for _, seg := range segments {
365 - dir := seg.Directive()
366 - dirFunc, ok := registeredDirectives[dir]
367 - if !ok {
368 - return nil, h.Errf("unrecognized directive: %s", dir)
369 - }
370 -
371 - subHelper := h
372 - subHelper.Dispenser = caddyfile.NewDispenser(seg)
373 - subHelper.matcherDefs = matcherDefs
374 -
375 - results, err := dirFunc(subHelper)
376 - if err != nil {
377 - return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
378 - }
379 - for _, result := range results {
380 - result.directive = dir
381 - allResults = append(allResults, result)
382 - }
383 - }
384 - }
385 -
386 - return buildSubroute(allResults, h.groupCounter)
387 -}
388 -
390 // serverBlock pairs a Caddyfile server block with
391 // a "pile" of config values, keyed by class name,
392 // as well as its parsed keys for convenience.
caddytest/integration/caddyfile_adapt/handle_path.txt new
+52
@@ -0,0 +1,52 @@
1 +:80
2 +handle_path /api/v1/* {
3 + respond "API v1"
4 +}
5 +----------
6 +{
7 + "apps": {
8 + "http": {
9 + "servers": {
10 + "srv0": {
11 + "listen": [
12 + ":80"
13 + ],
14 + "routes": [
15 + {
16 + "match": [
17 + {
18 + "path": [
19 + "/api/v1/*"
20 + ]
21 + }
22 + ],
23 + "handle": [
24 + {
25 + "handler": "subroute",
26 + "routes": [
27 + {
28 + "handle": [
29 + {
30 + "handler": "rewrite",
31 + "strip_path_prefix": "/api/v1"
32 + }
33 + ]
34 + },
35 + {
36 + "handle": [
37 + {
38 + "body": "API v1",
39 + "handler": "static_response"
40 + }
41 + ]
42 + }
43 + ]
44 + }
45 + ]
46 + }
47 + ]
48 + }
49 + }
50 + }
51 + }
52 +}
\ No newline at end of file
modules/caddyhttp/rewrite/caddyfile.go
+74
@@ -15,9 +15,12 @@
15 package rewrite
16
17 import (
18 + "encoding/json"
19 "strconv"
20 "strings"
21
22 + "github.com/caddyserver/caddy/v2"
23 + "github.com/caddyserver/caddy/v2/caddyconfig"
24 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
25 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
26 )
@@ -25,6 +28,7 @@ import (
28 func init() {
29 httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfileRewrite)
30 httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI)
31 + httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath)
32 }
33
34 // parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
@@ -110,3 +114,73 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
114 }
115 return rewr, nil
116 }
117 +
118 +// parseCaddyfileHandlePath parses the handle_path directive. Syntax:
119 +//
120 +// handle_path [<matcher>] {
121 +// <directives...>
122 +// }
123 +//
124 +// Only path matchers (with a `/` prefix) are supported as this is a shortcut
125 +// for the handle directive with a strip_prefix rewrite.
126 +func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
127 + if !h.Next() {
128 + return nil, h.ArgErr()
129 + }
130 + if !h.NextArg() {
131 + return nil, h.ArgErr()
132 + }
133 +
134 + // read the prefix to strip
135 + path := h.Val()
136 + if !strings.HasPrefix(path, "/") {
137 + return nil, h.Errf("path matcher must begin with '/', got %s", path)
138 + }
139 +
140 + // we only want to strip what comes before the '/' if
141 + // the user specified it (e.g. /api/* should only strip /api)
142 + var stripPath string
143 + if strings.HasSuffix(path, "/*") {
144 + stripPath = path[:len(path)-2]
145 + } else if strings.HasSuffix(path, "*") {
146 + stripPath = path[:len(path)-1]
147 + } else {
148 + stripPath = path
149 + }
150 +
151 + // the ParseSegmentAsSubroute function expects the cursor
152 + // to be at the token just before the block opening,
153 + // so we need to rewind because we already read past it
154 + h.Reset()
155 + h.Next()
156 +
157 + // parse the block contents as a subroute handler
158 + handler, err := httpcaddyfile.ParseSegmentAsSubroute(h)
159 + if err != nil {
160 + return nil, err
161 + }
162 + subroute, ok := handler.(*caddyhttp.Subroute)
163 + if !ok {
164 + return nil, h.Errf("segment was not parsed as a subroute")
165 + }
166 +
167 + // make a matcher on the path and everything below it
168 + pathMatcher := caddy.ModuleMap{
169 + "path": h.JSON(caddyhttp.MatchPath{path}),
170 + }
171 +
172 + // build a route with a rewrite handler to strip the path prefix
173 + route := caddyhttp.Route{
174 + HandlersRaw: []json.RawMessage{
175 + caddyconfig.JSONModuleObject(Rewrite{
176 + StripPathPrefix: stripPath,
177 + }, "handler", "rewrite", nil),
178 + },
179 + }
180 +
181 + // prepend the route to the subroute
182 + subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...)
183 +
184 + // build and return a route from the subroute
185 + return h.NewRoute(pathMatcher, subroute), nil
186 +}