Ensure upstreams are sorted by longest first
This commit is contained in:
		
							parent
							
								
									8a06779d41
								
							
						
					
					
						commit
						075cb9c3a0
					
				|  | @ -6,6 +6,7 @@ import ( | |||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/gorilla/mux" | ||||
|  | @ -26,7 +27,7 @@ func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, write | |||
| 		serveMux: mux.NewRouter(), | ||||
| 	} | ||||
| 
 | ||||
| 	for _, upstream := range upstreams { | ||||
| 	for _, upstream := range sortByPathLongest(upstreams) { | ||||
| 		if upstream.Static { | ||||
| 			if err := m.registerStaticResponseHandler(upstream, writer); err != nil { | ||||
| 				return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err) | ||||
|  | @ -145,3 +146,33 @@ func registerTrailingSlashHandler(serveMux *mux.Router) { | |||
| 		http.Redirect(rw, req, req.URL.String()+"/", http.StatusMovedPermanently) | ||||
| 	})) | ||||
| } | ||||
| 
 | ||||
| // sortByPathLongest ensures that the upstreams are sorted by longest path.
 | ||||
| // If rewrites are involved, a rewrite takes precedence over a non-rewrite.
 | ||||
| // When two upstreams define rewrites, whichever has the longest path will take
 | ||||
| // precedence (note this is the input to the rewrite logic).
 | ||||
| // This does not account for when a rewrite would actually make the path shorter.
 | ||||
| // This should maintain the sorting behaviour of the standard go serve mux.
 | ||||
