Redirect request if it would match with an appended trailing slash
This commit is contained in:
		
							parent
							
								
									6c62b25bf1
								
							
						
					
					
						commit
						8a06779d41
					
				|  | @ -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) | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -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", | ||||||
|  | 		}), | ||||||
| 	) | 	) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
| ) | ) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue