spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Forward Routing filter doesn't support path parameters for local forwarding

Open Anmol24 opened this issue 4 years ago • 14 comments

Describe the bug I am using forward scheme in my route to forward the request to local controller defined in the gateway application. My controller's API path is something like this:

/api/v1/service/{id}/path where id is path parameter. As per the documentation of Forward Routing filter, the path part of the request URL is overridden with the path in the forward URL. That means that whatever was mentioned in the forward:/mycustompath will be added to the url. In this case, i can't route the request to my controller where id will change for each request.

Is there a way to achieve this? We were earlier using Spring cloud zuul and we are now migrating to spring cloud gateway and this is not working for us. I am trying to write a custom filter for this where i modify the correct path in the filter. Is this correct approach for this?

Sample image

Anmol24 avatar Dec 29 '20 02:12 Anmol24

RewritePathGatewayFilterFactory can help you?

ctlove0523 avatar Jan 08 '21 02:01 ctlove0523

@ctlove0523 that wouldn't work for me as i need to forward the request to local endpoint within the gateway itself. The ForwardPathFilter modifies the request for me and i will have to write my own custom filter for this to handle the path as mentioned in the ticket above.

Anmol24 avatar Jan 08 '21 05:01 Anmol24

We should add some things to the exchange attributes like in ForwardPathFilter:

  • original path
  • original query parameters
  • original uri template variables

spencergibb avatar Jan 11 '21 19:01 spencergibb

Thanks @spencergibb that's exactly what i need to preserve the original path with request parameters. This was already being done in spring cloud zuul. Any idea if this will be available in spring cloud gateway anytime soon?

Anmol24 avatar Jan 12 '21 03:01 Anmol24

Not immediately. TBH a pull request will be there fastest way

spencergibb avatar Jan 12 '21 05:01 spencergibb

@spencergibb will simply adding the additional exchange attributes help? wouldn't we also have to modify the ForwardPathFilter to preserve the request and route it as it to the local endpoint.

Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
URI routeUri = route.getUri();
String scheme = routeUri.getScheme();
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
	return chain.filter(exchange);
}
exchange = exchange.mutate()
			.request(exchange.getRequest().mutate().path(routeUri.getPath()).build())
			.build();
return chain.filter(exchange);

As per the current implementation of ForwardPathFilter it just overrides anything after forward:/{newpath} and add this to the request path. What we want is to preserve the path and query parameters.

Another difference between spring cloud zuul and cloud gateway is the order of RoutePredicateHandlerMapping and ZuulHandlerMapping. ZuulHandlerMapping intercepts the request first with forward scheme, runs the zuul filters and then forwards the same request to local endpoint defined in the gateway application, where as in cloud gateway the request is intercepted first by RequestMappingHandlerMapping even if the gateway has defined a route with forward scheme. The spring cloud gateway filters are not run if the request is already handled by Request mapping handler.

Anmol24 avatar Jan 12 '21 07:01 Anmol24

The trouble is what part of the existing path should go there?

spencergibb avatar Jan 12 '21 21:01 spencergibb

if the original path has path variables and query parameters then ideally these should be added.

Anmol24 avatar Jan 13 '21 06:01 Anmol24

I just found another use-case for this suggestion: We have multiple SPAs in our static resources, for each app all unmatched paths should be forwarded to index.html:

uri: forward:/app/{appName}/index.html
predicates:
  - Path=/app/{appName}/**

Currently we are using a workaround using a dummy uri (forward:...) with a SetPath filter instead.

hwanders avatar Feb 10 '22 20:02 hwanders

What i ended up doing in my code was to write a custom filter to route to the internal path to my uri.

uri: forward:/
predicates:
      - Path=/api/v1/abc/*/xyz
      - Method=GET

Here is the CustomForwardPathFilter that i ended up creating:

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
    
        URI routeUri = route.getUri();
        String scheme = routeUri.getScheme();
        if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        String customPath = String.format("/internal%s", request.getPath().toString());
         
        return Mono.just(exchange.mutate()
                .request(exchange.getRequest().mutate().path(customPath).build())
                .build())
                .doOnEach(LogHelper.logOnNext(webExchange -> log.debug("Forwarding to URI: {}", webExchange.getRequest().getPath())))
                .flatMap(webExchange -> this.getDispatcherHandler().handle(webExchange));
    }

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

This should run before the ForwardPathFilter . Here is my mapping for the api defined in the gateway application.

@GetMapping(path = "/internal/api/v1/abc/{id}/xyz")
    public Mono<String> getResponse() {
       // custom code here
    }

The filter modifies the request from /api/v1/abx/*/xyz to /internal/api/v1/abx/*/xyz.

Anmol24 avatar Feb 11 '22 15:02 Anmol24

@Anmol24 can you please share the whole filter?

leventozkann avatar Apr 05 '22 20:04 leventozkann

@leventozkann You only need to add this additionally to use the dispatcher handler bean, rest of the code is same.

private final ObjectProvider<DispatcherHandler> dispatcherHandlerProvider;

 // do not use this dispatcherHandler directly, use getDispatcherHandler() instead.
 private volatile DispatcherHandler dispatcherHandler;

 public MyForwardPathFilter(
         ObjectProvider<DispatcherHandler> dispatcherHandlerProvider) {
     this.dispatcherHandlerProvider = dispatcherHandlerProvider;
 }

 private DispatcherHandler getDispatcherHandler() {
     if (dispatcherHandler == null) {
         dispatcherHandler = dispatcherHandlerProvider.getIfAvailable();
     }

     return dispatcherHandler;
 }

Anmol24 avatar Apr 30 '22 05:04 Anmol24

你好,你的邮件我已经收到,每天会在晚上查看邮件并回复。急事请打电话

ctlove0523 avatar Apr 30 '22 05:04 ctlove0523

@spencergibb Is this feature released in 4.x spring cloud gateway version? If not then by when it will become available?

deepak-chhetri avatar Feb 14 '24 16:02 deepak-chhetri