summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2020-05-26 17:27:51 -0400
committerGitHub <noreply@github.com>2020-05-26 15:27:51 -0600
commit8c5d00b2bc815c182e1a510be6dddc128949bf23 (patch)
treead09755b3df1de68f885542a849533da5410121d
parentaa20878887c592fd79bef2a9943b1013ead8bac2 (diff)
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
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go4
-rw-r--r--caddyconfig/httpcaddyfile/directives.go115
-rw-r--r--caddytest/integration/caddyfile_adapt/handle_path.txt52
-rw-r--r--modules/caddyhttp/rewrite/caddyfile.go74
4 files changed, 186 insertions, 59 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 2bb9b90..7026dfe 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -442,11 +442,11 @@ func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
}
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
- return parseSegmentAsSubroute(h)
+ return ParseSegmentAsSubroute(h)
}
func parseHandleErrors(h Helper) ([]ConfigValue, error) {
- subroute, err := parseSegmentAsSubroute(h)
+ subroute, err := ParseSegmentAsSubroute(h)
if err != nil {
return nil, err
}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 1578772..cde9743 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -57,6 +57,7 @@ var directiveOrder = []string{
// special routing directives
"handle",
"route",
+ "handle_path",
// handlers that typically respond to requests
"respond",
@@ -261,6 +262,63 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
return []ConfigValue{{Class: "bind", Value: addrs}}
}
+// ParseSegmentAsSubroute parses the segment such that its subdirectives
+// are themselves treated as directives, from which a subroute is built
+// and returned.
+func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
+ var allResults []ConfigValue
+
+ for h.Next() {
+ // slice the linear list of tokens into top-level segments
+ var segments []caddyfile.Segment
+ for nesting := h.Nesting(); h.NextBlock(nesting); {
+ segments = append(segments, h.NextSegment())
+ }
+
+ // copy existing matcher definitions so we can augment
+ // new ones that are defined only in this scope
+ matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
+ for key, val := range h.matcherDefs {
+ matcherDefs[key] = val
+ }
+
+ // find and extract any embedded matcher definitions in this scope
+ for i, seg := range segments {
+ if strings.HasPrefix(seg.Directive(), matcherPrefix) {
+ err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
+ if err != nil {
+ return nil, err
+ }
+ segments = append(segments[:i], segments[i+1:]...)
+ }
+ }
+
+ // with matchers ready to go, evaluate each directive's segment
+ for _, seg := range segments {
+ dir := seg.Directive()
+ dirFunc, ok := registeredDirectives[dir]
+ if !ok {
+ return nil, h.Errf("unrecognized directive: %s", dir)
+ }
+
+ subHelper := h
+ subHelper.Dispenser = caddyfile.NewDispenser(seg)
+ subHelper.matcherDefs = matcherDefs
+
+ results, err := dirFunc(subHelper)
+ if err != nil {
+ return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
+ }
+ for _, result := range results {
+ result.directive = dir
+ allResults = append(allResults, result)
+ }
+ }
+ }
+
+ return buildSubroute(allResults, h.groupCounter)
+}
+
// ConfigValue represents a value to be added to the final
// configuration, or a value to be consulted when building
// the final configuration.
@@ -329,63 +387,6 @@ func sortRoutes(routes []ConfigValue) {
})
}
-// parseSegmentAsSubroute parses the segment such that its subdirectives
-// are themselves treated as directives, from which a subroute is built
-// and returned.
-func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
- var allResults []ConfigValue
-
- for h.Next() {
- // slice the linear list of tokens into top-level segments
- var segments []caddyfile.Segment
- for nesting := h.Nesting(); h.NextBlock(nesting); {
- segments = append(segments, h.NextSegment())
- }
-
- // copy existing matcher definitions so we can augment
- // new ones that are defined only in this scope
- matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
- for key, val := range h.matcherDefs {
- matcherDefs[key] = val
- }
-
- // find and extract any embedded matcher definitions in this scope
- for i, seg := range segments {
- if strings.HasPrefix(seg.Directive(), matcherPrefix) {
- err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
- if err != nil {
- return nil, err
- }
- segments = append(segments[:i], segments[i+1:]...)
- }
- }
-
- // with matchers ready to go, evaluate each directive's segment
- for _, seg := range segments {
- dir := seg.Directive()
- dirFunc, ok := registeredDirectives[dir]
- if !ok {
- return nil, h.Errf("unrecognized directive: %s", dir)
- }
-
- subHelper := h
- subHelper.Dispenser = caddyfile.NewDispenser(seg)
- subHelper.matcherDefs = matcherDefs
-
- results, err := dirFunc(subHelper)
- if err != nil {
- return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
- }
- for _, result := range results {
- result.directive = dir
- allResults = append(allResults, result)
- }
- }
- }
-
- return buildSubroute(allResults, h.groupCounter)
-}
-
// serverBlock pairs a Caddyfile server block with
// a "pile" of config values, keyed by class name,
// as well as its parsed keys for convenience.
diff --git a/caddytest/integration/caddyfile_adapt/handle_path.txt b/caddytest/integration/caddyfile_adapt/handle_path.txt
new file mode 100644
index 0000000..7f40fcf
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/handle_path.txt
@@ -0,0 +1,52 @@
+:80
+handle_path /api/v1/* {
+ respond "API v1"
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "path": [
+ "/api/v1/*"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "rewrite",
+ "strip_path_prefix": "/api/v1"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "body": "API v1",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go
index ee7dd23..950119d 100644
--- a/modules/caddyhttp/rewrite/caddyfile.go
+++ b/modules/caddyhttp/rewrite/caddyfile.go
@@ -15,9 +15,12 @@
package rewrite
import (
+ "encoding/json"
"strconv"
"strings"
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
@@ -25,6 +28,7 @@ import (
func init() {
httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfileRewrite)
httpcaddyfile.RegisterHandlerDirective("uri", parseCaddyfileURI)
+ httpcaddyfile.RegisterDirective("handle_path", parseCaddyfileHandlePath)
}
// parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
@@ -110,3 +114,73 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
}
return rewr, nil
}
+
+// parseCaddyfileHandlePath parses the handle_path directive. Syntax:
+//
+// handle_path [<matcher>] {
+// <directives...>
+// }
+//
+// Only path matchers (with a `/` prefix) are supported as this is a shortcut
+// for the handle directive with a strip_prefix rewrite.
+func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
+ if !h.Next() {
+ return nil, h.ArgErr()
+ }
+ if !h.NextArg() {
+ return nil, h.ArgErr()
+ }
+
+ // read the prefix to strip
+ path := h.Val()
+ if !strings.HasPrefix(path, "/") {
+ return nil, h.Errf("path matcher must begin with '/', got %s", path)
+ }
+
+ // we only want to strip what comes before the '/' if
+ // the user specified it (e.g. /api/* should only strip /api)
+ var stripPath string
+ if strings.HasSuffix(path, "/*") {
+ stripPath = path[:len(path)-2]
+ } else if strings.HasSuffix(path, "*") {
+ stripPath = path[:len(path)-1]
+ } else {
+ stripPath = path
+ }
+
+ // the ParseSegmentAsSubroute function expects the cursor
+ // to be at the token just before the block opening,
+ // so we need to rewind because we already read past it
+ h.Reset()
+ h.Next()
+
+ // parse the block contents as a subroute handler
+ handler, err := httpcaddyfile.ParseSegmentAsSubroute(h)
+ if err != nil {
+ return nil, err
+ }
+ subroute, ok := handler.(*caddyhttp.Subroute)
+ if !ok {
+ return nil, h.Errf("segment was not parsed as a subroute")
+ }
+
+ // make a matcher on the path and everything below it
+ pathMatcher := caddy.ModuleMap{
+ "path": h.JSON(caddyhttp.MatchPath{path}),
+ }
+
+ // build a route with a rewrite handler to strip the path prefix
+ route := caddyhttp.Route{
+ HandlersRaw: []json.RawMessage{
+ caddyconfig.JSONModuleObject(Rewrite{
+ StripPathPrefix: stripPath,
+ }, "handler", "rewrite", nil),
+ },
+ }
+
+ // prepend the route to the subroute
+ subroute.Routes = append([]caddyhttp.Route{route}, subroute.Routes...)
+
+ // build and return a route from the subroute
+ return h.NewRoute(pathMatcher, subroute), nil
+}