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

Support webflux.base-path in StripFilter and possibly others

Open Saljack opened this issue 5 years ago • 14 comments

Describe the bug StripPrefix filter (and maybe RewritePath too) isn't able to remove a contextPath if there is any set. It started with upgrading to Spring Boot 2.3. I think it is connected with introducing spring.webflux.base-path but I don't use it. I use ForwardedHeaderTransformer for adding na prefix from a header X-Forwarded-Prefix (SCG runs behind NGINX proxy). This transformer sets contextPath to prefix builder.contextPath(prefix). StripPrefix filter strips only in path and not in contextPath request.mutate().path(newPath) and then it throws an IllegalArgumentException if build() method is called. See a stacktrace:

java.lang.IllegalArgumentException: Invalid contextPath '/admin': must match the start of requestPath: '/sba/'
    at org.springframework.http.server.DefaultRequestPath.validateContextPath(DefaultRequestPath.java:82)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
 Error has been observed at the following site(s):
    |_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.cloud.sleuth.instrument.web.TraceWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
    |_ checkpoint ⇢ HTTP GET "/admin/sba/" [ExceptionHandlingWebHandler]
 Stack trace:
            at org.springframework.http.server.DefaultRequestPath.validateContextPath(DefaultRequestPath.java:82)
            at org.springframework.http.server.DefaultRequestPath.initContextPath(DefaultRequestPath.java:57)
            at org.springframework.http.server.DefaultRequestPath.<init>(DefaultRequestPath.java:42)
            at org.springframework.http.server.RequestPath.parse(RequestPath.java:60)
            at org.springframework.http.server.reactive.AbstractServerHttpRequest.<init>(AbstractServerHttpRequest.java:81)
            at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder$MutatedServerHttpRequest.<init>(DefaultServerHttpRequestBuilder.java:197)
            at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder.build(DefaultServerHttpRequestBuilder.java:136)
            at org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory$1.filter(StripPrefixGatewayFilterFactory.java:71)
            at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44)
            at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:118)

Sample Configuration

