Release DMaaP client API

* remove @ExperimentalApi annotation
* deprecate old API
* extract http-client module + refactor
* change DmaapClientFactory so it's more configurable

Change-Id: I710d20558eece8cc3d7c0740e765d34737134b3a
Issue-ID: DCAEGEN2-1492
Signed-off-by: Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
diff --git a/rest-services/http-client/pom.xml b/rest-services/http-client/pom.xml
new file mode 100644
index 0000000..1d32105
--- /dev/null
+++ b/rest-services/http-client/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ ============LICENSE_START====================================
+  ~ DCAEGEN2-SERVICES-SDK
+  ~ =========================================================
+  ~ Copyright (C) 2019 Nokia. All rights reserved.
+  ~ =========================================================
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~       http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~ ============LICENSE_END=====================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.dcaegen2.services.sdk</groupId>
+        <artifactId>dcaegen2-services-sdk-rest-services</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.onap.dcaegen2.services.sdk.rest.services</groupId>
+    <artifactId>http-client</artifactId>
+
+    <name>dcaegen2-services-sdk-rest-services-http-client</name>
+    <description>HTTP adapter</description>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.dcaegen2.services.sdk.security</groupId>
+            <artifactId>ssl</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.dcaegen2.services.sdk.rest.services</groupId>
+            <artifactId>common-dependency</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.vavr</groupId>
+            <artifactId>vavr</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClient.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClient.java
new file mode 100644
index 0000000..77f3811
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClient.java
@@ -0,0 +1,141 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import com.google.gson.Gson;
+import io.netty.handler.ssl.SslContext;
+import io.vavr.collection.HashMap;
+import java.util.Collections;
+import java.util.Map;
+import org.onap.dcaegen2.services.sdk.rest.services.model.ClientModel;
+import org.onap.dcaegen2.services.sdk.rest.services.model.JsonBodyBuilder;
+import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author <a href="mailto:przemyslaw.wasala@nokia.com">Przemysław Wąsala</a> on 11/15/18
+ * @deprecated use {@link RxHttpClient} instead
+ */
+@Deprecated
+public class CloudHttpClient {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CloudHttpClient.class);
+    private final Gson gson = new Gson();
+    private final RxHttpClient httpClient;
+
+    CloudHttpClient(RxHttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    public CloudHttpClient() {
+        this(RxHttpClientFactory.create());
+    }
+
+    public CloudHttpClient(SslContext sslContext) {
+        this(RxHttpClientFactory.create(sslContext));
+    }
+
+    public <T> Mono<T> get(String url, Class<T> bodyClass) {
+        return get(url, RequestDiagnosticContext.create(), bodyClass);
+    }
+
+    public <T> Mono<T> get(String url, RequestDiagnosticContext context, Class<T> bodyClass) {
+        return get(url, context, Collections.emptyMap(), bodyClass);
+    }
+
+    public Mono<HttpResponse> get(
+            String url,
+            RequestDiagnosticContext context,
+            Map<String, String> customHeaders) {
+        return httpClient.call(
+                ImmutableHttpRequest.builder()
+                        .method(HttpMethod.GET)
+                        .url(url)
+                        .customHeaders(HashMap.ofAll(customHeaders))
+                        .diagnosticContext(context)
+                        .build());
+    }
+
+    public <T> Mono<T> get(
+            String url,
+            RequestDiagnosticContext context,
+            Map<String, String> customHeaders,
+            Class<T> bodyClass) {
+        return get(url, context, customHeaders)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString)
+                .map(body -> gson.fromJson(body, bodyClass));
+    }
+
+
+    public Mono<HttpResponse> post(
+        String url,
+        RequestDiagnosticContext context,
+        Map<String, String> customHeaders,
+        JsonBodyBuilder jsonBodyBuilder,
+        ClientModel clientModel) {
+        return callForRawResponse(url, context, customHeaders, jsonBodyBuilder, clientModel, HttpMethod.POST);
+    }
+
+    public Mono<HttpResponse> patch(
+        String url,
+        RequestDiagnosticContext context,
+        Map<String, String> customHeaders,
+        JsonBodyBuilder jsonBodyBuilder,
+        ClientModel clientModel) {
+        return callForRawResponse(url, context, customHeaders, jsonBodyBuilder, clientModel, HttpMethod.PATCH);
+    }
+
+    public Mono<HttpResponse> put(
+        String url,
+        RequestDiagnosticContext context,
+        Map<String, String> customHeaders,
+        JsonBodyBuilder jsonBodyBuilder,
+        ClientModel clientModel) {
+        return callForRawResponse(url, context, customHeaders, jsonBodyBuilder, clientModel, HttpMethod.PUT);
+    }
+
+    private Mono<HttpResponse> callForRawResponse(
+        String url,
+        RequestDiagnosticContext context,
+        Map<String, String> customHeaders,
+        JsonBodyBuilder jsonBodyBuilder,
+        ClientModel clientModel,
+        HttpMethod method) {
+
+        String jsonBody = jsonBodyBuilder.createJsonBody(clientModel);
+        LOGGER.debug("CloudHttpClient JSon body:: {}", jsonBody);
+        LOGGER.debug("CloudHttpClient url: {}", url);
+        LOGGER.debug("CloudHttpClient customHeaders: {}", customHeaders);
+
+        return httpClient.call(
+            ImmutableHttpRequest.builder()
+                .url(url)
+                .customHeaders(HashMap.ofAll(customHeaders))
+                .diagnosticContext(context)
+                .body(RequestBody.fromString(jsonBody))
+                .method(method)
+                .build());
+    }
+}
+
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpHeaders.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpHeaders.java
new file mode 100644
index 0000000..4ef43a5
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpHeaders.java
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since April 2019
+ */
+public final class HttpHeaders {
+
+    private HttpHeaders() {
+    }
+
+    public static final String CONTENT_TYPE = "Content-Type";
+    public static final String CONTENT_LENGTH = "Content-Length";
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpMethod.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpMethod.java
new file mode 100644
index 0000000..78e6789
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpMethod.java
@@ -0,0 +1,48 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+public enum HttpMethod {
+
+    CONNECT(io.netty.handler.codec.http.HttpMethod.CONNECT),
+    DELETE(io.netty.handler.codec.http.HttpMethod.DELETE),
+    GET(io.netty.handler.codec.http.HttpMethod.GET),
+    HEAD(io.netty.handler.codec.http.HttpMethod.HEAD),
+    OPTIONS(io.netty.handler.codec.http.HttpMethod.OPTIONS),
+    POST(io.netty.handler.codec.http.HttpMethod.POST),
+    PATCH(io.netty.handler.codec.http.HttpMethod.PATCH),
+    PUT(io.netty.handler.codec.http.HttpMethod.PUT),
+    TRACE(io.netty.handler.codec.http.HttpMethod.TRACE);
+
+    private final io.netty.handler.codec.http.HttpMethod nettyMethod;
+
+    HttpMethod(io.netty.handler.codec.http.HttpMethod nettyMethod) {
+        this.nettyMethod = nettyMethod;
+    }
+
+    io.netty.handler.codec.http.HttpMethod asNetty() {
+        return nettyMethod;
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpRequest.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpRequest.java
new file mode 100644
index 0000000..33060c9
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpRequest.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import io.vavr.collection.HashMap;
+import io.vavr.collection.Map;
+import org.immutables.value.Value;
+import org.jetbrains.annotations.Nullable;
+import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+@Value.Immutable
+public interface HttpRequest {
+
+    String url();
+
+    HttpMethod method();
+
+    @Nullable RequestBody body();
+
+    @Value.Default
+    default RequestDiagnosticContext diagnosticContext() {
+        return RequestDiagnosticContext.create();
+    }
+
+    @Value.Default
+    default Map<String, String> customHeaders() {
+        return HashMap.empty();
+    }
+
+    @Value.Derived
+    default Map<String, String> headers() {
+        final RequestDiagnosticContext ctx = diagnosticContext();
+        return ctx == null
+                ? customHeaders()
+                : customHeaders().merge(ctx.remoteCallHttpHeaders());
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpResponse.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpResponse.java
new file mode 100644
index 0000000..ce10047
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/HttpResponse.java
@@ -0,0 +1,77 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import com.google.gson.Gson;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import org.immutables.value.Value;
+import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.exceptions.HttpException;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+@Value.Immutable
+public interface HttpResponse {
+
+    String url();
+
+    int statusCode();
+
+    byte[] rawBody();
+
+    @Value.Default
+    default String statusReason() {
+        return "";
+    }
+
+    @Value.Derived
+    default boolean successful() {
+        return statusCode() >= 200 && statusCode() < 300;
+    }
+
+    @Value.Derived
+    default String bodyAsString() {
+        return bodyAsString(StandardCharsets.UTF_8);
+    }
+
+    @Value.Derived
+    default String bodyAsString(Charset charset) {
+        return new String(rawBody(), charset);
+    }
+
+    @Value.Derived
+    default <T> T bodyAsJson(Class<T> clazz) {
+        return bodyAsJson(StandardCharsets.UTF_8, new Gson(), clazz);
+    }
+
+    @Value.Derived
+    default <T> T bodyAsJson(Charset charset, Gson gson, Class<T> clazz) {
+        return gson.fromJson(bodyAsString(charset), clazz);
+    }
+
+    default void throwIfUnsuccessful() {
+        if (!successful()) {
+            throw new HttpException(url(), statusCode(), statusReason());
+        }
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/NettyHttpResponse.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/NettyHttpResponse.java
new file mode 100644
index 0000000..3dcd709
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/NettyHttpResponse.java
@@ -0,0 +1,73 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpStatusClass;
+import java.nio.charset.Charset;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+class NettyHttpResponse implements HttpResponse {
+
+    private final String url;
+    private final HttpResponseStatus status;
+    private final byte[] body;
+
+    NettyHttpResponse(String url, HttpResponseStatus status, byte[] body) {
+        this.url = url;
+        this.status = status;
+        this.body = body;
+    }
+
+    @Override
+    public String url() {
+        return url;
+    }
+
+    @Override
+    public boolean successful() {
+        return status.codeClass() == HttpStatusClass.SUCCESS;
+    }
+
+    @Override
+    public int statusCode() {
+        return status.code();
+    }
+
+    @Override
+    public String statusReason() {
+        return status.reasonPhrase();
+    }
+
+    @Override
+    public byte[] rawBody() {
+        return new byte[0];
+    }
+
+    @Override
+    public String bodyAsString(Charset charset) {
+        return new String(body, charset);
+    }
+
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RequestBody.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RequestBody.java
new file mode 100644
index 0000000..d427ee5
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RequestBody.java
@@ -0,0 +1,74 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import com.google.gson.JsonElement;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import org.immutables.value.Value;
+import org.jetbrains.annotations.Nullable;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Mono;
+import reactor.netty.ByteBufFlux;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+@Value.Immutable
+public interface RequestBody {
+
+    Publisher<ByteBuf> contents();
+
+    @Nullable Integer length();
+
+    static RequestBody chunkedFromString(Publisher<String> contents) {
+        return chunkedFromString(contents, StandardCharsets.UTF_8);
+    }
+
+    static RequestBody chunkedFromString(Publisher<String> contents, Charset charset) {
+        return ImmutableRequestBody.builder()
+                .length(null)
+                .contents(ByteBufFlux.fromString(contents, charset, ByteBufAllocator.DEFAULT))
+                .build();
+    }
+
+    static RequestBody fromString(String contents) {
+        return fromString(contents, StandardCharsets.UTF_8);
+    }
+
+    static RequestBody fromString(String contents, Charset charset) {
+        ByteBuf encodedContents = ByteBufAllocator.DEFAULT.buffer();
+        encodedContents.writeCharSequence(contents, charset);
+
+        return ImmutableRequestBody.builder()
+                .length(encodedContents.readableBytes())
+                .contents(Mono.just(encodedContents.retain()))
+                .build();
+    }
+
+    static RequestBody fromJson(JsonElement contents) {
+        return fromString(contents.toString());
+    }
+
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClient.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClient.java
new file mode 100644
index 0000000..234a380
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClient.java
@@ -0,0 +1,111 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import io.vavr.collection.Stream;
+import java.util.stream.Collectors;
+import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.http.client.HttpClient.ResponseReceiver;
+import reactor.netty.http.client.HttpClientRequest;
+import reactor.netty.http.client.HttpClientResponse;
+
+/**
+ * @since 1.1.4
+ */
+public class RxHttpClient {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RxHttpClient.class);
+    private final HttpClient httpClient;
+
+    RxHttpClient(HttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    public Mono<HttpResponse> call(HttpRequest request) {
+        return prepareRequest(request)
+                .responseSingle((resp, content) ->
+                        content.asByteArray()
+                                .defaultIfEmpty(new byte[0])
+                                .map(bytes -> new NettyHttpResponse(request.url(), resp.status(), bytes)));
+    }
+
+    ResponseReceiver<?> prepareRequest(HttpRequest request) {
+        final HttpClient theClient = httpClient
+                .doOnRequest((req, conn) -> logRequest(request.diagnosticContext(), req))
+                .doOnResponse((rsp, conn) -> logResponse(request.diagnosticContext(), rsp))
+                .headers(hdrs -> request.headers().forEach(hdr -> hdrs.set(hdr._1, hdr._2)));
+
+        return prepareBody(request, theClient);
+    }
+
+    private ResponseReceiver<?> prepareBody(HttpRequest request, HttpClient theClient) {
+        if (request.body() == null) {
+            return prepareBodyWithoutContents(request, theClient);
+        } else {
+            return request.body().length() == null
+                    ? prepareBodyChunked(request, theClient)
+                    : prepareBodyUnchunked(request, theClient);
+        }
+    }
+
+    private ResponseReceiver<?> prepareBodyChunked(HttpRequest request, HttpClient theClient) {
+        return theClient
+                .chunkedTransfer(true)
+                .request(request.method().asNetty())
+                .send(request.body().contents())
+                .uri(request.url());
+    }
+
+    private ResponseReceiver<?> prepareBodyUnchunked(HttpRequest request, HttpClient theClient) {
+        return theClient
+                .headers(hdrs -> hdrs.set(HttpHeaders.CONTENT_LENGTH, request.body().length().toString()))
+                .request(request.method().asNetty())
+                .send(request.body().contents())
+                .uri(request.url());
+    }
+
+    private ResponseReceiver<?> prepareBodyWithoutContents(HttpRequest request, HttpClient theClient) {
+        return theClient
+                .request(request.method().asNetty())
+                .uri(request.url());
+    }
+
+    private void logRequest(RequestDiagnosticContext context, HttpClientRequest httpClientRequest) {
+        context.withSlf4jMdc(LOGGER.isDebugEnabled(), () -> {
+            LOGGER.debug("Request: {} {} {}", httpClientRequest.method(), httpClientRequest.uri(),
+                    httpClientRequest.requestHeaders());
+            if (LOGGER.isTraceEnabled()) {
+                final String headers = Stream.ofAll(httpClientRequest.requestHeaders())
+                        .map(entry -> entry.getKey() + "=" + entry.getValue())
+                        .collect(Collectors.joining("\n"));
+                LOGGER.trace(headers);
+            }
+        });
+    }
+
+    private void logResponse(RequestDiagnosticContext context, HttpClientResponse httpClientResponse) {
+        context.withSlf4jMdc(LOGGER.isDebugEnabled(),
+                () -> LOGGER.debug("Response status: {}", httpClientResponse.status()));
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientFactory.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientFactory.java
new file mode 100644
index 0000000..cfa98f2
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientFactory.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import io.netty.handler.ssl.SslContext;
+import org.jetbrains.annotations.NotNull;
+import org.onap.dcaegen2.services.sdk.security.ssl.SecurityKeys;
+import org.onap.dcaegen2.services.sdk.security.ssl.SslFactory;
+import reactor.netty.http.client.HttpClient;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since May 2019
+ */
+public final class RxHttpClientFactory {
+
+    private static final SslFactory SSL_FACTORY = new SslFactory();
+
+    private RxHttpClientFactory() {
+    }
+
+    public static RxHttpClient create() {
+        return new RxHttpClient(HttpClient.create());
+    }
+
+
+    public static RxHttpClient create(SecurityKeys securityKeys) {
+        final SslContext context = SSL_FACTORY.createSecureClientContext(securityKeys);
+        return create(context);
+    }
+
+    public static RxHttpClient createInsecure() {
+        final SslContext context = SSL_FACTORY.createInsecureClientContext();
+        return create(context);
+    }
+
+    // TODO: make it private after removing CloudHttpClient
+    static RxHttpClient create(@NotNull SslContext sslContext) {
+        return new RxHttpClient(HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)));
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/exceptions/HttpException.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/exceptions/HttpException.java
new file mode 100644
index 0000000..9631f4c
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/exceptions/HttpException.java
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http.exceptions;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since March 2019
+ */
+public class HttpException extends RuntimeException {
+
+    private final String url;
+    private final int responseCode;
+    private final String reason;
+
+    public HttpException(String url, int responseCode, String reason) {
+        this.url = url;
+        this.responseCode = responseCode;
+        this.reason = reason;
+    }
+
+    @Override
+    public String getMessage() {
+        return String.format("Request failed for URL '%s'. Response code: %d %s",
+                url,
+                responseCode,
+                reason);
+    }
+}
diff --git a/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/test/DummyHttpServer.java b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/test/DummyHttpServer.java
new file mode 100644
index 0000000..2a1ba7a
--- /dev/null
+++ b/rest-services/http-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/test/DummyHttpServer.java
@@ -0,0 +1,99 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http.test;
+
+import io.vavr.CheckedFunction0;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import org.reactivestreams.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+import reactor.netty.DisposableServer;
+import reactor.netty.http.server.HttpServer;
+import reactor.netty.http.server.HttpServerResponse;
+import reactor.netty.http.server.HttpServerRoutes;
+
+/**
+ * @author <a href="mailto:piotr.jaszczyk@nokia.com">Piotr Jaszczyk</a>
+ * @since February 2019
+ */
+public class DummyHttpServer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DummyHttpServer.class);
+    private final DisposableServer server;
+
+    private DummyHttpServer(DisposableServer server) {
+        this.server = server;
+    }
+
+    public static DummyHttpServer start(Consumer<HttpServerRoutes> routes) {
+        LOGGER.info("Starting dummy server");
+        final DisposableServer server = HttpServer.create()
+                .host("127.0.0.1")
+                .route(routes)
+                .bind()
+                .block();
+        LOGGER.info("Server started");
+        return new DummyHttpServer(server);
+    }
+
+    public static Publisher<Void> sendInOrder(AtomicInteger state, Publisher<Void>... responses) {
+        return responses[state.getAndIncrement()];
+    }
+
+    public static Publisher<Void> sendResource(HttpServerResponse httpServerResponse, String resourcePath) {
+        return sendString(httpServerResponse, Mono.fromCallable(() -> readResource(resourcePath)));
+    }
+
+    public static Publisher<Void> sendString(HttpServerResponse httpServerResponse, Publisher<String> content) {
+        return httpServerResponse.sendString(content);
+    }
+
+    public void close() {
+        server.disposeNow();
+    }
+
+    public String host() {
+        return server.host();
+    }
+
+    public int port() {
+        return server.port();
+    }
+
+    private static String readResource(String resourcePath) {
+        try {
+            return CheckedFunction0.constant(resourcePath)
+                    .andThen(DummyHttpServer.class::getResource)
+                    .andThen(URL::toURI)
+                    .andThen(Paths::get)
+                    .andThen(Files::readAllBytes)
+                    .andThen(String::new)
+                    .apply();
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+}
diff --git a/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClientIT.java b/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClientIT.java
new file mode 100644
index 0000000..f4efe1a
--- /dev/null
+++ b/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/CloudHttpClientIT.java
@@ -0,0 +1,220 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.dcaegen2.services.sdk.rest.services.model.DmaapModel;
+import org.onap.dcaegen2.services.sdk.rest.services.model.JsonBodyBuilder;
+import org.onap.dcaegen2.services.sdk.rest.services.model.logging.ImmutableRequestDiagnosticContext;
+import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
+import reactor.core.publisher.Mono;
+import reactor.netty.DisposableServer;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.http.server.HttpServer;
+import reactor.netty.resources.ConnectionProvider;
+import reactor.test.StepVerifier;
+
+class CloudHttpClientIT {
+
+    private static final int MAX_CONNECTIONS = 1;
+    private static final String SAMPLE_STRING = "sampleString";
+    private static final String SAMPLE_URL = "/sampleURL";
+    private static final String JSON_BODY = "{\"correlationId\":\"NOKnhfsadhff\","
+            + "\"ipaddress-v4\":\"256.22.33.155\", "
+            + "\"ipaddress-v6\":\"200J:0db8:85a3:0000:0000:8a2e:0370:7334\"}";
+    private static final ConnectionProvider connectionProvider = ConnectionProvider.fixed("test", MAX_CONNECTIONS);
+
+    private final DmaapModel dmaapModel = mock(DmaapModel.class);
+    private final JsonBodyBuilder<DmaapModel> jsonBodyBuilder = mock(JsonBodyBuilder.class);
+
+    @Test
+    void successfulPatchResponse() {
+        DisposableServer server = createValidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+                .patch(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                        jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.OK.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    @Test
+    void errorPatchRequest() {
+        DisposableServer server = createInvalidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+                .patch(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                        jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    @Test
+    void successfulPostResponse() {
+        DisposableServer server = createValidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+                .post(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                        jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.OK.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    @Test
+    void errorPostRequest() {
+        DisposableServer server = createInvalidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+                .post(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                        jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    @Test
+    void successfulGetResponse() {
+        DisposableServer server = createValidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<String> content = cloudHttpClient.get(SAMPLE_URL, String.class);
+        Mono<String> contentWithHeaders = cloudHttpClient.get(SAMPLE_URL, createRequestDiagnosticContext(),
+                createCustomHeaders(), String.class);
+
+        StepVerifier.create(content)
+                .expectNext(SAMPLE_STRING)
+                .expectComplete()
+                .verify();
+        StepVerifier.create(contentWithHeaders)
+                .expectNext(SAMPLE_STRING)
+                .expectComplete()
+                .verify();
+        server.disposeNow();
+    }
+
+    @Test
+    void errorGetRequest() {
+        DisposableServer server = createInvalidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        Mono<String> content = cloudHttpClient.get(SAMPLE_URL, String.class);
+
+        StepVerifier.create(content)
+                .expectError()
+                .verify();
+        server.disposeNow();
+    }
+
+    @Test
+    void successfulPutResponse() {
+        DisposableServer server = createValidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+            .put(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.OK.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    @Test
+    void errorPutRequest() {
+        DisposableServer server = createInvalidServer();
+        RxHttpClient httpClient = createHttpClientForContextWithAddress(server);
+        CloudHttpClient cloudHttpClient = new CloudHttpClient(httpClient);
+
+        when(jsonBodyBuilder.createJsonBody(dmaapModel)).thenReturn(JSON_BODY);
+        Mono<HttpResponse> content = cloudHttpClient
+            .put(SAMPLE_URL, createRequestDiagnosticContext(), createCustomHeaders(),
+                jsonBodyBuilder, dmaapModel);
+        HttpResponse httpClientResponse = content.block();
+
+        assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), httpClientResponse.statusCode());
+        server.disposeNow();
+    }
+
+    private Map<String, String> createCustomHeaders() {
+        Map<String, String> customHeaders = new HashMap<>();
+        customHeaders.put("X_INVOCATION_ID", UUID.randomUUID().toString());
+        return customHeaders;
+    }
+
+    private DisposableServer createValidServer() {
+        Mono<String> response = Mono.just(SAMPLE_STRING);
+        return HttpServer.create()
+                .handle((req, resp) -> resp.sendString(response))
+                .wiretap(true)
+                .bindNow();
+    }
+
+    private DisposableServer createInvalidServer() {
+        return HttpServer.create()
+                .handle((req, resp) -> Mono.error(new Exception("returnError")))
+                .wiretap(true)
+                .bindNow();
+    }
+
+    private RequestDiagnosticContext createRequestDiagnosticContext() {
+        return ImmutableRequestDiagnosticContext.builder()
+                .invocationId(UUID.randomUUID()).requestId(UUID.randomUUID()).build();
+    }
+
+    private RxHttpClient createHttpClientForContextWithAddress(DisposableServer disposableServer) {
+        HttpClient client = HttpClient.create(connectionProvider)
+                .addressSupplier(disposableServer::address)
+                .wiretap(true);
+        return new RxHttpClient(client);
+    }
+}
\ No newline at end of file
diff --git a/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientIT.java b/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientIT.java
new file mode 100644
index 0000000..cdddaef
--- /dev/null
+++ b/rest-services/http-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/adapters/http/RxHttpClientIT.java
@@ -0,0 +1,161 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.adapters.http;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.onap.dcaegen2.services.sdk.rest.services.adapters.http.test.DummyHttpServer.sendString;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.time.Duration;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.exceptions.HttpException;
+import org.onap.dcaegen2.services.sdk.rest.services.adapters.http.test.DummyHttpServer;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+class RxHttpClientIT {
+
+    private static final Duration TIMEOUT = Duration.ofHours(5);
+    private final RxHttpClient cut = RxHttpClientFactory.create();
+    private static DummyHttpServer httpServer;
+
+    @BeforeAll
+    static void setUpClass() {
+        httpServer = DummyHttpServer.start(routes ->
+                routes.get("/sample-get", (req, resp) -> sendString(resp, Mono.just("OK")))
+                        .get("/sample-get-500", (req, resp) -> resp.status(HttpResponseStatus.INTERNAL_SERVER_ERROR).send())
+                        .post("/headers-post", (req, resp) -> resp
+                                .sendString(Mono.just(req.requestHeaders().toString())))
+                        .post("/echo-post", (req, resp) -> resp.send(req.receive().retain()))
+        );
+    }
+
+    @AfterAll
+    static void tearDownClass() {
+        httpServer.close();
+    }
+
+    private ImmutableHttpRequest.Builder requestFor(String path) throws MalformedURLException {
+        return ImmutableHttpRequest.builder()
+                .url(new URL("http", httpServer.host(), httpServer.port(), path).toString());
+    }
+
+    @Test
+    void simpleGet() throws Exception {
+        // given
+        final HttpRequest httpRequest = requestFor("/sample-get").method(HttpMethod.GET).build();
+
+        // when
+        final Mono<String> bodyAsString = cut.call(httpRequest)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString);
+
+        // then
+        StepVerifier.create(bodyAsString).expectNext("OK").expectComplete().verify(TIMEOUT);
+    }
+
+    @Test
+    void getWithError() throws Exception {
+        // given
+        final HttpRequest httpRequest = requestFor("/sample-get-500").method(HttpMethod.GET).build();
+
+        // when
+        final Mono<String> bodyAsString = cut.call(httpRequest)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString);
+
+        // then
+        StepVerifier.create(bodyAsString).expectError(HttpException.class).verify(TIMEOUT);
+    }
+
+    @Test
+    void simplePost() throws Exception {
+        // given
+        final String requestBody = "hello world";
+        final HttpRequest httpRequest = requestFor("/echo-post")
+                .method(HttpMethod.POST)
+                .body(RequestBody.fromString(requestBody))
+                .build();
+
+        // when
+        final Mono<String> bodyAsString = cut.call(httpRequest)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString);
+
+        // then
+        StepVerifier.create(bodyAsString)
+                .expectNext(requestBody)
+                .expectComplete()
+                .verify(TIMEOUT);
+    }
+
+    @Test
+    void testChunkedEncoding() throws Exception {
+        // given
+        final String requestBody = "hello world";
+        final HttpRequest httpRequest = requestFor("/headers-post")
+                .method(HttpMethod.POST)
+                .body(RequestBody.chunkedFromString(Mono.just(requestBody)))
+                .build();
+
+        // when
+        final Mono<String> bodyAsString = cut.call(httpRequest)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString);
+
+        // then
+        StepVerifier.create(bodyAsString.map(String::toLowerCase))
+                .consumeNextWith(responseBody -> {
+                    assertThat(responseBody).contains("transfer-encoding: chunked");
+                    assertThat(responseBody).doesNotContain("content-length");
+                })
+                .expectComplete()
+                .verify(TIMEOUT);
+    }
+
+    @Test
+    void testUnchunkedEncoding() throws Exception {
+        // given
+        final String requestBody = "hello world";
+        final HttpRequest httpRequest = requestFor("/headers-post")
+                .method(HttpMethod.POST)
+                .body(RequestBody.fromString(requestBody))
+                .build();
+
+        // when
+        final Mono<String> bodyAsString = cut.call(httpRequest)
+                .doOnNext(HttpResponse::throwIfUnsuccessful)
+                .map(HttpResponse::bodyAsString);
+
+        // then
+        StepVerifier.create(bodyAsString.map(String::toLowerCase))
+                .consumeNextWith(responseBody -> {
+                    assertThat(responseBody).doesNotContain("transfer-encoding");
+                    assertThat(responseBody).contains("content-length");
+                })
+                .expectComplete()
+                .verify(TIMEOUT);
+    }
+}
\ No newline at end of file