28

I've been trying to find a way to set the context path for a webflux application. I know I can configure it using

server.servlet.context-path

if I deploy a servlet, but I would like to achieve it with webflux, without having to explicitly add the path to every route or use MVC.

Marcus Lindwall
  • 305
  • 1
  • 4
  • 6
  • Possible duplicate of [change spring boot 2.0 context-path](https://stackoverflow.com/questions/49192878/change-spring-boot-2-0-context-path) – Brian Clozel Mar 09 '18 at 16:24
  • 1
    I'm not looking to use context-path, I realize it's a part of servlet containers. I'm looking for an equivalent solution to use with webflux. – Marcus Lindwall Mar 09 '18 at 17:41

12 Answers12

51

According to this

There is servlet in the name of the property which should be a hint that won't work with webflux.

With springboot v2.3, you can put this in your properties file

spring.webflux.base-path=/your-path

release-notes reference: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#configurable-base-path-for-webflux-applications

  • 2
    Does not seem to have any effect. Not in this version, not in current 2.4.2. To verify there isn't conflict with servet-based approach, I put there also server.servlet.contextPath with different value, but neither was reflected, and controller annotated by @RequestMapping(path = {"/test"}) was accessible only directly via http://localhost:8080/test. – Martin Mucha Jan 27 '21 at 15:35
  • 2
    Should be marked as correct answer in 2022!!! Works out of the box – Laess3r Jan 25 '22 at 14:32
16

You can use web filter to make WebFlux support contextPath

@Bean
public WebFilter contextPathWebFilter() {
    String contextPath = serverProperties.getServlet().getContextPath();
    return (exchange, chain) -> {
        ServerHttpRequest request = exchange.getRequest();
        if (request.getURI().getPath().startsWith(contextPath)) {
            return chain.filter(
                exchange.mutate()
                .request(request.mutate().contextPath(contextPath).build())
                .build());
        }
        return chain.filter(exchange);
    };
}
Ram Koti
  • 2,203
  • 7
  • 26
  • 36
刘家华
  • 191
  • 1
  • 8
  • 2
    This solution supports all servers, including Netty – 刘家华 Jun 23 '18 at 05:32
  • 1
    I tried but it doesn't work. Also, I don't understand the code. You get a url with the context path and then change it with context path? Basically it has no change. – John Zhang Sep 06 '18 at 13:31
  • WebFlux doesn't auto recognize which part is the context path, so the above code uses WebFilter to tell WebFlux which part is the context path for each request. @JohnZhang – 刘家华 Sep 10 '18 at 07:06
  • I copied the above code in my class with some annotations like @Configuration etc., then I change the first statement to 'String contextPath = "api"'. However, I cannot access http://localhost/api/.... In the debug mode, the code is not called. Any idea? – John Zhang Sep 10 '18 at 11:37
  • try for 'String contextPath = "/api/"' @JohnZhang – 刘家华 Sep 15 '18 at 01:21
  • Hi, what is serverProperties ? – Melardev Feb 28 '19 at 16:38
  • 1
    @Melardev bean of type org.springframework.boot.autoconfigure.web.ServerProperties – 刘家华 Mar 01 '19 at 07:31
  • Script partly does the job, requests starting with servlet path are recognised. However, requests without are still valid. Ex: /mypath/products and /products are both valid. – Ermintar Apr 15 '19 at 17:28
  • @Ermintar Yes, you can block incorrect URL requests in this method – 刘家华 Apr 19 '19 at 09:56
8

I was facing a similar issue with the spring.webflux.base-path (which didn't seem to be working as expected) in webflux-reactive-spring-web and I realized that I had autoconfiguration disabled.

A manually workaround is:

@Bean
public WebFluxProperties webFluxProperties(){
    return new WebFluxProperties();
}
georgep
  • 81
  • 1
  • 2
  • spring.webflux.base-path worked for me in Spring Boot 2.4.7 – Philippe Jan 07 '22 at 19:08
  • hello @Philippe im using spring boot 2.4.7 but spring.webflux.base-path is not working for me. any ideas please ? – James May 26 '22 at 12:00
  • @James did you confirm that you are in fact on spring boot 2.4.7? The version is printed in the log when you start your project. If you are using gradle as you build system, you can use the dependencyInsight task to confirm the version of spring boot dependencies that are pulled in. The property key spring.webflux.base-path has been around since 2.3, as announced in https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#configurable-base-path-for-webflux-applications – Philippe Jun 02 '22 at 14:12
4

Here's my way of doing it with Tomcat Reactive:

@Configuration
public class TomcatReactiveWebServerConfig extends TomcatReactiveWebServerFactory {

    @Value("${server.servlet.context-path}")
    private String contextPath;

    /**
     * {@inheritDoc}
     */
    @Override
    protected void configureContext(final Context context) {

        super.configureContext(context);

        if (StringUtils.isNotBlank(this.contextPath)) {
            context.setPath(this.contextPath);
        }
    }
}
Sancho
  • 417
  • 4
  • 20
3

For Undertow I managed to add a context path by creating a customized UndertowReactiveWebServerFactory:

 @Bean
public UndertowReactiveWebServerFactory undertowReactiveWebServerFactory(
        @Value("${server.servlet.context-path}") String contextPath) {
    return new UndertowReactiveWebServerFactory() {
        @Override
        public WebServer getWebServer(HttpHandler httpHandler) {
            Map<String, HttpHandler> handlerMap = new HashMap<>();
            handlerMap.put(contextPath, httpHandler);
            return super.getWebServer(new ContextPathCompositeHandler(handlerMap));
        }
    };
}
  • 2
    The solution could be easily adapted for `NettyReactiveWebServerFactory` – Dmytro Boichenko Aug 21 '18 at 14:26
  • Thank you, perfect and most elegant solution for now. Ideally, it's possible to add factories of all three available servers (netty, tomcat and undertow) by instantiating proper beans and then use conditionals (@Conditional annotations) to determine which server is used within the application. This way, even if you switch a container, the proper bean will be created and context path will be added – skryvets Oct 28 '19 at 20:00
2

For use cases where WebFlux application is behind load balancer/proxy you can use dedicated class - ForwardedHeaderTransformer that will extract path context from X-Forwarded-Prefix and will add it to ServerHttpRequest.

Doing so you won't need to modify global context-path (which does not make sense in WebFlux)

More about it here: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-web-handler-api

pixel
  • 24,905
  • 36
  • 149
  • 251
1

Here is an example of configuring the context path for WebFlux using Netty server based on a comment by @Dmytro Boichenko. You can also include customizers to configure the port and other properties.

@Configuration
public class NettyServerConfig {

    @Value("${server.port}")
    private int port;

    @Value("${server.context.path}")
    private String contextPath;

    @Bean
    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
            NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory() {
                @Override
                public WebServer getWebServer(HttpHandler httpHandler) {
                    Map<String, HttpHandler> handlerMap = new HashMap<>();
                    handlerMap.put(contextPath, httpHandler);
                    return super.getWebServer(new ContextPathCompositeHandler(handlerMap));
                }
        };
        webServerFactory.addServerCustomizers(portCustomizer());
        return webServerFactory;
    }

    public NettyServerCustomizer portCustomizer() {
        return new NettyServerCustomizer() {
            @Override
            public HttpServer apply(HttpServer httpServer) {
                return httpServer.port(port);
            }
        };
    }
}

bitbyteboi
  • 11
  • 2
1

You can use web Filter, as mentioned in above answers, but you can do one more thing. Write a Base Controller and Extend every class to that Base Controller. For example:

Base Controller.java

@RestController
@RequestMapping("/{base_url}")
public abstract class BaseController {
}

NewController.java

@RestController
public class NewController extends BaseController{
  @Autowired
  DatabaseClient databaseClient;

  @GetMapping("/status")
  public Mono<Map<String, String>> status() {
    return databaseClient.execute("SELECT 'ok'").
      map(row -> singletonMap("status", row.get(0, String.class)))
      .one();
  }
}

So now you can hit /{base_url}/status

Luv
  • 169
  • 2
  • 13
  • Thanks a lot. This helped me to get a "base path" for a specific bunch of controllers while at the same time keep `/actuator/health` on root level. – Kai Stapel Jul 13 '20 at 13:57
1

Spring webflux version 2.3.4.RELEASE

need two elements, first declare a bean for enable weflux properties

@Bean public WebFluxProperties webFluxProperties(){
return new WebFluxProperties(); 
}

Second define the correct path

spring.webflux.base-path = mypath
Dharman
  • 30,962
  • 25
  • 85
  • 135
0

If you are configuring yourself the server (if you’re not using Spring Boot), you can setup a ContextPathCompositeHandler that wraps several handlers itself.

If you are using Spring Boot, this feature is not supported currently.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • Thanks, we're using Spring Boot mostly, so unfortunately we can't use this for now. Hopefully support for this will be added in the future, it would ease our transition to Spring Boot 2 in existing pipelines. – Marcus Lindwall Mar 12 '18 at 08:31
  • how would you plan on using it? The Spring Framework feature I described means you can deploy several handlers within the same application, i.e. not deploying several applications on the same container. – Brian Clozel Mar 12 '18 at 08:53
  • Our loadbalance configuration required each application have a context path, but we've solved this restriction in our pipeline. – Marcus Lindwall Mar 13 '18 at 08:58
  • 1
    Using Spring Boot + WebFlux too, my workaround was to write a `WebFilter` with `@Order(HIGHEST_PRECEDENCE)` and remove the context path from any incoming request so that the rest of the application remains unaware of the context. – Ghurdyl Jun 05 '18 at 14:37
0

I was having the same issue since the loader balancer bases on the context path to route to different back end apps. One way to get around Spring Boot Webflux w/ the context path is using variables in the @XXXXMapping annotations. For instance @RequestMapping(value = "${server.servlet.context-path}/subpath")

0

You can try this solution, hope to help you.

https://github.com/spring-projects/spring-boot/issues/22162

In my situation I follow the suggestion step by step as below

  1. Add both spring-boot-starter-web and spring-boot-starter-webflux to pom.xml
  2. Add spring.webflux.base-path=/service-name
  3. Update main class of spring boot application

SpringApplication springApplication = new SpringApplication(DscUserManagementService.class); springApplication.setWebApplicationType(WebApplicationType.REACTIVE); springApplication.run(args);

  1. Finally, let try API ip:port/service-name/...

Thank you wilkinsona

phancuongviet
  • 311
  • 2
  • 11