Uploaded image for project: 'Configuration Persistence Service'
  1. Configuration Persistence Service
  2. CPS-989

Replace RestTemplate with WebClient

XMLWordPrintable

    • Icon: Story Story
    • Resolution: Unresolved
    • Icon: High High
    • None
    • None
    • NCMP

      Reasons
      #RestTemplate no longer under development
      #WebClient supports both Sync and Async operations

      Possible impacts

      1. Performance, need to test!
      2. replace Apache Client (can be configured to use if needed)
      3. Works with Jetty? Need to confirm

      Actions

      1. Refresh study using a Proper proposal page

      Further investigations:

      1. impact on error handling
      2. async data read/write bulk operations (possible another item)
      3. CpsNcmpTaskExecutor could be deprecated/removed in the future
      4. performance impact

      A/C

      1. Publish and agree analysis with development team
      2. Performance Report (using up to 12 concurrent client) before and after. Should have no significant performance degradation
      3. Ensure all works with too with Apache HTTP Client 5 on the classpath
      4. Expose configuration parameter (if default don't suffices)
      5. Demo two scenarios: first the bulk interface. And the single interface (with topic) 
      6. Ignore the failing integration tests that are failing because of the introduction of WebClient

      Configure WebClient bean :

      package org.onap.cps.ncmp.dmi.config;
      
      import io.netty.channel.ChannelOption;
      import io.netty.handler.timeout.ReadTimeoutHandler;
      import io.netty.handler.timeout.WriteTimeoutHandler;
      import lombok.AllArgsConstructor;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.http.HttpHeaders;
      import org.springframework.http.MediaType;
      import org.springframework.http.client.reactive.ReactorClientHttpConnector;
      import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
      import org.springframework.web.reactive.function.client.WebClient;
      import reactor.core.publisher.Mono;
      import reactor.netty.http.client.HttpClient;
      
      import java.util.List;
      import java.util.concurrent.TimeUnit;
      
      @Slf4j
      @Configuration
      @AllArgsConstructor
      public class WebClientConfiguration {
      
          public static final int TIMEOUT = 2000;
          private final DmiConfiguration.SdncProperties sdncProperties;
      
          @Bean
          public WebClient webClient() {
              final var httpClient = HttpClient.create()
                      .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, TIMEOUT) // millis
                      .doOnConnected(connection ->
                              connection
                                      .addHandlerLast(new ReadTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS)) // millis
                                      .addHandlerLast(new WriteTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS))); //millis
      
              return WebClient.builder()
                      .baseUrl(sdncProperties.getBaseUrl())
                      .defaultHeaders(header -> header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
                      .defaultHeaders(header -> header.setBasicAuth(sdncProperties.getAuthUsername(), sdncProperties.getAuthPassword()))
                      .clientConnector(new ReactorClientHttpConnector(httpClient))
                      .filter(logRequest())
                      .filter(logResponse())
                      .build();
          }
      
          private ExchangeFilterFunction logRequest() {
              return (clientRequest, next) -> {
                  log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
                  log.info("--- Http Headers: ---");
                  clientRequest.headers().forEach(this::logHeader);
                  log.info("--- Http Cookies: ---");
                  clientRequest.cookies().forEach(this::logHeader);
                  return next.exchange(clientRequest);
              };
          }
      
          private ExchangeFilterFunction logResponse() {
              return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
                  log.info("Response: {}", clientResponse.statusCode());
                  clientResponse.headers().asHttpHeaders()
                          .forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
                  return Mono.just(clientResponse);
              });
          }
      
          private void logHeader(String name, List values) {
              values.forEach(value -> log.info("{}={}", name, value));
          }
      }

      RestTemplate should be replaced as below :

      return restTemplate.exchange(sdncRestconfUrl, httpMethod, httpEntity, String.class);

      WebClient GET sync call :

      String response= webClient
       .get()
       .uri(resourceUrl)
       .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
       .retrieve()
       .onStatus(HttpStatus::is4xxClientError,
       error -> Mono.error(new RuntimeException("API not found")))
       .onStatus(HttpStatus::is5xxServerError,
       error -> Mono.error(new RuntimeException("Server is not responding")))
       .bodyToMono(String.class)
       .block();
      return ResponseEntity.ok(response);

      WebClient POST sync call :

      String response= webClient
       .post()
       .uri(resourceUrl)
       .body(Mono.just(jsonData), String.class)
       .retrieve()
       .onStatus(HttpStatus::is4xxClientError,
       error -> Mono.error(new RuntimeException("API not found")))
       .onStatus(HttpStatus::is5xxServerError,
       error -> Mono.error(new RuntimeException("Server is not responding")))
       .bodyToMono(String.class)
       .block();
      return ResponseEntity.ok(response);

      WebClient GET async call :

      1. Define async call :
      
      public Mono<String> httpOperationWithJsonData(final HttpMethod httpMethod, final String resourceUrl,                                                        final String jsonData,
      final HttpHeaders httpHeaders) {
      
      return webClient
       .get()
       .uri(resourceUrl)
       .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
       .retrieve()
       .onStatus(HttpStatus::is4xxClientError,
       error -> Mono.error(new RuntimeException("API not found")))
       .onStatus(HttpStatus::is5xxServerError,
       error -> Mono.error(new RuntimeException("Server is not responding")))
       .bodyToMono(String.class);
      }
      
      2. Subscribe async call :
      
      httpOperationWithJsonDataAsync(HttpMethod.GET, getResourceUrl, null, httpHeaders)
                      .subscribe(response -> {
                          log.info("Get async response : {}", response);
                          responseEntity.getBody().concat(response);
                      });

            sourabh_sourabh Sourabh Sourabh
            sourabh_sourabh Sourabh Sourabh
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: