Spring-cloud-netflix: Zuul - host based routing

Created on 30 Mar 2015  路  11Comments  路  Source: spring-cloud/spring-cloud-netflix

It would be nice to have possibility to create hostname based routing rules.
For example, often admin panel of application is deployed on the same server and served from admin.foo.com hostname. This kind of setup reduces magic thats needs to be done to correctly serve static resources ect.
Without hostname proxy setup to achieve this, there is need to introduce another server or some proxy before zuul (apache httpd or nginx).

It should be rather easy to achieve, by extending ProxyRouteLocator and PreDecorationFilter.

enhancement

Most helpful comment

OK, I get it. It's a tough call because we wouldn't want to change a public API like that.

You can get what you want right now anyway if you are writing a custom RouteLocator (just access the thread local ZuulRequest in the getMatchingRoute() method). Or you could write a ZuulFilter that changes the request path and insert it before the PreDecorationFilter - I guess that is cleaner.

All 11 comments

Isn't that what you get with zuul.routes.*.url? What would be the difference?

@dsyer we still key off of the /context. I think this is a request to key off of host: send admin.foo.com -> admin service regardless of /context.

@spencergibb Yes, exactly.

I've just looked at this issue as I'm trying to run a UI sevice on www.example.com and its AIP on api.example.com, so DNS *.example.com would point to the zuul proxy.

I think the easiest fix would be to change RouteLocator.getMatchingRoute(String) to RouteLocator.getMatchingRoute(HttpServletRequest)` so custom implementations could look at more than just the path for route selection.

Currently, PreDecorationFilter gets the path from the request. This would simply be moved into SimpleRouteLocator.

If header matching should be offered out of the box, the next step could be to add Map<String, String> matchingHeaders to org.springframework.cloud.netflix.zuul.filters.Route and add matching to SimpleRouteLocator.

Another use case would be choosing a service depending on the Accept header.

OK, I get it. It's a tough call because we wouldn't want to change a public API like that.

You can get what you want right now anyway if you are writing a custom RouteLocator (just access the thread local ZuulRequest in the getMatchingRoute() method). Or you could write a ZuulFilter that changes the request path and insert it before the PreDecorationFilter - I guess that is cleaner.

@spencergibb you said it can be done to route based on host, but how? I can't find any tutorial about it.

@hykych I did not. Please see the comment above yours. It's not possible without custom code.

Maybe something like I did can help you:

/**
 * For this filter to work, you need to add some route in zuul that maps to /**.
 * This route should be placed AFTER your other routes.
 */
@Component
@ConfigurationProperties(prefix = "your-prefix")
public class HostnameRoutingFilter extends ZuulFilter {

    /**
     * Map from hostname to context path. Only hosts included here will be modified, and the given
     * context path (value in the map) will be added to the request.
     */
    private Map<String, String> hostToContextPath = new HashMap<>();

    public Map<String, String> getHostToContextPath() {
        return hostToContextPath;
    }

    public void setHostToContextPath(Map<String, String> hostToContextPath) {
        this.hostToContextPath = hostToContextPath;
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        // Apply this before the decoration filter, as suggested here:
        // https://github.com/spring-cloud/spring-cloud-netflix/issues/285#issuecomment-277235991
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        String serverName = getCurrentContext().getRequest().getServerName();
        return StringUtils.isNotBlank(serverName) && hostToContextPath.containsKey(serverName);
    }

    /**
     * Usage of {@link WebUtils#INCLUDE_REQUEST_URI_ATTRIBUTE} is a temporary hack (until change to spring-cloud-gateway) that
     * takes advantage of exactly how the
     * {@link org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter} determines the request uri.
     * See {@link UrlPathHelper#getRequestUri(javax.servlet.http.HttpServletRequest).
     */
    @Override
    public Object run() {
        String serverName = getCurrentContext().getRequest().getServerName();
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String newURI = hostToContextPath.get(serverName) + request.getRequestURI();
        request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, newURI);
        return null;
    }
}

@anderius I'm trying your solution with no luck. I get that "no route found for uri: users" when sending requests to services using subdomains, like users.my-public-dns.com. Any help? Can you share an example hostToContextPath?

@fiunchinho I actually had some troubles combining it with other routes, but it should work given you have a route that matches on /**.

I did not proceed with this, however, as the plan for me is to migrate to Spring Cloud Gateway, which seems much better, and has this feature built-in.

This module has entered maintenance mode. This means that the Spring Cloud team will no longer be adding new features to the module. We will fix blocker bugs and security issues, and we will also consider and review small pull requests from the community.

Was this page helpful?
0 / 5 - 0 ratings