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.