springspring-bootspring-cloudresttemplate

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3


I use Spring Gateway 2021.0.8. I want to make this Post request using RestTemplate:

    import org.springframework.web.client.RestTemplate;
    
    public ResponseEntity<Response> getRemotePayload() {

       HttpHeaders headers = new HttpHeaders();
       headers.add("AUTHORIZATION", "test");

       MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
       body.add("type", "test");

       HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);

       return getRestTemplate()
        .exchange("http://127.0.0.1:8080/users", HttpMethod.POST, request, Response.class);
  }

I get error during execution:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.29.jar:3.4.29]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ org.springframework.web.filter.reactive.ServerWebExchangeContextFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]

Do you know how I can solve this issue?

I tried this:

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }

}  


  @Autowired
  public WebClient webClient;

  public Mono<Response> getResponse(......) {

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.add(HttpHeaders.AUTHORIZATION, authHeader);

    MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
    requestBody.add(.....);

    Mono<Response> responseMono = webClient.method(HttpMethod.POST)
            .uri("some usrl")
            .headers(httpHeaders -> httpHeaders.addAll(headers))
            .body(BodyInserters.fromValue(requestBody))
            .retrieve()
            .bodyToMono(Response.class);

    Mono<Response> customObjMono = responseMono.map(customObject -> {
      Response customObj = new Response();
      customObj.setAccessToken(customObject.getAccessToken());
      return customObj;
    });

    return customObjMono;
  }


Mono<Response> tokden = client.getResponse(........);
      AtomicReference<Response> plainJavaObject = new AtomicReference<>();
      tokden.subscribe(transformedToken -> {
        plainJavaObject.set(transformedToken);
      });
      Response token = plainJavaObject.get();

The code works but I need implementation with Feign to use eureka client resolution. I tried this:

@FeignClient(name = "http://remote/...")
public interface ClientFeign {

    @PostMapping(value = "/....", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    Mono<Response> getJwt(@RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader, .....);
}


@Component
public class YourService {

    private ClientFeign clientFeign;

    @Autowired
    public YourService(ClientFeign clientFeign) {
        this.clientFeign = clientFeign;
    }

    public Mono<Response> getJwt(........) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add(.......);

        return clientFeign.getJwt(.......)
                .map(customObject -> {
                    Response customObj = new Response();
                    customObj.setAccessToken(customObject.getAccessToken());
                    return customObj;
                });
    }
}



AtomicReference<Response> plainJavaObject = new AtomicReference<>();
      Mono<Response> tokenMono = yourService.getJwt(.........);
      tokenMono.subscribe(transformedToken -> {
        plainJavaObject.set(transformedToken);
      });
      Response token = plainJavaObject.get();

I get error for this line return clientFeign.getJwt(.......):

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.29.jar:3.4.29]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ org.springframework.web.filter.reactive.ServerWebExchangeContextFilter [DefaultWebFilterChain]
    *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]

Do you know how I can fix this issue?

EDIT: code to reproduce the issue: https://github.com/rcbandit111/gateway_reactive_feign_poc/tree/master/gateway


Solution

  • well , below is general usage of webclient

    Configure WebClient

    configure your webclient host and port , if you are using reactor-netty ,wired

    @Configuration
    @Slf4j
    public class ClientApiBootstrap {
    
        @Bean("server")
    
        public HttpClient httpClient(){
            return HttpClient.create()
                    .host("localhost") // your server host
                    .port(8080);  // your server port
        }
    
    
    
        @Bean("webClient")
        @ConditionalOnBean(HttpClient.class)
        public WebClient webclient(@Autowired HttpClient client){
            return WebClient
                    .builder()
                    .clientConnector(new ReactorClientHttpConnector(client))
                    .build();
        }
    
    
    }
    

    Defination webclient api

    
    @Component
    @Slf4j
    public class WebClinetApi {
    
        @Autowired
        public WebClient webClient;
    
        public Mono<YourResponse> auth () {
           return webClient
                    .post()
                    .uri("/auth")
                    .contentType(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .onStatus(HttpStatusCode::isError,
                        (clientResponse ) ->
                            clientResponse
                                .bodyToMono(ProblemDetail.class)
                                .flatMap(problemDetail ->
                                    Mono.error(()->
                                        new RuntimeException(problemDetail.getDetail()))))
                    .bodyToMono(YourResponse.class)
                    .doOnError((throwable)-> {
                        log.error( throwable.getMessage());
                    });
        }
    }
    

    Appendix

    Q : Looks like latest Spring Gateway uses reactive implementation so I need a reactive client also to make a external call?

    A: Actually , it is unnecessary that use reactive-programming client , it has extra api reference such as Mono, Flux , you d better that familiar with reactive-programing. and t is more complex with blocking server which based on tomcat . if you would not use it just modify your application code like following

    public static void main(String[] args) {
            SpringApplication application = new SpringApplication(APP.class);
            application.setWebApplicationType(WebApplicationType.SERVLET);
            application.run(args);
    
        }