Spring Webflux Redirects

Spring Webflux Redirects

Written by Ketone Maniac on Feb 5th, 2021 Views Report Post

Aha, my first blog post.

As a guy who missed the early days of Spring, it's sometimes confusing to see how many different ways the same thing could be done. Anyway I think I've experimented enough to get me document down all these and hopefully help somebody down the road with an easy copy and paste :)

My work project uses the latest Spring Boot (2.3.x at the time of writing) with full Reactive webflux REST controllers. My task is to add a redirect to one of the endpoints based on certain conditions.

For all the time I've been working with Spring projects, it's always with RestController with hooked to some other front end stack (HTML/JQuery, C# WPF, React) via ajax so I've never really dealt with anything other than providing JSONs or binary streams of data. Now making redirects I obviously have to use something other than the RestController, otherwise I will not be able to manipulate anything other than the body. The first thing I found in Webflux is RouterFunction, in which I tried to write a simple endpoint:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.BodyInserters.fromValue;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class RouterConfiguration {

    @Bean
    RouterFunction<ServerResponse> helloRouter() {
        return route(GET("/hello"), request -> ServerResponse.ok().body(fromValue("Hello World")));
    }

}

To my surprise, the endpoint doesn't work. Some digging into StackOverflow and my own code reveal that if you have spring-boot-starter-web as a dependency, RouterFunctions will be ignored. So RouterFunctions are part of the spring-boot-starter-webflux stack and they don't mix well with the traditional @Controllers. This sadly is not my choice since my work project imports some common module which itself mandates a spring-boot-starter-web dependency.

So what I have to do is actually just to wrap pass a redirect: String out and wrap it in a Mono.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import reactor.core.publisher.Mono;

@Controller
public class TestController {

    @GetMapping("hi")
    @ResponseBody
    public Mono<String> hi() {
        return Mono.just("hi");
    }

    @GetMapping("hiRedirect")
    public Mono<String> hiRedirect() {
        return Mono.just("redirect:/hi");
    }

}

Now the view concept is pretty alien to me, as I said I usually work with REST Controllers. Just to list a few other favours getting the same result as I dug more into Model View coding, apparently giving more control on the headers and response codes:

    @GetMapping("hiRedirectMVC")
    public Mono<ModelAndView> hiRedirectMVC(ModelMap modelMap) {
        return Mono.just(new ModelAndView("redirect:/hi", modelMap));
    }

    @GetMapping("hiRedirectRespEntity")
    public Mono<ResponseEntity<String>> hiRedirectRespEntity() {
        return Mono.just(new HttpHeaders())
                .doOnNext(header -> header.add("Location", "/hi"))
                .map(header -> new ResponseEntity<>(null, header, HttpStatus.MOVED_PERMANENTLY));
    }

Here ends my first post. Till next time, happy coding!

Comments (0)