diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc old mode 100644 new mode 100755 index 891ddeca5..6904301fb --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -1491,6 +1491,22 @@ NOTE: This special flag works only with `SimpleHostRoutingFilter`. Also, you loo query parameters with `RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)`, because the query string is now fetched directly on the original `HttpServletRequest`. +=== Request URI Encoding +When processing the incoming request, request URI is decoded before matching them to routes. +The request URI is then re-encoded when the back end request is rebuilt in the route filters. +This can cause some unexpected behavior if your URI includes the encoded "/" character. + +To use the original request URI, it is possible to pass a special flag to 'ZuulProperties' so that the URI will be taken as is with the `HttpServletRequest::getRequestURI` method, as shown in the following example: + +.application.yml +[source,yaml] +---- + zuul: + decodeUrl: false +---- + +NOTE: If you are overriding request URI using `requestURI` RequestContext attribute and this flag is set to false, then the URL set in the request context will not be encoded. It will be your responsibility to make sure the URL is already encoded. + === Plain Embedded Zuul If you use `@EnableZuulServer` (instead of `@EnableZuulProxy`), you can also run a Zuul server without proxying or selectively switch on parts of the proxying platform. diff --git a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java old mode 100644 new mode 100755 index 0a5fd7cdb..7197ce98b --- a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java +++ b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ProxyRequestHelper.java @@ -78,6 +78,8 @@ public class ProxyRequestHelper { private boolean addHostHeader = false; + private boolean urlDecoded = true; + @Deprecated //TODO Remove in 2.1.x public ProxyRequestHelper() {} @@ -86,6 +88,7 @@ public class ProxyRequestHelper { this.ignoredHeaders.addAll(zuulProperties.getIgnoredHeaders()); this.traceRequestBody = zuulProperties.isTraceRequestBody(); this.addHostHeader = zuulProperties.isAddHostHeader(); + this.urlDecoded = zuulProperties.isDecodeUrl(); } public void setWhitelistHosts(Set whitelistHosts) { @@ -114,7 +117,10 @@ public class ProxyRequestHelper { String contextURI = (String) context.get(REQUEST_URI_KEY); if (contextURI != null) { try { - uri = UriUtils.encodePath(contextURI, characterEncoding(request)); + uri = contextURI; + if (this.urlDecoded) { + uri = UriUtils.encodePath(contextURI, characterEncoding(request)); + } } catch (Exception e) { log.debug( diff --git a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java old mode 100644 new mode 100755 index c37b39e54..04f7e6fd0 --- a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java +++ b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java @@ -140,6 +140,11 @@ public class ZuulProperties { */ private boolean removeSemicolonContent = true; + /** + * Flag to indicate whether to decode the matched URL or use it as is. + */ + private boolean decodeUrl = true; + /** * List of sensitive headers that are not passed to downstream requests. Defaults to a * "safe" set of headers that commonly contain user credentials. It's OK to remove @@ -764,6 +769,14 @@ public class ZuulProperties { this.removeSemicolonContent = removeSemicolonContent; } + public boolean isDecodeUrl() { + return decodeUrl; + } + + public void setDecodeUrl(boolean decodeUrl) { + this.decodeUrl = decodeUrl; + } + public Set getSensitiveHeaders() { return sensitiveHeaders; } diff --git a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java old mode 100644 new mode 100755 index 5ff66507f..aa291e045 --- a/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java +++ b/spring-cloud-netflix-zuul/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java @@ -88,6 +88,7 @@ public class PreDecorationFilter extends ZuulFilter { this.routeLocator = routeLocator; this.properties = properties; this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent()); + this.urlPathHelper.setUrlDecode(properties.isDecodeUrl()); this.dispatcherServletPath = dispatcherServletPath; this.proxyRequestHelper = proxyRequestHelper; } diff --git a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java old mode 100644 new mode 100755 index ad1033901..947fc3eb1 --- a/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java +++ b/spring-cloud-netflix-zuul/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java @@ -329,6 +329,22 @@ public class PreDecorationFilterTests { getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid")); } + @Test + public void dontDecodeUrl() { + this.properties.setPrefix("/api"); + this.properties.setStripPrefix(true); + this.properties.setDecodeUrl(false); + this.request.setRequestURI("/api/foo/encoded%2Fpath"); + this.request.setContextPath("/context-path"); + this.routeLocator.addRoute( + new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null)); + this.filter = new PreDecorationFilter(this.routeLocator, "/", this.properties, + this.proxyRequestHelper); + this.filter.run(); + RequestContext ctx = RequestContext.getCurrentContext(); + assertEquals("/foo/encoded%2Fpath", ctx.get(REQUEST_URI_KEY)); + } + @Test public void routeIgnoreContextPathIfPrefixHeader() { this.properties.setStripPrefix(false);