Commit dfae4454 by Dave Syer

Strip prefixes from zuul routes by default

The default behaviour is now to strip the prefixes (global and route-specific) by default. E.g. zuul: prefix: /api routes: customers: /customers/** Will forward /api/customers/101 to /101 on the customers service See gh-43
parent 7b270c4b
...@@ -500,10 +500,26 @@ Zuul's rule engine allows rules and filters to be written in essentially any JVM ...@@ -500,10 +500,26 @@ Zuul's rule engine allows rules and filters to be written in essentially any JVM
[[netflix-zuul-reverse-proxy]] [[netflix-zuul-reverse-proxy]]
=== Embedded Zuul Reverse Proxy === Embedded Zuul Reverse Proxy
Spring Cloud has created an embedded Zuul proxy to ease the development of a very common use case where a UI application wants to proxy calls to one or more back end services. To enable it, annotate a Spring Boot main class with `@EnableZuulProxy`, and this forwards local calls to the appropriate service. By convention, a service with the Eureka ID "users", will receive requests from the proxy located at `/users`. The proxy uses Ribbon to locate an instance to forward to via Eureka. To skip having a service automatically added from eureka, set `zuul.ignored-services = service1`. Spring Cloud has created an embedded Zuul proxy to ease the
development of a very common use case where a UI application wants to
To augment or change the proxy routes, you can add external proxy calls to one or more back end services. This feature is useful
configuration like the following: for a user interface to proxy to the backend services it requires,
avoiding the need to manage CORS and authentication concerns
independently for all the backends.
To enable it, annotate a Spring Boot main class with
`@EnableZuulProxy`, and this forwards local calls to the appropriate
service. By convention, a service with the Eureka ID "users", will
receive requests from the proxy located at `/users` (with the prefix
stripped). The proxy uses Ribbon to locate an instance to forward to
via Eureka, and all requests are executed in a hystrix command, so
failures will show up in Hystrix metrics, and once the circuit is open
the proxy will not try to contact the service.
To skip having a service automatically added, set
`zuul.ignored-services` to a list of service ids. To augment or change
the proxy routes, you can add external configuration like the
following:
.application.yml .application.yml
[source,yaml] [source,yaml]
...@@ -514,9 +530,7 @@ configuration like the following: ...@@ -514,9 +530,7 @@ configuration like the following:
---- ----
This means that http calls to "/myusers" get forwarded to the "users" This means that http calls to "/myusers" get forwarded to the "users"
service. This configuration is useful for a user interface to proxy to service (for example "/myusers/101" is forwarded to "/101").
the backend services it requires (avoiding the need to manage CORS and
authentication concerns independently for all the backends).
To get more fine-grained control over a route you can specify the path To get more fine-grained control over a route you can specify the path
and the serviceId independently: and the serviceId independently:
...@@ -549,9 +563,11 @@ The location of the backend can be specified as either a "serviceId" ...@@ -549,9 +563,11 @@ The location of the backend can be specified as either a "serviceId"
url: http://example.com/users_service url: http://example.com/users_service
---- ----
Forwarding to the service is protected by a Hystrix circuit breaker so if a service is down the client will see an error, but once the circuit is open the proxy will not try to contact the service. To add a prefix to all mappings, set `zuul.prefix` to a value, such as
`/api`. The proxy prefix is stripped from the request before the
To add a prefix to all mappings, set `zuul.prefix` to a value, such as `/api`. To strip the proxy prefix from the request before the request is forwarded set `zuul.stripPrefix = true`. You can also strip the non-wildcard prefix from individual routes, e.g. request is forwarded by default (switch this behaviour off with
`zuul.stripPrefix=false`). You can also switch off the stripping of
the service-specific prefix from individual routes, e.g.
.application.yml .application.yml
[source,yaml] [source,yaml]
...@@ -560,12 +576,18 @@ To add a prefix to all mappings, set `zuul.prefix` to a value, such as `/api`. ...@@ -560,12 +576,18 @@ To add a prefix to all mappings, set `zuul.prefix` to a value, such as `/api`.
routes: routes:
users: users:
path: /myusers/** path: /myusers/**
stripPrefix: true stripPrefix: false
---- ----
In this example requests to "/myusers/101" will be forwarded to "/101" on the "users" service (the path is stripped up to the first wildcard character). In this example requests to "/myusers/101" will be forwarded to "/myusers/101" on the "users" service.
The `X-Forwarded-Host` header added to the forwarded requests by default. To turn it off set `zuul.addProxyHeaders = false`. The `X-Forwarded-Host` header added to the forwarded requests by
default. To turn it off set `zuul.addProxyHeaders = false`. The
prefix path is stripped by default, and the request to the backend
picks up a header "X-Forwarded-Prefix" ("/myusers" in the examples
above).
An application with the `@EnableZuulProxy` could act as a standalone server if you set a default route ("/"), for example `zuul.route.home: /` would route all traffic (i.e. "/**") to the "home" service. An application with the `@EnableZuulProxy` could act as a standalone
server if you set a default route ("/"), for example `zuul.route.home:
/` would route all traffic (i.e. "/**") to the "home" service.
...@@ -85,9 +85,9 @@ public class ProxyRouteLocator { ...@@ -85,9 +85,9 @@ public class ProxyRouteLocator {
if (route.isStripPrefix()) { if (route.isStripPrefix()) {
int index = route.getPath().indexOf("*") - 1; int index = route.getPath().indexOf("*") - 1;
if (index > 0) { if (index > 0) {
targetPath = path.substring(prefix.length() + index); String routePrefix = route.getPath().substring(0, index);
prefix = prefix targetPath = targetPath.replaceFirst(routePrefix, "");
+ path.substring(prefix.length(), prefix.length() + index); prefix = prefix + routePrefix;
} }
} }
break; break;
......
...@@ -21,7 +21,7 @@ import org.springframework.util.StringUtils; ...@@ -21,7 +21,7 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties("zuul") @ConfigurationProperties("zuul")
public class ZuulProperties { public class ZuulProperties {
private String prefix = ""; private String prefix = "";
private boolean stripPrefix = false; private boolean stripPrefix = true;
private Map<String, ZuulRoute> routes = new LinkedHashMap<String, ZuulRoute>(); private Map<String, ZuulRoute> routes = new LinkedHashMap<String, ZuulRoute>();
private boolean addProxyHeaders = true; private boolean addProxyHeaders = true;
private List<String> ignoredServices = new ArrayList<String>(); private List<String> ignoredServices = new ArrayList<String>();
...@@ -43,7 +43,7 @@ public class ZuulProperties { ...@@ -43,7 +43,7 @@ public class ZuulProperties {
private String path; private String path;
private String serviceId; private String serviceId;
private String url; private String url;
private boolean stripPrefix = false; private boolean stripPrefix = true;
public ZuulRoute(String text) { public ZuulRoute(String text) {
String location = null; String location = null;
......
...@@ -59,14 +59,37 @@ public class ProxyRouteLocatorTests { ...@@ -59,14 +59,37 @@ public class ProxyRouteLocatorTests {
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation()); assertEquals("foo", route.getLocation());
assertEquals("/1", route.getPath());
}
@Test
public void testGetMatchingPathWithNoPrefixStripping() throws Exception {
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**", "foo", null, false));
this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation());
assertEquals("/proxy/foo/1", route.getPath()); assertEquals("/proxy/foo/1", route.getPath());
} }
@Test @Test
public void testGetMatchingPathWithPrefixStripping() throws Exception { public void testGetMatchingPathWithLocalPrefixStripping() throws Exception {
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties); ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**")); this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**", "foo"));
this.properties.setStripPrefix(true); this.properties.setStripPrefix(false);
this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1");
assertEquals("foo", route.getLocation());
assertEquals("/proxy/1", route.getPath());
}
@Test
public void testGetMatchingPathWithGlobalPrefixStripping() throws Exception {
ProxyRouteLocator routeLocator = new ProxyRouteLocator(this.discovery, this.properties);
this.properties.getRoutes().put("foo", new ZuulRoute("/foo/**", "foo", null, false));
this.properties.setPrefix("/proxy"); this.properties.setPrefix("/proxy");
routeLocator.getRoutes(); // force refresh routeLocator.getRoutes(); // force refresh
ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1"); ProxyRouteSpec route = routeLocator.getMatchingRoute("/proxy/foo/1");
......
...@@ -21,17 +21,17 @@ public class SampleZuulProxyApplication { ...@@ -21,17 +21,17 @@ public class SampleZuulProxyApplication {
throw new RuntimeException("myerror"); throw new RuntimeException("myerror");
} }
@RequestMapping("/local/self") @RequestMapping("/local")
public String local() { public String local() {
return "Hello local"; return "Hello local";
} }
@RequestMapping(value="/local/self/{id}", method=RequestMethod.DELETE) @RequestMapping(value="/local/{id}", method=RequestMethod.DELETE)
public String delete() { public String delete() {
return "Deleted!"; return "Deleted!";
} }
@RequestMapping(value="/local/self/{id}", method=RequestMethod.GET) @RequestMapping(value="/local/{id}", method=RequestMethod.GET)
public String get() { public String get() {
return "Gotten!"; return "Gotten!";
} }
......
...@@ -55,7 +55,7 @@ public class PreDecorationFilterTests { ...@@ -55,7 +55,7 @@ public class PreDecorationFilterTests {
properties.setPrefix("/api"); properties.setPrefix("/api");
properties.setStripPrefix(true); properties.setStripPrefix(true);
request.setRequestURI("/api/foo/1"); request.setRequestURI("/api/foo/1");
routeLocator.addRoute("/foo/**", "foo"); routeLocator.addRoute(new ZuulRoute("/foo/**", "foo", null, false));
filter.run(); filter.run();
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/foo/1", ctx.get("requestURI")); assertEquals("/foo/1", ctx.get("requestURI"));
...@@ -69,7 +69,7 @@ public class PreDecorationFilterTests { ...@@ -69,7 +69,7 @@ public class PreDecorationFilterTests {
properties.setPrefix("/api"); properties.setPrefix("/api");
properties.setStripPrefix(true); properties.setStripPrefix(true);
request.setRequestURI("/api/foo/1"); request.setRequestURI("/api/foo/1");
routeLocator.addRoute(new ZuulRoute("/foo/**", "foo", null, true)); routeLocator.addRoute("/foo/**", "foo");
filter.run(); filter.run();
RequestContext ctx = RequestContext.getCurrentContext(); RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/1", ctx.get("requestURI")); assertEquals("/1", ctx.get("requestURI"));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment