Spring Webflux, How to forward to index.html to serve static content
Asked Answered
B

5

28

spring-boot-starter-webflux (Spring Boot v2.0.0.M2) is already configured like in a spring-boot-starter-web to serve static content in the static folder in resources. But it doesn't forward to index.html. In Spring MVC it is possible to configure like this:

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("forward:/index.html");
}

How to do it in Spring Webflux?

Butterfish answered 17/7, 2017 at 14:47 Comment(0)
F
39

Do it in WebFilter:

@Component
public class CustomWebFilter implements WebFilter {
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    if (exchange.getRequest().getURI().getPath().equals("/")) {
        return chain.filter(exchange.mutate().request(exchange.getRequest().mutate().path("/index.html").build()).build());
    }

    return chain.filter(exchange);
  }
}
Fortunato answered 19/7, 2017 at 12:32 Comment(2)
This is ok but for dynamic requirements it is not that okUlrick
@Ulrick depends what you mean dynamic, but it works just fine with things /blog/{id}/comments for example (that being a dynamic route).Strawn
I
13
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Bean
public RouterFunction<ServerResponse> indexRouter(@Value("classpath:/static/index.html") final Resource indexHtml) {
return route(GET("/"), request -> ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml));
}
Ines answered 14/5, 2018 at 6:50 Comment(2)
This one was better than filtersUlrick
Thank you, this was the last piece in the puzzle.Nakitanalani
F
9

There's a ticket in the Spring Boot tracker for this.

Frederico answered 19/7, 2017 at 14:4 Comment(0)
H
3

The same by using WebFlux Kotlin DSL:

@Bean
open fun indexRouter(): RouterFunction<ServerResponse> {
    val redirectToIndex =
            ServerResponse
                    .temporaryRedirect(URI("/index.html"))
                    .build()

    return router {
        GET("/") {
            redirectToIndex // also you can create request here
        }
    }
}
Homochromatic answered 3/4, 2019 at 13:26 Comment(2)
How do I do it by forwarding instead of redirect?Partner
@lfmunoz, I didn't find Forward http status. Probably, there is pre-built function. However you can construct your own response with construction like this: ServerResponse .status(HttpStatus.I_AM_A_TEAPOT) .header("my-header", "2342"), just put right status and right headers thereHomochromatic
W
1

If you came here like me looking for a solution that works for dynamic routes as a fallback solution for SPA use cases, there is an open spring issue here

The following WebExceptionHandler solved it for me

@Component
@Order(-2)
public class HtmlRequestNotFoundHandler implements WebExceptionHandler {

    private final DispatcherHandler dispatcherHandler;

    private final RequestPredicate PREDICATE = RequestPredicates.accept(MediaType.TEXT_HTML);

    public HtmlRequestNotFoundHandler(DispatcherHandler dispatcherHandler) {
        this.dispatcherHandler = dispatcherHandler;
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
        if (
            isNotFoundAndShouldBeForwarded(exchange, throwable)
        ) {
            var forwardRequest = exchange.mutate().request(it -> it.path("/index.html"));
            return dispatcherHandler.handle(forwardRequest.build());
        }
        return Mono.error(throwable);
    }

    private boolean isNotFoundAndShouldBeForwarded(ServerWebExchange exchange, Throwable throwable) {
        if (throwable instanceof ResponseStatusException
            && ((ResponseStatusException) throwable).getStatusCode() == HttpStatus.NOT_FOUND
        ) {

            var serverRequest = ServerRequest.create(exchange, Collections.emptyList());
            return PREDICATE.test(serverRequest);
        }

        return false;
    }

}
Waiver answered 24/6, 2023 at 17:0 Comment(1)
This should be the accepted answer! If I got it right this replicates succesfully nginx's "try_files" behaviour for hosting SPAs.Bikol

© 2022 - 2024 — McMap. All rights reserved.