| func sortByPathLongest(in options.Upstreams) options.Upstreams { | ||||
| 	sort.Slice(in, func(i, j int) bool { | ||||
| 		iRW := in[i].RewriteTarget | ||||
| 		jRW := in[j].RewriteTarget | ||||
| 
 | ||||
| 		switch { | ||||
| 		case iRW != "" && jRW != "": | ||||
| 			// If both have a rewrite target, whichever has the longest pattern
 | ||||
| 			// should go first
 | ||||
| 			return len(in[i].Path) > len(in[j].Path) | ||||
| 		case iRW != "" && jRW == "": | ||||
| 			// Only one has rewrite, it goes first
 | ||||
| 			return true | ||||
| 		case iRW == "" && jRW != "": | ||||
| 			// Only one has rewrite, it goes first
 | ||||
| 			return false | ||||
| 		default: | ||||
| 			// Default to longest Path wins
 | ||||
| 			return len(in[i].Path) > len(in[j].Path) | ||||
| 		} | ||||
| 	}) | ||||
| 	return in | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import ( | |||
| var _ = Describe("Proxy Suite", func() { | ||||
| 	var upstreamServer http.Handler | ||||
| 
 | ||||
| 	Context("multiUpstreamProxy", func() { | ||||
| 		BeforeEach(func() { | ||||
| 			sigData := &options.SignatureData{Hash: crypto.SHA256, Key: "secret"} | ||||
| 
 | ||||
|  | @ -54,6 +55,12 @@ var _ = Describe("Proxy Suite", func() { | |||
| 					Static:     true, | ||||
| 					StaticCode: &accepted, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:         "static-backend-long", | ||||
| 					Path:       "/static/long", | ||||
| 					Static:     true, | ||||
| 					StaticCode: &accepted, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:   "bad-http-backend", | ||||
| 					Path: "/bad-http/", | ||||
|  | @ -71,6 +78,17 @@ var _ = Describe("Proxy Suite", func() { | |||
| 					RewriteTarget: "/different/backend/path/$1", | ||||
| 					URI:           serverAddr, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:   "double-match-plain", | ||||
| 					Path: "/double-match/", | ||||
| 					URI:  serverAddr, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:            "double-match-rewrite", | ||||
| 					Path:          "^/double-match/(.*)", | ||||
| 					RewriteTarget: "/double-match/rewrite/$1", | ||||
| 					URI:           serverAddr, | ||||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			var err error | ||||
|  | @ -262,5 +280,107 @@ var _ = Describe("Proxy Suite", func() { | |||
| 				}, | ||||
| 				upstream: "static-backend-no-trailing-slash", | ||||
| 			}), | ||||
| 			Entry("should match longest path first", &proxyTableInput{ | ||||
| 				target: "http://example.localhost/static/long", | ||||
| 				response: testHTTPResponse{ | ||||
| 					code:   202, | ||||
| 					header: map[string][]string{}, | ||||
| 					raw:    "Authenticated", | ||||
| 				}, | ||||
| 				upstream: "static-backend-long", | ||||
| 			}), | ||||
| 			Entry("should match rewrite path first", &proxyTableInput{ | ||||
| 				target: "http://example.localhost/double-match/foo", | ||||
| 				response: testHTTPResponse{ | ||||
| 					code: 200, | ||||
| 					header: map[string][]string{ | ||||
| 						contentType: {applicationJSON}, | ||||
| 					}, | ||||
| 					request: testHTTPRequest{ | ||||
| 						Method: "GET", | ||||
| 						URL:    "http://example.localhost/double-match/rewrite/foo", | ||||
| 						Header: map[string][]string{ | ||||
| 							"Gap-Auth":      {""}, | ||||
| 							"Gap-Signature": {"sha256 eYyUNdsrTmnvFpavpP8AdHGUGzqJ39QEjqn0/3fQPHA="}, | ||||
| 						}, | ||||
| 						Body:       []byte{}, | ||||
| 						Host:       "example.localhost", | ||||
| 						RequestURI: "http://example.localhost/double-match/rewrite/foo", | ||||
| 					}, | ||||
| 				}, | ||||
| 				upstream: "double-match-rewrite", | ||||
| 			}), | ||||
| 		) | ||||
| 	}) | ||||
| 
 | ||||
| 	Context("sortByPathLongest", func() { | ||||
| 		type sortByPathLongestTableInput struct { | ||||
| 			input          options.Upstreams | ||||
| 			expectedOutput options.Upstreams | ||||
| 		} | ||||
| 
 | ||||
| 		var httpPath = options.Upstream{ | ||||
| 			Path: "/http/", | ||||
| 		} | ||||
| 
 | ||||
| 		var httpSubPath = options.Upstream{ | ||||
| 			Path: "/http/subpath/", | ||||
| 		} | ||||
| 
 | ||||
| 		var longerPath = options.Upstream{ | ||||
| 			Path: "/longer-than-http", | ||||
| 		} | ||||
| 
 | ||||
| 		var shortPathWithRewrite = options.Upstream{ | ||||
| 			Path:          "^/h/(.*)", | ||||
| 			RewriteTarget: "/$1", | ||||
| 		} | ||||
| 
 | ||||
| 		var shortSubPathWithRewrite = options.Upstream{ | ||||
| 			Path:          "^/h/bar/(.*)", | ||||
| 			RewriteTarget: "/$1", | ||||
| 		} | ||||
| 
 | ||||
| 		DescribeTable("short sort into the correct order", | ||||
| 			func(in sortByPathLongestTableInput) { | ||||
| 				Expect(sortByPathLongest(in.input)).To(Equal(in.expectedOutput)) | ||||
| 			}, | ||||
| 			Entry("with a mix of paths registered", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{httpPath, httpSubPath, shortSubPathWithRewrite, longerPath, shortPathWithRewrite}, | ||||
| 				expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite, longerPath, httpSubPath, httpPath}, | ||||
| 			}), | ||||
| 			Entry("when a subpath is registered (in order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{httpSubPath, httpPath}, | ||||
| 				expectedOutput: options.Upstreams{httpSubPath, httpPath}, | ||||
| 			}), | ||||
| 			Entry("when a subpath is registered (out of order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{httpPath, httpSubPath}, | ||||
| 				expectedOutput: options.Upstreams{httpSubPath, httpPath}, | ||||
| 			}), | ||||
| 			Entry("when longer paths are registered (in order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{longerPath, httpPath}, | ||||
| 				expectedOutput: options.Upstreams{longerPath, httpPath}, | ||||
| 			}), | ||||
| 			Entry("when longer paths are registered (out of order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{httpPath, longerPath}, | ||||
| 				expectedOutput: options.Upstreams{longerPath, httpPath}, | ||||
| 			}), | ||||
| 			Entry("when a rewrite target is registered (in order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{shortPathWithRewrite, longerPath}, | ||||
| 				expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath}, | ||||
| 			}), | ||||
| 			Entry("when a rewrite target is registered (out of order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{longerPath, shortPathWithRewrite}, | ||||
| 				expectedOutput: options.Upstreams{shortPathWithRewrite, longerPath}, | ||||
| 			}), | ||||
| 			Entry("with multiple rewrite targets registered (in order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite}, | ||||
| 				expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite}, | ||||
| 			}), | ||||
| 			Entry("with multiple rewrite targets registered (out of order)", sortByPathLongestTableInput{ | ||||
| 				input:          options.Upstreams{shortPathWithRewrite, shortSubPathWithRewrite}, | ||||
| 				expectedOutput: options.Upstreams{shortSubPathWithRewrite, shortPathWithRewrite}, | ||||
| 			}), | ||||
| 		) | ||||
| 	}) | ||||
| }) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue