Setting the route programmatically in Spring Cloud Netflix Zuul

canary-deployment, netflix-zuul, spring, spring-cloud-netflix

Solution

What you really need to do is that changing `requestURI`, not server location in your case. You can easily do that like below.

First, your filter's order should be bigger than `PreDecorationFilter`'s order that is currently 5 in Dalston release. (You need to check the value from the release you're using). `PreDecorationFilter` processes request header and fill necessary info into `RequestContext`. And this `RequestContext` will be used to define actual URL for your request. `requestURI` is the key that you need to change. In you case, you can append or modify requestURI based on headers. The below is snippet for your pre-filter.

@Override
public int filterOrder() {
    return 6;
}

@Override
public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    // override request URI
    ctx.set("requestURI", "/v1073" + ctx.get("requestURI"));
    return null;
}

Your original implementation seems to have some problems. First, you're trying to change location in ZuulProperties route. This object is shared for all requests. If you change it just for a specific request, other request that may have different headers could be routed to wrong location.

Second, you're setting location property in `ZuulRoute` object like below.

currentRoute.setLocation("https://beta.myserver.com/v1073");

Originally location property value is your service-id -`beta-root` - with your configuration. If you change it with a specific url that has 'http' or 'https`prefix, original`RibbonRoutingFilter`will not work. Instead,`SimpleRoutingHostFilter`will process your request. It means that your not-modified requests will be handled by`RibbonRoutingFilter`and modified requests will be handled by`SimpleHostRoutingFilter`.

Update : To route different hosts based on Http Header

If you want to route to different hosts based on http headers, there are several ways to do that.

Case 1: In case that you are using Ribbon (and RibbonRoutingFilter)

The following feature only works on Edgware.SR1 and later version. From Eddware.SR1, you can put specify value for loadbalancer called `FilterConstants.LOAD_BALANCER_KEY` in `RequestContext`. This value will be passed into Ribbon load balancer. You can put any value(any object) that you want in your prefilter. If you want to change route based on a special http header, you can do that in your custom prefilter. And then define your own `IRule` implementation for Ribbon.`LOAD_BALANCER_KEY` will be given to your `IRule` implementation. Therefore you can choose the specific server from the list of server that Ribbon has based on the value of `LOAD_BALANCER_KEY` that you set.

You can find brief documentation here.

You can find sample code from the test case in PR. (RibbonRoutingFilterLoadBalancerKeyIntegrationTests.java)

Case 2: In case that you are using SimpleHostRoutingFilter (without Ribbon)

If you specify `url` instead of `serviceId` in zuul's route properties, the request will be routed by SimpleHostRoutingFilter without Ribbon.

SimpleHostRoutingFilter just use the host address by the below code

RequestContext.getCurrentContext().getRouteHost();

This value is set by PreDecorationFilter. So you can change this info in your prefilter.

- Make your own custom prefilter that has the order value between PreDecorationFilter's and SimpleHostRoutingFilter's.

- Inside your filter, check route host. If it is any known host that you want to change it based on HTTP header, change the route host via `RequestContext.getCurrentContext().setRouteHost` based on Http Header.

In case of the second approach, I didn't try to do that by myself. It's just theoretical solution that I think. The problem of the second approach is that it is using SimpleHostRoutingFilter. SimpleHostRoutingFilter doesn't make any HystrixCommand for the request, so you can't use any circuit breaker features that is provided by Hystrix inside Zuul. If you are using Edgware release, the first approach is better as I think.

Problem

I have created two AWS Beanstalk envs, each with their own version of the applications. The urls for these envs are https://beta.myserver.com/v1073 and https://beta.myserver.com/v1084. These urls point to the load balancer. Now I also have a Zuul implementation that have the following configurations. ``` zuul: routes: beta: path: /api/** serviceId: beta-root strip-prefix: false sensitive-headers: Cookie,Set-Cookie ribbon: eureka: enabled: false hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000 beta-root: ribbon: listOfServers: https://beta.myserver.com ``` Request for my application must have a version header. I have a Zuul "pre" filter that inspect this header value. The goal is to route the request based on the header value. I have been able to intercept the request, but have not been able to route it from the filter. The code snippet is below. After running the code the request still tries to go https://beta.myserver.com/api/... ``` @Override public Object run() { /* Logic to get version header etc */ /* Set the new route */ Map<String, ZuulRoute> routes = zuulProps.getRoutes(); ZuulRoute currentRoute = routes.get("beta-root"); currentRoute.setLocation("https://beta.myserver.com/v1073"); /* Refresh the route */ routeLocator.getRoutes(); logger.warn("Current Route:" + currentRoute.getLocation()); return null; } ``` Any suggestion how to resolve this issue?

Original source