2 * ========================LICENSE_START=================================
5 * Copyright (C) 2019 Nordix Foundation
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ========================LICENSE_END===================================
21 package org.oransc.portal.nonrtric.controlpanel.util;
23 import io.netty.channel.ChannelOption;
24 import io.netty.handler.ssl.SslContext;
25 import io.netty.handler.ssl.SslContextBuilder;
26 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
27 import io.netty.handler.timeout.ReadTimeoutHandler;
28 import io.netty.handler.timeout.WriteTimeoutHandler;
30 import java.lang.invoke.MethodHandles;
32 import javax.net.ssl.SSLException;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.springframework.http.MediaType;
37 import org.springframework.http.ResponseEntity;
38 import org.springframework.http.client.reactive.ReactorClientHttpConnector;
39 import org.springframework.web.reactive.function.client.WebClient;
40 import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
41 import org.springframework.web.reactive.function.client.WebClientResponseException;
43 import reactor.core.publisher.Mono;
44 import reactor.netty.http.client.HttpClient;
45 import reactor.netty.tcp.TcpClient;
48 * Generic reactive REST client.
50 public class AsyncRestClient {
51 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
52 private WebClient webClient = null;
53 private final String baseUrl;
55 public AsyncRestClient(String baseUrl) {
56 this.baseUrl = baseUrl;
59 public Mono<ResponseEntity<String>> putForEntity(String uri, String body) {
60 logger.debug("PUT uri = '{}{}''", baseUrl, uri);
61 return getWebClient() //
63 RequestHeadersSpec<?> request = client.put() //
65 .contentType(MediaType.APPLICATION_JSON) //
67 return retrieve(request);
71 public Mono<ResponseEntity<String>> putForEntity(String uri) {
72 logger.debug("PUT uri = '{}{}''", baseUrl, uri);
73 return getWebClient() //
75 RequestHeadersSpec<?> request = client.put() //
77 return retrieve(request);
81 public Mono<String> put(String uri, String body) {
82 return putForEntity(uri, body) //
83 .flatMap(this::toBody);
86 public Mono<ResponseEntity<String>> getForEntity(String uri) {
87 logger.debug("GET uri = '{}{}''", baseUrl, uri);
88 return getWebClient() //
90 RequestHeadersSpec<?> request = client.get().uri(uri);
91 return retrieve(request);
95 public Mono<String> get(String uri) {
96 return getForEntity(uri) //
97 .flatMap(this::toBody);
100 public Mono<ResponseEntity<String>> deleteForEntity(String uri) {
101 logger.debug("DELETE uri = '{}{}''", baseUrl, uri);
102 return getWebClient() //
104 RequestHeadersSpec<?> request = client.delete().uri(uri);
105 return retrieve(request);
109 public Mono<String> delete(String uri) {
110 return deleteForEntity(uri) //
111 .flatMap(this::toBody);
114 private Mono<ResponseEntity<String>> retrieve(RequestHeadersSpec<?> request) {
115 return request.retrieve() //
116 .toEntity(String.class) //
117 .doOnError(this::onHttpError);
120 private void onHttpError(Throwable t) {
121 if (t instanceof WebClientResponseException) {
122 WebClientResponseException exception = (WebClientResponseException) t;
123 logger.debug("HTTP error status = '{}', body '{}'", exception.getStatusCode(),
124 exception.getResponseBodyAsString());
126 logger.debug("HTTP error: {}", t.getMessage());
130 private Mono<String> toBody(ResponseEntity<String> entity) {
131 if (entity.getBody() == null) {
132 return Mono.just("");
134 return Mono.just(entity.getBody());
138 private static SslContext createSslContext() throws SSLException {
139 return SslContextBuilder.forClient() //
140 .trustManager(InsecureTrustManagerFactory.INSTANCE) //
144 private static WebClient createWebClient(String baseUrl, SslContext sslContext) {
145 TcpClient tcpClient = TcpClient.create() //
146 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
147 .secure(c -> c.sslContext(sslContext)) //
148 .doOnConnected(connection -> {
149 connection.addHandler(new ReadTimeoutHandler(10));
150 connection.addHandler(new WriteTimeoutHandler(30));
152 HttpClient httpClient = HttpClient.from(tcpClient);
153 ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
155 return WebClient.builder() //
156 .clientConnector(connector) //
161 private Mono<WebClient> getWebClient() {
162 if (this.webClient == null) {
164 SslContext sslContext = createSslContext();
165 this.webClient = createWebClient(this.baseUrl, sslContext);
166 } catch (SSLException e) {
167 logger.error("Could not create WebClient {}", e.getMessage());
168 return Mono.error(e);
171 return Mono.just(this.webClient);