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
This commit is contained in:
Francis Lavoie
2020-05-26 17:27:51 -04:00
committed by GitHub
parent aa20878887
commit 8c5d00b2bc
4 changed files with 186 additions and 59 deletions

View File

@ -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
}