Redirect request if it would match with an appended trailing slash

This commit is contained in:
Joel Speed 2021-03-07 12:42:10 +00:00
parent 6c62b25bf1
commit 8a06779d41
No known key found for this signature in database
GPG Key ID: 6E80578D6751DEFB
3 changed files with 53 additions and 0 deletions

View File

@ -1,6 +1,7 @@
package upstream package upstream
import ( import (
"context"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -50,6 +51,8 @@ func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, write
return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme) return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
} }
} }
registerTrailingSlashHandler(m.serveMux)
return m, nil return m, nil
} }
@ -120,3 +123,25 @@ func (m *multiUpstreamProxy) registerRewriteHandler(upstream options.Upstream, h
return nil return nil
} }
// registerTrailingSlashHandler creates a new matcher that will check if the
// requested path would match if it had a trailing slash appended.
// If the path matches with a trailing slash, we send back a redirect.
// This allows us to be consistent with the built in go servemux implementation.
func registerTrailingSlashHandler(serveMux *mux.Router) {
serveMux.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
if strings.HasSuffix(req.URL.Path, "/") {
return false
}
// Use a separate RouteMatch so that we can redirect to the path + /.
// If we pass through the match then the matched backed will be served
// instead of the redirect handler.
m := &mux.RouteMatch{}
slashReq := req.Clone(context.Background())
slashReq.URL.Path += "/"
return serveMux.Match(slashReq, m)
}).Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, req.URL.String()+"/", http.StatusMovedPermanently)
}))
}

View File

@ -29,6 +29,7 @@ var _ = Describe("Proxy Suite", func() {
} }
ok := http.StatusOK ok := http.StatusOK
accepted := http.StatusAccepted
upstreams := options.Upstreams{ upstreams := options.Upstreams{
{ {
@ -47,6 +48,12 @@ var _ = Describe("Proxy Suite", func() {
Static: true, Static: true,
StaticCode: &ok, StaticCode: &ok,
}, },
{
ID: "static-backend-no-trailing-slash",
Path: "/static",
Static: true,
StaticCode: &accepted,
},
{ {
ID: "bad-http-backend", ID: "bad-http-backend",
Path: "/bad-http/", Path: "/bad-http/",
@ -235,5 +242,25 @@ var _ = Describe("Proxy Suite", func() {
}, },
upstream: "backend-with-rewrite-prefix", upstream: "backend-with-rewrite-prefix",
}), }),
Entry("with a request to a path, missing the trailing slash", &proxyTableInput{
target: "http://example.localhost/http",
response: testHTTPResponse{
code: 301,
header: map[string][]string{
contentType: {textHTMLUTF8},
"Location": {"http://example.localhost/http/"},
},
raw: "<a href=\"http://example.localhost/http/\">Moved Permanently</a>.\n\n",
},
}),
Entry("with a request to a path, missing the trailing slash, but registered separately", &proxyTableInput{
target: "http://example.localhost/static",
response: testHTTPResponse{
code: 202,
header: map[string][]string{},
raw: "Authenticated",
},
upstream: "static-backend-no-trailing-slash",
}),
) )
}) })

View File

@ -59,6 +59,7 @@ const (
acceptEncoding = "Accept-Encoding" acceptEncoding = "Accept-Encoding"
applicationJSON = "application/json" applicationJSON = "application/json"
textPlainUTF8 = "text/plain; charset=utf-8" textPlainUTF8 = "text/plain; charset=utf-8"
textHTMLUTF8 = "text/html; charset=utf-8"
gapAuth = "Gap-Auth" gapAuth = "Gap-Auth"
gapSignature = "Gap-Signature" gapSignature = "Gap-Signature"
) )