1

Background: I have traditional Spring MVC application (Spring Boot) that utilizes Spring Cloud OpenFeign (REST service consumption) and Apache CXF (SOAP service consumption). I'm trying to see if I can consolidate these two HTTP clients to just Spring's WebClient.


I'm trying to figure out if there is a better way to reference attributes placed in RequestContextHolder and then consume/use them in an ExchangeFilterFunction (replaces various Feign/CXF request interceptors).

For example, the following works:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;
@SpringBootApplication
public class DemoApplication {

    private static Log logger = LogFactory.getLog(DemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    RouterFunction<ServerResponse> router(WebClient.Builder webClientBuilder) {
        WebClient webClient = webClientBuilder.baseUrl("https://jsonplaceholder.typicode.com").filter((req, next) -> {
            Object foo = requireNonNull(RequestContextHolder.getRequestAttributes()).getAttribute("foo", WebRequest.SCOPE_REQUEST);
            logger.info("Foo=" + foo);
            return next.exchange(ClientRequest.from(req).header("foo", foo.toString()).build());
        }).build();

        return RouterFunctions.route()
                .before((request) -> {
                    logger.info("Setting foo from " + Thread.currentThread().getName());
                    RequestContextHolder.getRequestAttributes().setAttribute("foo", "bar", WebRequest.SCOPE_REQUEST);
                    return ServerRequest.from(request).build();
                })
                .GET("/test", (request) -> {
                    ClientResponse response = webClient.get().uri("/todos/1").exchange().block();

                    Map<String, Object> body = new HashMap<>();
                    body.put("attributes", RequestContextHolder.getRequestAttributes().getAttributeNames(WebRequest.SCOPE_REQUEST));
                    body.put("todo", response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}).block());

                    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(body);
                })
                .build();
    }
}

Please note the above is still a servlet/blocking application example. The only reactive part is WebClient and the ExchangeFilterFunction. RestTemplate is in maintenance mode and the recommendation is to use WebClient as per the javadoc

When I spam the endpoint with multiple requests, it seemingly works:

2020-08-24 21:26:22.829  INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-1
2020-08-24 21:26:22.829  INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:22.915  INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-2
2020-08-24 21:26:22.916  INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:23.011  INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-3
2020-08-24 21:26:23.011  INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:23.118  INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-4
2020-08-24 21:26:23.119  INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication         : Foo=bar

However this feels wrong to do since Reactor offers Context and even more so since this Reactor FAQ explicitly calls out the following (emphasis mine):

Now that we’ve established that MDC "just working" is not the best assumption to make in a declarative API

Any help/guidance is appreciated.

Cisco
  • 20,972
  • 5
  • 38
  • 60
  • I've addressed a similar question in one of my answers. It might help: https://stackoverflow.com/a/61720539/7652010 – Akhil Bojedla Aug 25 '20 at 09:56
  • you should not be using the `RequestContextHolder` at all since it is based on using `threadlocal`. Your little test is faulty because as you can see it is always the same thread that is setting the value that is later fetching the value. But if webflux needs to change threads the value will get lost or even worse, wrong thread will get the wrong data. also you should not use `block` in a reactive application at all, blocking is very bad in reactive applications. – Toerktumlare Aug 25 '20 at 09:57
  • @ThomasAndolf This isn't a reactive application, it is still a servlet/blocking one. The only "reactive" bits are `WebClient` since `RestTemplate` is in maintance mode and the recommendation is to use `WebClient` per the javadoc – Cisco Aug 25 '20 at 12:37

0 Answers0