spring:
  cloud:
    gateway:
      routes:
      - id: sba
        uri: http://localhost:20010
        predicates:
        - Path=/admin/sba/**
        filters:
        - StripPrefix=1

Enable ForwardedHeaderTransformer

@Configuration
public class ForwardedHeaderConfiguration {
  @Bean
  public ForwardedHeaderTransformer forwardedHeaderTransformer() {
    return new ForwardedHeaderTransformerImpl();
  }
}

Call http://localhost:{applicationPort}/sba with X-Forwarded-Prefix: /admin

Saljack avatar Sep 10 '20 09:09 Saljack

Special care will be needed in RewritePath, SetPath, StripPath and PrefixPath to set the context. See #2013 for details.

spencergibb avatar Nov 03 '20 17:11 spencergibb

I upgraded to this version because I needed the webflux.base-path to solve the issues when using the Gateway with Spring Security. Specifically, how to add the path prefix to the authorization/login url's. Are there any workarounds to this issue until the contextPath can be handled?

mzeitlin avatar Nov 12 '20 18:11 mzeitlin

@mzeitlin I'm not sure, isn't that a spring security question?

spencergibb avatar Nov 12 '20 19:11 spencergibb

I created copy of StripFilter and set contextPath to null:

request.mutate().contextPath(null).path(newPath).build()

Saljack avatar Nov 12 '20 19:11 Saljack

@spencergibb because looking at the stack trace, it looks like the issue is coming from Spring Gateway, not Spring Security. @Saljack , thanks. I will try something similar for the RewritePathFilter which is where my error seems to be coming from.


java.lang.IllegalArgumentException: Invalid contextPath '/gateway': must match the start of requestPath: '/login/oauth2/callback/oam'
--
  | at org.springframework.http.server.DefaultRequestPath.validateContextPath(DefaultRequestPath.java:82) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
  | Error has been observed at the following site(s):
  | \|_ checkpoint ? org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.csrf.CsrfWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
  | \|_ checkpoint ? org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
  | \|_ checkpoint ? HTTP GET "/gateway/login/oauth2/callback/oam?code=eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIng1dCI6InRzQVhOV0N1UjJOMWFFZkcxWThEZ1FreGIyYyIsImtpZCI6Im9yYWtleSJ9.eyJvcmFjbGUub2F1dGgucmVkaXJlY3QtdXJpIjoiaHR0cHM6Ly9jc20tZGV2LXNoYXJlZC5hcHBzLmRldnRlc3Qub3BlbnNoaWZ0LmNhLnN0YXRlLnNidS9nYXRld2F5L2xvZ2luL29hdXRoMi9jYWxsYmFjay9vYW0iLCJzdWIiOm51bGwsIm9yYWNsZS5vYXV0aC51c2VyX29yaWdpbl9pZF90eXBlIjoiTERBUF9VSUQiLCJvcmFjbGUub2F1dGgudXNlcl9vcmlnaW5faWQiOiJ6ZWl0bGlubUBzdGF0ZS5nb3YiLCJpc3MiOiJ3d3cub3JhY2xlLmV4YW1wbGUuY29tIiwib3JhY2xlLm9hdXRoLnN2Y19wX24iOiJPQXV0aFNlcnZpY2VQcm9maWxlIiwiaWF0IjoxNjA1Mjc3MTU3LCJvcmFjbGUub2F1dGgudGtfY29udGV4dCI6ImF6YyIsImV4cCI6MTYwNTI3ODA1NywicHJuIjpudWxsLCJqdGkiOiJiMjZlYThkMC1mYjRiLTRkMDktYjgxMy01Yzc3NzUzOThjYjMiLCJvcmFjbGUub2F1dGguY2xpZW50X29yaWdpbl9pZCI6ImN1c3RvbWVyQ2xpZW50IiwidXNlci50ZW5hbnQubmFtZSI6IkRlZmF1bHREb21haW4iLCJvcmFjbGUub2F1dGguaWRfZF9pZCI6IjEyMzQ1Njc4LTEyMzQtMTIzNC0xMjM0LTEyMzQ1Njc4OTAxMiJ9.duYMhHLwgUuiivNRTcDSZ6gzfHsbmAiXx8Xn38qHdiShsxd9M4YgBt1Mk7b5qaYXvJUDFqk-ZHiEQiWfL2v4hFJy_tGH4HWd0Ddu7CHZ2zTRu5P5aAvYDbCE5LLvIpfIVtu_uzA1HNF0nKu3UveMk7oRf1SeIgXZqbt9bG_fdJs&state=8ABoVzaMGeOlDRPuT02gLY_YU6RHg91PYiKSpToM21c=" [ExceptionHandlingWebHandler]
  | Stack trace:
  | at org.springframework.http.server.DefaultRequestPath.validateContextPath(DefaultRequestPath.java:82) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.DefaultRequestPath.initContextPath(DefaultRequestPath.java:57) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.DefaultRequestPath.<init>(DefaultRequestPath.java:42) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.RequestPath.parse(RequestPath.java:60) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.reactive.AbstractServerHttpRequest.<init>(AbstractServerHttpRequest.java:81) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder$MutatedServerHttpRequest.<init>(DefaultServerHttpRequestBuilder.java:197) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder.build(DefaultServerHttpRequestBuilder.java:136) ~[spring-web-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
  | at org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory$1.filter(RewritePathGatewayFilterFactory.java:71) ~[spring-cloud-gateway-core-2.2.3.RELEASE.jar!/:2.2.3.RELEASE]
  | at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44) ~[spring-cloud-gateway-core-2.2.3.RELEASE.jar!/:2.2.3.RELEASE]
  | at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:118) ~[spring-cloud-gateway-core-2.2.3.RELEASE.jar!/:2.2.3.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:173) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFilterWhen$MonoFilterWhenMain.onNext(MonoFilterWhen.java:140) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2344) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFilterWhen$MonoFilterWhenMain.onSubscribe(MonoFilterWhen.java:103) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:243) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDematerialize$DematerializeSubscriber.onNext(FluxDematerialize.java:91) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDematerialize$DematerializeSubscriber.onNext(FluxDematerialize.java:38) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:267) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:225) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDematerialize$DematerializeSubscriber.request(FluxDematerialize.java:120) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:228) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDematerialize$DematerializeSubscriber.onSubscribe(FluxDematerialize.java:70) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:141) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:107) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onNext(FluxFilterFuseable.java:112) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2344) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.request(FluxFilterFuseable.java:184) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:103) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onSubscribe(FluxFilterFuseable.java:81) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:160) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:78) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2346) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2152) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2026) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:96) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:160) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:838) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:600) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:580) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:457) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:289) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:225) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:363) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) [reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onComplete(FluxDefaultIfEmpty.java:100) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxFilter$FilterSubscriber.onComplete(FluxFilter.java:160) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onComplete(FluxMap.java:262) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1783) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:320) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:337) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:192) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:249) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) ~[reactor-core-3.3.9.RELEASE.jar!/:3.3.9.RELEASE]
  | at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_222]
  | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[na:1.8.0_222]
  | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[na:1.8.0_222]
  | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_222]
  | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_222]
  | at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_222]

mzeitlin avatar Nov 13 '20 14:11 mzeitlin

I had a similar issue, as list in #1759. I resolved it using: https://github.com/spring-cloud/spring-cloud-gateway/issues/1759#issuecomment-736000670

sumitsarkar avatar Nov 30 '20 19:11 sumitsarkar

I also have this issue with StripPrefix. I'm using SCG behind NGINX reverse proxy.

rigon avatar May 27 '21 15:05 rigon

@spencergibb 2 questions.

  1. Why is this not bug?
  2. Still need help on this?

MazizEsa avatar Jun 08 '21 15:06 MazizEsa

PRs welcome.

spencergibb avatar Jun 08 '21 15:06 spencergibb

@spencergibb so which branch I am suppose to branch off from?

MazizEsa avatar Jun 12 '21 00:06 MazizEsa

PRs welcome.

This is a huge quagmire. I would happily provide a PR, but for a solid fix a view conceptual clarifications are necessary.

Look at this route configuration:

spring:
  cloud:
    gateway:
      routes:
      - id: remote-route
        uri: https://upstream-service
        predicates:
        - Path=/api/**
      - id: local-route
        uri: forward:/local/path
        predicates:
        - Path=/local/**

Now if a context-path (base-path) is defined, all given path patterns should continue to work, so they must be considered as app-local paths without context-path. The fix for the Path-predicate is trivial. The context-path must also be preserved for the local route.

But how shall the context-path be handled for the upstream service? Is it included by default, or is it stripped by default?

One could argue that all paths in the configuration are app-local paths without context-path, so the context-path should be stripped by default (in other words, the scope of the context-path is only the local gateway itself, without affecting outgoing routes).

On the other hand, this might cause confusion when looking at the full incoming and outgoing paths (request URIs), so it might be a valid strategy that the context-path has to be explicitly stripped with an additional "StripContextPath" filter.

Additionally, a lot of clarifications are necessary in the docs, e.g. for StripPrefix and all other components manipulating the path.

Any ideas?

kpeterbauer avatar Oct 07 '22 09:10 kpeterbauer

A server =>
spring:
  webflux:
    base-path: "/aaa"

B server =>
spring:
  webflux:
    base-path: "/bbb"

A server route B server
routes:
     filters:
            - ContextPathRewritePath=/aaa/api, /bbb/api

RewritePathGatewayFilterFactory.java#L70

Add ".contextPath("/")"

From
ServerHttpRequest request = req.mutate().path(newPath).build();
To
ServerHttpRequest request = req.mutate().path(newPath).contextPath("/").build();
@Component
public class ContextPathRewritePathGatewayFilterFactory extends RewritePathGatewayFilterFactory {

    @Override
    public GatewayFilter apply(Config config) {
        String replacement = config.getReplacement().replace("$\\", "$");
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest req = exchange.getRequest();

                addOriginalRequestUrl(exchange, req.getURI());
                String path = req.getURI().getRawPath();

                String newPath = path.replaceAll(config.getRegexp(), replacement);
                ServerHttpRequest request = req.mutate().path(newPath).contextPath("/").build();

                exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());

                return chain.filter(exchange.mutate().request(request).build());
            }
        };
    }

}

leotu avatar Oct 24 '22 10:10 leotu

Thanks @leotu for the workaround.

It's basically just a copy of the RewritePathGatewayFilterFactory with one line different. Instead of copying the source - that might deviate over time - we can set the context path and then simply call the default RewritePathGatewayFilterFactory.

@Component
@Primary
@AllArgsConstructor // Lombok
public static class ContextPathRewritePathGatewayFilterFactory extends RewritePathGatewayFilterFactory {
    private final RewritePathGatewayFilterFactory defaultGatewayFilterFactory;

    @Override
    public GatewayFilter apply(final Config config) {
        return (exchange, chain) -> {
            final ServerHttpRequest request = exchange.getRequest().mutate().contextPath("/").build();
            return defaultGatewayFilterFactory.apply(config)
                    .filter(exchange.mutate().request(request).build(), chain);
        };
    }
}

sdoeringNew avatar Feb 08 '23 12:02 sdoeringNew

Just wanted to point out, that solution in the comment above by @sdoeringNew is not optimal - its creating new RewritePathGatewayFilter object on each request (via calling apply(config) method), which can be costly on high load gateways.

My advice is to pre-create the delegate filter and use it in closure, something like:

@Override
public GatewayFilter apply(final RewritePathGatewayFilterFactory.Config config)
{
    GatewayFilter delegate = rewritePathGatewayFilterFactory.apply(config);
    return (exchange, chain) -> {
        final ServerHttpRequest request = exchange.getRequest().mutate().contextPath("/").build();
        return delegate.filter(exchange.mutate().request(request).build(), chain);
    };
}

marfer avatar Jul 09 '24 16:07 marfer

Hi i had the same issue by using the RewritePath filter the above provided workaround solved the issue. But please can this be fixed withing the spring cloud gateway?

Spring Gateway: 4.1.5

If anybody uses spring.webflux.base-path the following Issue could also be interesting: https://github.com/spring-projects/spring-security/issues/8967

HeneryHawk avatar Nov 22 '24 09:11 HeneryHawk

Hi @spencergibb,

i would really like to create a pr but i basically don't know what to do? Why not just set the context path to "/" in the RewritePathGatewayFilterFactory? Would this break something? I mean this would add a lot of flexibility because you can forward requests to the downstream services without having to consider the context path. This is basically a valid feature for me as the paths of the downstream services do not have to be the same as those of the gateway. Or am i wrong here?

Regards

HeneryHawk avatar Nov 26 '24 13:11 HeneryHawk