spring-cloud / spring-cloud-netflix

Integration with Netflix OSS components
http://cloud.spring.io/spring-cloud-netflix/
Apache License 2.0
4.87k stars 2.44k forks source link

Dynamically re-route all Zuul proxied requests to single URL #1754

Closed ashleyconnor closed 7 years ago

ashleyconnor commented 7 years ago

I'm trying to forward all requests to my API to a single endpoint based upon some condition.

The Gateway app runs on port 8080

I've created the following filter:

public class OutagePeriodFilter extends ZuulFilter {

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

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

    @Override
    public boolean shouldFilter() {
        return isOutagePeriod();
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        // if outage - redirect everyone to http://localhost:8082/outage
        try {
            String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
                                     .toUriString();
            ctx.setRouteHost(new URL(url));
        } catch(MalformedURLException mue) {
            log.error("Cannot forward to outage period website");
        }
        return null;
    }

    private boolean isOutagePeriod() {
        // returns true if outage
    }
}

However after making a request to http://localhost:8080/alerts/public my API logs show:

2017-03-03 16:11:30.735 EST 0037 DEBUG                    o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/outage/alerts/public'; against '/beans/'

For some reason it appends the original PATH to the redirect PATH resulting in a request to /outage/alerts/public which doesn't exist. I want to make a request to just /outage.

Putting a breakpoint in my filter just as the ctx.setRouteHost() is called shows that correct URL (http://10.50.36.43:8082/outage/).

My application.properties:

zuul.routes.api.path=/api/**
zuul.routes.api.url=http://localhost:8082/
spencergibb commented 7 years ago

It should be a "pre" filter and it should run before PreDecorationFilter. I think setting the requestURI field in RequestContext will help. Otherwise spring cloud appends the original path.

ashleyconnor commented 7 years ago

The request still makes it to the original destination.

New code:

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

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        try {
            String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
                                     .toUriString();
            ctx.setRouteHost(new URL(url));
            ctx.set("requestURI", url);
        } catch(MalformedURLException mue) {
            log.error("Cannot forward to outage period endpoint");
        }
        return null;
    }

Breakpoint confirming it's run before PreDecorationFilter.

2017-03-06 11_49_23-virtualbox

spencergibb commented 7 years ago

What happens in ProxyRequestHelper.buildZuulRequestURI()?

spencergibb commented 7 years ago

My guess is that predecorationfilter is still running later. Maybe it needs to happen after predecoration.

ashleyconnor commented 7 years ago

Changing the filter to run after PreDecorationFilter and setting ctx.set("requestURI", url) worked.

I removed ctx.setRouteHost(new URL(url)); as it doesn't appear to be required.

Final code:

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
                                     .toUriString();
        ctx.set("requestURI", url);
        return null;
    }
oterrien commented 7 years ago

Strange because I had the same need and I succeeded thanks to this post but with ctx.setRouteHost

GhanshyamRawat-eGov commented 7 years ago

This solution is not working for me, please help me: My pre filter class code:

@Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest();

    System.out.println(
            "Request Method : " + request.getMethod() + " Request URL : " + request.getRequestURL().toString());
    String body = readRequestBody(request);
    String url1 = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/wf-service").path("/_create")
            .build().toUriString();
    try {
        ctx.setRouteHost(new URL(url1));
        ctx.set("requestURI", url1);
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    requestParser.setReqAsMap(body);
    if (requestParser.hasRequestInfo()) {
        System.out.println("has req info");
    }
    return null;
}
@Override
public boolean shouldFilter() {
    return true;
}
@Override
public int filterOrder() {
    return 1;
}
@Override
public String filterType() {
    return "pre";
}

application.properties

zuul.routes.wf.path=/wf-service/** zuul.routes.wf.stripPrefix=false zuul.routes.wf.url=http://localhost:8082/

qcastel commented 7 years ago

The previous answers seems to indicate that the filter needs to be triggered after the PreDecoration.

Have you try to change the filterOrder to:

@Override
public int filterOrder() {
    return 6;
}
GhanshyamRawat-eGov commented 7 years ago

i change this also now it appending complete url twise { "timestamp": 1510720356916, "status": 404, "error": "Not Found", "message": "No message available", "path": "/wf-service/_createhttp:/localhost:8082/wf-service/_create" }

san13692 commented 6 years ago

Worked for me too by changing the FilterOrder to 6. I was trying to prevent routing from happening.And so removed "routeHost" from the RequestContext in the Pre Filter. Thanks @spencergibb

yuvalbar84 commented 6 years ago

Hi all, trying to achieve the same thing, but the ctx.setRouteHost() actually sends a 302 (Redirect) to my browser, and therefore it is not proxied by Zuul. Any suggestion would be highly appreciated.

aswinde03032008 commented 6 years ago

This solution is not working for me, please help me: My pre filter class code:

@override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest();

  System.out.println(
          "Request Method : " + request.getMethod() + " Request URL : " + request.getRequestURL().toString());
  String body = readRequestBody(request);
  String url1 = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/wf-service").path("/_create")
          .build().toUriString();
  try {
      ctx.setRouteHost(new URL(url1));
      ctx.set("requestURI", url1);
  } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
  }

  requestParser.setReqAsMap(body);
  if (requestParser.hasRequestInfo()) {
      System.out.println("has req info");
  }
  return null;
}
@Override
public boolean shouldFilter() {
  return true;
}
@Override
public int filterOrder() {
  return 1;
}
@Override
public String filterType() {
  return "pre";
}

application.properties

zuul.routes.wf.path=/wf-service/** zuul.routes.wf.stripPrefix=false zuul.routes.wf.url=http://localhost:8082/

GhanshyamRawat -> is resolved now? I am also getting same issue.

aswinde03032008 commented 6 years ago

Changing the filter to run after PreDecorationFilter and setting ctx.set("requestURI", url) worked.

I removed ctx.setRouteHost(new URL(url)); as it doesn't appear to be required.

Final code:

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8082").path("/outage").build()
                                     .toUriString();
        ctx.set("requestURI", url);
        return null;
    }

I did the same but it did not work Here is my code : bootstrao.yml: zuul.routes.cp.path: /navxcp/** zuul.routes.cp.service-id: navxcp

zuul.routes.web.path: /navx/** zuul.routes.web.service-id: navxweb

Prefilter: requestUri = requestUri.replace("navx", "navxcp"); ctx.set(FilterConstants.REQUEST_URI_KEY,requestUri ); log.info("--- request-uri has been modified -> {}",requestUri);

actually I am hitting http://host:port/gateway/navx/getData Here getData endpoint is available on navxcp microservice So I am trying to change the requestURIin pre-filter so that it should be routed to navxcp

sail-y commented 5 years ago

I found one way to do this.

here is my zuul config:

zuul:
  routes:
    sevice-demo-b:
      path: /b/**
      url: http://localhost:5010
    sevice-demo-a: /a/**

The filter should be pre and before PRE_DECORATION_FILTER

the requestUri attribute can change you uri after preDecorationFilter choose the route, but if you want to change the route, you need use this code request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, url);

here is the code

public class ChangeUriFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        final String uri = request.getRequestURI();

        return uri.contains("/helloA");

    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        final HttpServletRequest request = ctx.getRequest();

        String uri = "/b/helloB"

        // change uri 
        request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, url);

        return null;
    }
}

when you request 'http://host:port/a/helloA', it will forward to http://host:port/b/helloB

😆