This is an automated email from the ASF dual-hosted git repository.

bbende pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 8ba7d7805a NIFI-13619 Switch web-client from OkHttp to Java HttpClient 
(#9140)
8ba7d7805a is described below

commit 8ba7d7805a039e03a23a3ae2d75c9544101cdf19
Author: David Handermann <[email protected]>
AuthorDate: Tue Aug 6 14:46:54 2024 -0500

    NIFI-13619 Switch web-client from OkHttp to Java HttpClient (#9140)
    
    * NIFI-13619 Switched web-client from OkHttp to Java HttpClient
    * NIFI-13619 Corrected comments to removed OkHttp references.
    * NIFI-13619 Added writeTimeout handling and close method
    * NIFI-13619 Added onDisabled to Service Provider
---
 nifi-commons/nifi-web-client/pom.xml               |  11 +-
 .../nifi/web/client/InputStreamRequestBody.java    |  56 -----
 .../nifi/web/client/StandardHttpUriBuilder.java    | 134 ++++++++++--
 .../nifi/web/client/StandardWebClientService.java  | 226 ++++++++++++++-------
 .../SSLContextProvider.java}                       |  33 +--
 ...ovider.java => StandardSSLContextProvider.java} |  20 +-
 .../ssl/StandardSSLSocketFactoryProvider.java      |  36 +---
 .../web/client/StandardHttpUriBuilderTest.java     | 139 ++++++++++++-
 .../web/client/StandardWebClientServiceTest.java   |  17 +-
 .../processors/opentelemetry/ListenOTLPTest.java   |  33 +--
 .../service/StandardWebClientServiceProvider.java  |   8 +-
 11 files changed, 467 insertions(+), 246 deletions(-)

diff --git a/nifi-commons/nifi-web-client/pom.xml 
b/nifi-commons/nifi-web-client/pom.xml
index c3b2adbfea..1b71aa9a32 100644
--- a/nifi-commons/nifi-web-client/pom.xml
+++ b/nifi-commons/nifi-web-client/pom.xml
@@ -21,7 +21,7 @@
         <version>2.0.0-SNAPSHOT</version>
     </parent>
     <artifactId>nifi-web-client</artifactId>
-    <description>Standard implementation of nifi-web-client-api using 
OkHttp</description>
+    <description>Standard implementation of nifi-web-client-api using Java 
HttpClient</description>
 
     <dependencies>
         <dependency>
@@ -29,15 +29,6 @@
             <artifactId>nifi-web-client-api</artifactId>
             <version>2.0.0-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>com.squareup.okhttp3</groupId>
-            <artifactId>okhttp</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.squareup.okio</groupId>
-            <artifactId>okio-jvm</artifactId>
-            <version>${okio.version}</version>
-        </dependency>
         <dependency>
             <groupId>com.squareup.okhttp3</groupId>
             <artifactId>mockwebserver</artifactId>
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/InputStreamRequestBody.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/InputStreamRequestBody.java
deleted file mode 100644
index 68ab5183da..0000000000
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/InputStreamRequestBody.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-package org.apache.nifi.web.client;
-
-import okhttp3.MediaType;
-import okhttp3.RequestBody;
-import okio.BufferedSink;
-import okio.Okio;
-import okio.Source;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * OkHttp Request Body implementation based on an InputStream
- */
-class InputStreamRequestBody extends RequestBody {
-    private final InputStream inputStream;
-
-    private final long contentLength;
-
-    InputStreamRequestBody(final InputStream inputStream, final long 
contentLength) {
-        this.inputStream = inputStream;
-        this.contentLength = contentLength;
-    }
-
-    @Override
-    public long contentLength() {
-        return contentLength;
-    }
-
-    @Override
-    public MediaType contentType() {
-        return null;
-    }
-
-    @Override
-    public void writeTo(final BufferedSink bufferedSink) throws IOException {
-        final Source source = Okio.source(inputStream);
-        bufferedSink.writeAll(source);
-    }
-}
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardHttpUriBuilder.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardHttpUriBuilder.java
index 33ecfa3f46..6b5816facd 100644
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardHttpUriBuilder.java
+++ 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardHttpUriBuilder.java
@@ -16,65 +16,171 @@
  */
 package org.apache.nifi.web.client;
 
-import okhttp3.HttpUrl;
 import org.apache.nifi.web.client.api.HttpUriBuilder;
 
 import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
- * Standard HTTP URI Builder based on OkHttp HttpUrl
+ * Standard HTTP URI Builder based using java.net.URI
  */
 public class StandardHttpUriBuilder implements HttpUriBuilder {
-    private final HttpUrl.Builder builder;
+    private static final String HTTP_SCHEME = "http";
 
-    public StandardHttpUriBuilder() {
-        this.builder = new HttpUrl.Builder();
-    }
+    private static final String HTTPS_SCHEME = "https";
+
+    private static final int UNKNOWN_PORT = -1;
+
+    private static final int MAXIMUM_PORT = 65535;
+
+    private static final String PATH_SEGMENT_SEPARATOR = "/";
+
+    private static final String QUERY_PARAMETER_SEPARATOR = "&";
+
+    private static final String QUERY_PARAMETER_VALUE_SEPARATOR = "=";
+
+    private String scheme = HTTP_SCHEME;
+
+    private String host;
+
+    private int port = UNKNOWN_PORT;
+
+    private String encodedPath;
+
+    private final List<String> pathSegments = new ArrayList<>();
+
+    private final Map<String, List<String>> queryParameters = new 
LinkedHashMap<>();
 
     @Override
     public URI build() {
-        final HttpUrl httpUrl = builder.build();
-        return httpUrl.uri();
+        if (host == null) {
+            throw new IllegalStateException("Host not specified");
+        }
+
+        final String resolvedPath = getResolvedPath();
+        final String resolvedQuery = getResolvedQuery();
+
+        try {
+            return new URI(scheme, null, host, port, resolvedPath, 
resolvedQuery, null);
+        } catch (final URISyntaxException e) {
+            throw new IllegalArgumentException("URI construction failed", e);
+        }
     }
 
     @Override
     public HttpUriBuilder scheme(final String scheme) {
         Objects.requireNonNull(scheme, "Scheme required");
-        builder.scheme(scheme);
+        if (HTTP_SCHEME.equals(scheme) || HTTPS_SCHEME.equals(scheme)) {
+            this.scheme = scheme;
+        } else {
+            throw new IllegalArgumentException("Scheme [%s] not 
supported".formatted(scheme));
+        }
         return this;
     }
 
     @Override
     public HttpUriBuilder host(final String host) {
         Objects.requireNonNull(host, "Host required");
-        builder.host(host);
+        this.host = host;
         return this;
     }
 
     @Override
     public HttpUriBuilder port(int port) {
-        builder.port(port);
+        if (port < UNKNOWN_PORT || port > MAXIMUM_PORT) {
+            throw new IllegalArgumentException("Port [%d] not 
valid".formatted(port));
+        }
+        this.port = port;
         return this;
     }
 
     @Override
     public HttpUriBuilder encodedPath(final String encodedPath) {
-        builder.encodedPath(encodedPath);
+        this.encodedPath = encodedPath;
         return this;
     }
 
     @Override
     public HttpUriBuilder addPathSegment(final String pathSegment) {
         Objects.requireNonNull(pathSegment, "Path segment required");
-        builder.addPathSegment(pathSegment);
+        pathSegments.add(pathSegment);
         return this;
     }
 
     @Override
     public HttpUriBuilder addQueryParameter(final String name, final String 
value) {
         Objects.requireNonNull(name, "Parameter name required");
-        builder.addQueryParameter(name, value);
+        final List<String> parameters = queryParameters.computeIfAbsent(name, 
parameterName -> new ArrayList<>());
+        parameters.add(value);
         return this;
     }
+
+    private String getResolvedPath() {
+        final StringBuilder pathBuilder = new StringBuilder();
+        if (encodedPath != null) {
+            // Decoded path required for subsequent encoding in java.net.URI 
construction
+            final String decodedPath = URLDecoder.decode(encodedPath, 
StandardCharsets.UTF_8);
+            pathBuilder.append(decodedPath);
+        }
+
+        if (!pathSegments.isEmpty()) {
+            final String separatedPath = String.join(PATH_SEGMENT_SEPARATOR, 
pathSegments);
+            pathBuilder.append(separatedPath);
+        }
+        final String path = pathBuilder.toString();
+
+        final String resolvedPath;
+        if (path.startsWith(PATH_SEGMENT_SEPARATOR)) {
+            resolvedPath = path;
+        } else {
+            resolvedPath = PATH_SEGMENT_SEPARATOR + path;
+        }
+        return resolvedPath;
+    }
+
+    private String getResolvedQuery() {
+        final StringBuilder queryBuilder = new StringBuilder();
+
+        final Iterator<Map.Entry<String, List<String>>> parameters = 
queryParameters.entrySet().iterator();
+        while (parameters.hasNext()) {
+            final Map.Entry<String, List<String>> parameter = 
parameters.next();
+
+            final String name = parameter.getKey();
+            queryBuilder.append(name);
+
+            final Iterator<String> values = parameter.getValue().iterator();
+            while (values.hasNext()) {
+                final String value = values.next();
+                if (value != null) {
+                    queryBuilder.append(QUERY_PARAMETER_VALUE_SEPARATOR);
+                    queryBuilder.append(value);
+                }
+
+                if (values.hasNext()) {
+                    queryBuilder.append(QUERY_PARAMETER_SEPARATOR);
+                    queryBuilder.append(name);
+                }
+            }
+
+            if (parameters.hasNext()) {
+                queryBuilder.append(QUERY_PARAMETER_SEPARATOR);
+            }
+        }
+
+        final String query;
+        if (queryBuilder.isEmpty()) {
+            query = null;
+        } else {
+            query = queryBuilder.toString();
+        }
+        return query;
+    }
 }
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardWebClientService.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardWebClientService.java
index c0da84130b..2295e52f5a 100644
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardWebClientService.java
+++ 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/StandardWebClientService.java
@@ -16,16 +16,6 @@
  */
 package org.apache.nifi.web.client;
 
-import okhttp3.Call;
-import okhttp3.Credentials;
-import okhttp3.Headers;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-
 import org.apache.nifi.web.client.api.HttpEntityHeaders;
 import org.apache.nifi.web.client.api.HttpRequestBodySpec;
 import org.apache.nifi.web.client.api.HttpRequestHeadersSpec;
@@ -37,37 +27,60 @@ import org.apache.nifi.web.client.api.WebClientService;
 import org.apache.nifi.web.client.api.WebClientServiceException;
 import org.apache.nifi.web.client.proxy.ProxyContext;
 import org.apache.nifi.web.client.redirect.RedirectHandling;
-import org.apache.nifi.web.client.ssl.SSLSocketFactoryProvider;
-import org.apache.nifi.web.client.ssl.StandardSSLSocketFactoryProvider;
+import org.apache.nifi.web.client.ssl.SSLContextProvider;
+import org.apache.nifi.web.client.ssl.StandardSSLContextProvider;
 import org.apache.nifi.web.client.ssl.TlsContext;
 
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.X509TrustManager;
+import javax.net.ssl.SSLContext;
 import java.io.ByteArrayInputStream;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
 import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
 import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.ByteBuffer;
 import java.time.Duration;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.OptionalLong;
+import java.util.concurrent.Flow;
 
 /**
- * Standard implementation of Web Client Service using OkHttp
+ * Standard implementation of Web Client Service using Java HttpClient
  */
-public class StandardWebClientService implements WebClientService {
+public class StandardWebClientService implements WebClientService, Closeable {
     private static final byte[] EMPTY_BYTES = new byte[0];
 
-    private static final SSLSocketFactoryProvider sslSocketFactoryProvider = 
new StandardSSLSocketFactoryProvider();
+    private static final SSLContextProvider sslContextProvider = new 
StandardSSLContextProvider();
+
+    private HttpClient httpClient;
+
+    private Duration connectTimeout;
+
+    private Duration readTimeout;
+
+    private Duration writeTimeout;
 
-    private OkHttpClient okHttpClient;
+    private RedirectHandling redirectHandling;
+
+    private ProxyContext proxyContext;
+
+    private TlsContext tlsContext;
 
     /**
-     * Standard Web Client Service constructor creates OkHttpClient using 
default settings
+     * Standard Web Client Service constructor creates a Java HttpClient using 
default settings
      */
     public StandardWebClientService() {
-        okHttpClient = new OkHttpClient.Builder().build();
+        httpClient = HttpClient.newBuilder().build();
     }
 
     /**
@@ -77,27 +90,28 @@ public class StandardWebClientService implements 
WebClientService {
      */
     public void setConnectTimeout(final Duration connectTimeout) {
         Objects.requireNonNull(connectTimeout, "Connect Timeout required");
-        okHttpClient = 
okHttpClient.newBuilder().connectTimeout(connectTimeout).build();
+        this.connectTimeout = connectTimeout;
+        this.httpClient = buildHttpClient();
     }
 
     /**
-     * Set timeout for reading responses from socket connection
+     * Set timeout for reading responses from socket connection takes 
precedence over write timeout
      *
      * @param readTimeout Read Timeout
      */
     public void setReadTimeout(final Duration readTimeout) {
         Objects.requireNonNull(readTimeout, "Read Timeout required");
-        okHttpClient = 
okHttpClient.newBuilder().readTimeout(readTimeout).build();
+        this.readTimeout = readTimeout;
     }
 
     /**
-     * Set timeout for writing requests to socket connection
+     * Set timeout for writing requests to socket connection when read timeout 
is not specified
      *
      * @param writeTimeout Write Timeout
      */
     public void setWriteTimeout(final Duration writeTimeout) {
         Objects.requireNonNull(writeTimeout, "Write Timeout required");
-        okHttpClient = 
okHttpClient.newBuilder().writeTimeout(writeTimeout).build();
+        this.writeTimeout = writeTimeout;
     }
 
     /**
@@ -107,17 +121,9 @@ public class StandardWebClientService implements 
WebClientService {
      */
     public void setProxyContext(final ProxyContext proxyContext) {
         Objects.requireNonNull(proxyContext, "Proxy Context required");
-        final Proxy proxy = Objects.requireNonNull(proxyContext.getProxy(), 
"Proxy required");
-        okHttpClient = okHttpClient.newBuilder().proxy(proxy).build();
-
-        final Optional<String> proxyUsername = proxyContext.getUsername();
-        if (proxyUsername.isPresent()) {
-            final String username = proxyUsername.get();
-            final String password = proxyContext.getPassword().orElseThrow(() 
-> new IllegalArgumentException("Proxy password required"));
-            final String credentials = Credentials.basic(username, password);
-            final BasicProxyAuthenticator proxyAuthenticator = new 
BasicProxyAuthenticator(credentials);
-            okHttpClient = 
okHttpClient.newBuilder().proxyAuthenticator(proxyAuthenticator).build();
-        }
+        Objects.requireNonNull(proxyContext.getProxy(), "Proxy required");
+        this.proxyContext = proxyContext;
+        this.httpClient = buildHttpClient();
     }
 
     /**
@@ -127,8 +133,8 @@ public class StandardWebClientService implements 
WebClientService {
      */
     public void setRedirectHandling(final RedirectHandling redirectHandling) {
         Objects.requireNonNull(redirectHandling, "Redirect Handling required");
-        final boolean followRedirects = RedirectHandling.FOLLOWED == 
redirectHandling;
-        okHttpClient = 
okHttpClient.newBuilder().followRedirects(followRedirects).followSslRedirects(followRedirects).build();
+        this.redirectHandling = redirectHandling;
+        this.httpClient = buildHttpClient();
     }
 
     /**
@@ -138,9 +144,9 @@ public class StandardWebClientService implements 
WebClientService {
      */
     public void setTlsContext(final TlsContext tlsContext) {
         Objects.requireNonNull(tlsContext, "TLS Context required");
-        final X509TrustManager trustManager = 
Objects.requireNonNull(tlsContext.getTrustManager(), "Trust Manager required");
-        final SSLSocketFactory sslSocketFactory = 
sslSocketFactoryProvider.getSocketFactory(tlsContext);
-        okHttpClient = 
okHttpClient.newBuilder().sslSocketFactory(sslSocketFactory, 
trustManager).build();
+        Objects.requireNonNull(tlsContext.getTrustManager(), "Trust Manager 
required");
+        this.tlsContext = tlsContext;
+        this.httpClient = buildHttpClient();
     }
 
     /**
@@ -190,6 +196,7 @@ public class StandardWebClientService implements 
WebClientService {
      *
      * @return HTTP Request URI Specification builder
      */
+    @Override
     public HttpRequestUriSpec post() {
         return method(StandardHttpRequestMethod.POST);
     }
@@ -199,10 +206,77 @@ public class StandardWebClientService implements 
WebClientService {
      *
      * @return HTTP Request URI Specification builder
      */
+    @Override
     public HttpRequestUriSpec put() {
         return method(StandardHttpRequestMethod.PUT);
     }
 
+    /**
+     * Close configured HttpClient and shutdown executor resources
+     */
+    @Override
+    public void close() {
+        httpClient.close();
+    }
+
+    private HttpClient buildHttpClient() {
+        final HttpClient.Builder builder = HttpClient.newBuilder();
+
+        if (connectTimeout != null) {
+            builder.connectTimeout(connectTimeout);
+        }
+        if (tlsContext != null) {
+            final SSLContext sslContext = 
sslContextProvider.getSslContext(tlsContext);
+            builder.sslContext(sslContext);
+        }
+        if (RedirectHandling.FOLLOWED == redirectHandling) {
+            builder.followRedirects(HttpClient.Redirect.ALWAYS);
+        } else if (RedirectHandling.IGNORED == redirectHandling) {
+            builder.followRedirects(HttpClient.Redirect.NEVER);
+        }
+        if (proxyContext != null) {
+            final Proxy proxy = proxyContext.getProxy();
+            final SocketAddress proxyAddress = proxy.address();
+            if (proxyAddress instanceof InetSocketAddress proxySocketAddress) {
+                final ProxySelector proxySelector = 
ProxySelector.of(proxySocketAddress);
+                builder.proxy(proxySelector);
+
+                final Optional<String> proxyUsername = 
proxyContext.getUsername();
+                if (proxyUsername.isPresent()) {
+                    final ProxyPasswordAuthenticator passwordAuthenticator = 
getProxyPasswordAuthenticator(proxyUsername.get());
+                    builder.authenticator(passwordAuthenticator);
+                }
+            }
+        }
+
+        return builder.build();
+    }
+
+    private ProxyPasswordAuthenticator getProxyPasswordAuthenticator(final 
String proxyUsername) {
+        final char[] password;
+        final Optional<String> proxyPassword = proxyContext.getPassword();
+        if (proxyPassword.isPresent()) {
+            password = proxyPassword.get().toCharArray();
+        } else {
+            throw new IllegalArgumentException("Proxy Password not 
configured");
+        }
+        final PasswordAuthentication passwordAuthentication = new 
PasswordAuthentication(proxyUsername, password);
+        return new ProxyPasswordAuthenticator(passwordAuthentication);
+    }
+
+    static class ProxyPasswordAuthenticator extends Authenticator {
+        private final PasswordAuthentication passwordAuthentication;
+
+        ProxyPasswordAuthenticator(final PasswordAuthentication 
passwordAuthentication) {
+            this.passwordAuthentication = passwordAuthentication;
+        }
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            return passwordAuthentication;
+        }
+    }
+
     class StandardHttpRequestUriSpec implements HttpRequestUriSpec {
         private final HttpRequestMethod httpRequestMethod;
 
@@ -224,7 +298,7 @@ public class StandardWebClientService implements 
WebClientService {
 
         private final URI uri;
 
-        private final Headers.Builder headersBuilder;
+        private final HttpRequest.Builder requestBuilder;
 
         private long contentLength = UNKNOWN_CONTENT_LENGTH;
 
@@ -233,7 +307,7 @@ public class StandardWebClientService implements 
WebClientService {
         StandardHttpRequestBodySpec(final HttpRequestMethod httpRequestMethod, 
final URI uri) {
             this.httpRequestMethod = httpRequestMethod;
             this.uri = uri;
-            this.headersBuilder = new Headers.Builder();
+            this.requestBuilder = HttpRequest.newBuilder();
         }
 
         @Override
@@ -247,57 +321,73 @@ public class StandardWebClientService implements 
WebClientService {
         public HttpRequestBodySpec header(final String headerName, final 
String headerValue) {
             Objects.requireNonNull(headerName, "Header Name required");
             Objects.requireNonNull(headerValue, "Header Value required");
-            headersBuilder.add(headerName, headerValue);
+            requestBuilder.header(headerName, headerValue);
             return this;
         }
 
         @Override
         public HttpResponseEntity retrieve() {
-            final Request request = getRequest();
-            final Call call = okHttpClient.newCall(request);
-            final Response response = execute(call);
+            final HttpRequest request = getRequest();
+            final HttpResponse<InputStream> response = getResponse(request);
 
-            final int code = response.code();
-            final Headers responseHeaders = response.headers();
-            final HttpEntityHeaders headers = new 
StandardHttpEntityHeaders(responseHeaders.toMultimap());
-            final ResponseBody responseBody = response.body();
-            final InputStream body = responseBody == null ? new 
ByteArrayInputStream(EMPTY_BYTES) : responseBody.byteStream();
+            final int code = response.statusCode();
+
+            final HttpHeaders responseHeaders = response.headers();
+            final HttpEntityHeaders headers = new 
StandardHttpEntityHeaders(responseHeaders.map());
+
+            final InputStream responseBody = response.body();
+            final InputStream body = responseBody == null ? new 
ByteArrayInputStream(EMPTY_BYTES) : responseBody;
 
             return new StandardHttpResponseEntity(code, headers, body);
         }
 
-        private Response execute(final Call call) {
+        private HttpResponse<InputStream> getResponse(final HttpRequest 
request) {
             try {
-                return call.execute();
+                return httpClient.send(request, 
HttpResponse.BodyHandlers.ofInputStream());
             } catch (final IOException e) {
                 throw new WebClientServiceException("Request execution 
failed", e, uri, httpRequestMethod);
+            } catch (final InterruptedException e) {
+                throw new WebClientServiceException("Request execution 
interrupted", e, uri, httpRequestMethod);
             }
         }
 
-        private Request getRequest() {
-            final HttpUrl url = HttpUrl.get(uri);
-            Objects.requireNonNull(url, "HTTP Request URI required");
+        private HttpRequest getRequest() {
+            if (writeTimeout != null) {
+                requestBuilder.timeout(writeTimeout);
+            }
+            // Prefer Read Timeout over Write Timeout when specified
+            if (readTimeout != null) {
+                requestBuilder.timeout(readTimeout);
+            }
 
-            final Headers headers = headersBuilder.build();
-            final RequestBody requestBody = getRequestBody();
+            final HttpRequest.BodyPublisher bodyPublisher = getBodyPublisher();
 
-            return new Request.Builder()
-                    .method(httpRequestMethod.getMethod(), requestBody)
-                    .url(url)
-                    .headers(headers)
+            return requestBuilder.method(httpRequestMethod.getMethod(), 
bodyPublisher)
+                    .uri(uri)
                     .build();
         }
 
-        private RequestBody getRequestBody() {
-            final RequestBody requestBody;
+        private HttpRequest.BodyPublisher getBodyPublisher() {
+            final HttpRequest.BodyPublisher bodyPublisher;
 
             if (body == null) {
-                requestBody = null;
+                bodyPublisher = HttpRequest.BodyPublishers.noBody();
             } else {
-                requestBody = new InputStreamRequestBody(body, contentLength);
+                final HttpRequest.BodyPublisher inputStreamPublisher = 
HttpRequest.BodyPublishers.ofInputStream(() -> body);
+                bodyPublisher = new HttpRequest.BodyPublisher() {
+                    @Override
+                    public long contentLength() {
+                        return contentLength;
+                    }
+
+                    @Override
+                    public void subscribe(Flow.Subscriber<? super ByteBuffer> 
subscriber) {
+                        inputStreamPublisher.subscribe(subscriber);
+                    }
+                };
             }
 
-            return requestBody;
+            return bodyPublisher;
         }
     }
 }
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/BasicProxyAuthenticator.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/SSLContextProvider.java
similarity index 51%
rename from 
nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/BasicProxyAuthenticator.java
rename to 
nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/SSLContextProvider.java
index 2377812cbb..c9b112e7a6 100644
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/BasicProxyAuthenticator.java
+++ 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/SSLContextProvider.java
@@ -14,30 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.nifi.web.client;
+package org.apache.nifi.web.client.ssl;
 
-import okhttp3.Authenticator;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.Route;
+import javax.net.ssl.SSLContext;
 
 /**
- * OkHttp Authenticator supporting Proxy Authentication using HTTP Basic 
credentials
+ * SSLContext Provider from TLS Context configured
  */
-class BasicProxyAuthenticator implements Authenticator {
-    private static final String PROXY_AUTHORIZATION_HEADER = 
"Proxy-Authorization";
-
-    private final String credentials;
-
-    BasicProxyAuthenticator(final String credentials) {
-        this.credentials = credentials;
-    }
-
-    @Override
-    public Request authenticate(final Route route, final Response response) {
-        return response.request()
-                .newBuilder()
-                .header(PROXY_AUTHORIZATION_HEADER, credentials)
-                .build();
-    }
+public interface SSLContextProvider {
+    /**
+     * Get SSL Context
+     *
+     * @param tlsContext TLS Context configured
+     * @return SSL Context
+     */
+    SSLContext getSslContext(TlsContext tlsContext);
 }
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLContextProvider.java
similarity index 79%
copy from 
nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
copy to 
nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLContextProvider.java
index efa312d214..f2358e22b5 100644
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
+++ 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLContextProvider.java
@@ -18,7 +18,6 @@ package org.apache.nifi.web.client.ssl;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
@@ -28,20 +27,17 @@ import java.security.SecureRandom;
 import java.util.Objects;
 import java.util.Optional;
 
-/**
- * Standard implementation of SSLSocketFactory Provider
- */
-public class StandardSSLSocketFactoryProvider implements 
SSLSocketFactoryProvider {
+public class StandardSSLContextProvider implements SSLContextProvider {
     /**
-     * Get SSLSocketFactory defaults to system Trust Manager and allows an 
empty Key Manager
+     * Get SSL Context
      *
-     * @param tlsContext TLS Context configuration
-     * @return SSLSocketFactory
+     * @param tlsContext TLS Context configured
+     * @return SSL Context
      */
     @Override
-    public SSLSocketFactory getSocketFactory(final TlsContext tlsContext) {
+    public SSLContext getSslContext(final TlsContext tlsContext) {
         Objects.requireNonNull(tlsContext, "TLS Context required");
-        final SSLContext sslContext = getSslContext(tlsContext);
+        final SSLContext sslContext = getSslContextConfigured(tlsContext);
 
         try {
             final Optional<X509KeyManager> keyManager = 
tlsContext.getKeyManager();
@@ -53,13 +49,13 @@ public class StandardSSLSocketFactoryProvider implements 
SSLSocketFactoryProvide
             final SecureRandom secureRandom = new SecureRandom();
             sslContext.init(keyManagers, trustManagers, secureRandom);
 
-            return sslContext.getSocketFactory();
+            return sslContext;
         } catch (final KeyManagementException e) {
             throw new IllegalArgumentException("SSLContext initialization 
failed", e);
         }
     }
 
-    private SSLContext getSslContext(final TlsContext tlsContext) {
+    private SSLContext getSslContextConfigured(final TlsContext tlsContext) {
         final String protocol = 
Objects.requireNonNull(tlsContext.getProtocol(), "TLS Protocol required");
         try {
             return SSLContext.getInstance(protocol);
diff --git 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
index efa312d214..859943161f 100644
--- 
a/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
+++ 
b/nifi-commons/nifi-web-client/src/main/java/org/apache/nifi/web/client/ssl/StandardSSLSocketFactoryProvider.java
@@ -16,17 +16,9 @@
  */
 package org.apache.nifi.web.client.ssl;
 
-import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Objects;
-import java.util.Optional;
 
 /**
  * Standard implementation of SSLSocketFactory Provider
@@ -41,30 +33,8 @@ public class StandardSSLSocketFactoryProvider implements 
SSLSocketFactoryProvide
     @Override
     public SSLSocketFactory getSocketFactory(final TlsContext tlsContext) {
         Objects.requireNonNull(tlsContext, "TLS Context required");
-        final SSLContext sslContext = getSslContext(tlsContext);
-
-        try {
-            final Optional<X509KeyManager> keyManager = 
tlsContext.getKeyManager();
-            final KeyManager[] keyManagers = keyManager.map(x509KeyManager -> 
new KeyManager[]{x509KeyManager}).orElse(null);
-
-            final X509TrustManager trustManager = tlsContext.getTrustManager();
-            final TrustManager[] trustManagers = trustManager == null ? null : 
new TrustManager[]{trustManager};
-
-            final SecureRandom secureRandom = new SecureRandom();
-            sslContext.init(keyManagers, trustManagers, secureRandom);
-
-            return sslContext.getSocketFactory();
-        } catch (final KeyManagementException e) {
-            throw new IllegalArgumentException("SSLContext initialization 
failed", e);
-        }
-    }
-
-    private SSLContext getSslContext(final TlsContext tlsContext) {
-        final String protocol = 
Objects.requireNonNull(tlsContext.getProtocol(), "TLS Protocol required");
-        try {
-            return SSLContext.getInstance(protocol);
-        } catch (final NoSuchAlgorithmException e) {
-            throw new IllegalArgumentException(String.format("SSLContext 
protocol [%s] not supported", protocol), e);
-        }
+        final SSLContextProvider sslContextProvider = new 
StandardSSLContextProvider();
+        final SSLContext sslContext = 
sslContextProvider.getSslContext(tlsContext);
+        return sslContext.getSocketFactory();
     }
 }
diff --git 
a/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardHttpUriBuilderTest.java
 
b/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardHttpUriBuilderTest.java
index 73cb81f0b7..24a2244873 100644
--- 
a/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardHttpUriBuilderTest.java
+++ 
b/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardHttpUriBuilderTest.java
@@ -33,23 +33,69 @@ class StandardHttpUriBuilderTest {
 
     private static final String ENCODED_PATH = "/resources/search";
 
+    private static final String PATH_WITH_SPACES_ENCODED = 
"/resources/%20separated%20search";
+
     private static final String RESOURCES_PATH_SEGMENT = "resources";
 
+    private static final String RESOURCES_PATH_SEGMENT_SEPARATED = 
"resources|separated";
+
+    private static final String RESOURCES_PATH_SEGMENT_SEPARATED_ENCODED = 
"resources%7Cseparated";
+
     private static final String PARAMETER_NAME = "search";
 
     private static final String PARAMETER_VALUE = "terms";
 
-    private static final URI HTTP_LOCALHOST_URI = 
URI.create(String.format("%s://%s/", HTTP_SCHEME, LOCALHOST));
+    private static final String SECOND_PARAMETER_NAME = "refresh";
+
+    private static final String SECOND_PARAMETER_VALUE = "{0}";
+
+    private static final String SECOND_PARAMETER_VALUE_ENCODED = "%7B0%7D";
+
+    private static final String PARAMETER_NAME_AND_VALUE = 
String.format("%s=%s", PARAMETER_NAME, PARAMETER_VALUE);
+
+    private static final URI HTTP_LOCALHOST_URI = URI.create(
+            String.format("%s://%s/", HTTP_SCHEME, LOCALHOST)
+    );
+
+    private static final URI HTTP_LOCALHOST_PORT_URI = URI.create(
+            String.format("%s://%s:%d/", HTTP_SCHEME, LOCALHOST, PORT)
+    );
+
+    private static final URI HTTP_LOCALHOST_PORT_ENCODED_PATH_URI = URI.create(
+            String.format("%s://%s:%d%s", HTTP_SCHEME, LOCALHOST, PORT, 
ENCODED_PATH)
+    );
 
-    private static final URI HTTP_LOCALHOST_PORT_URI = 
URI.create(String.format("%s://%s:%d/", HTTP_SCHEME, LOCALHOST, PORT));
+    private static final URI HTTP_LOCALHOST_PORT_ENCODED_PATH_WITH_SPACES_URI 
= URI.create(
+            String.format("%s://%s:%d%s", HTTP_SCHEME, LOCALHOST, PORT, 
PATH_WITH_SPACES_ENCODED)
+    );
 
-    private static final URI HTTP_LOCALHOST_PORT_ENCODED_PATH_URI = 
URI.create(String.format("%s://%s:%d%s", HTTP_SCHEME, LOCALHOST, PORT, 
ENCODED_PATH));
+    private static final URI HTTP_LOCALHOST_RESOURCES_URI = URI.create(
+            String.format("%s%s", HTTP_LOCALHOST_URI, RESOURCES_PATH_SEGMENT)
+    );
 
-    private static final URI HTTP_LOCALHOST_RESOURCES_URI = 
URI.create(String.format("%s%s", HTTP_LOCALHOST_URI, RESOURCES_PATH_SEGMENT));
+    private static final URI HTTP_LOCALHOST_RESOURCES_SEPARATED_URI = 
URI.create(
+            String.format("%s%s", HTTP_LOCALHOST_URI, 
RESOURCES_PATH_SEGMENT_SEPARATED_ENCODED)
+    );
 
-    private static final URI HTTP_LOCALHOST_QUERY_URI = 
URI.create(String.format("%s?%s=%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME, PARAMETER_VALUE));
+    private static final URI HTTP_LOCALHOST_QUERY_URI = URI.create(
+            String.format("%s?%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME_AND_VALUE)
+    );
 
-    private static final URI HTTP_LOCALHOST_QUERY_EMPTY_VALUE_URI = 
URI.create(String.format("%s?%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME));
+    private static final URI HTTP_LOCALHOST_QUERY_REPEATED_URI = URI.create(
+            String.format("%s?%s&%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME_AND_VALUE, PARAMETER_NAME_AND_VALUE)
+    );
+
+    private static final URI HTTP_LOCALHOST_QUERY_SECOND_PARAMETER_URI = 
URI.create(
+            String.format("%s?%s&%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME_AND_VALUE, SECOND_PARAMETER_NAME)
+    );
+
+    private static final URI HTTP_LOCALHOST_QUERY_PARAMETER_VALUE_URI = 
URI.create(
+            String.format("%s?%s=%s", HTTP_LOCALHOST_RESOURCES_URI, 
SECOND_PARAMETER_NAME, SECOND_PARAMETER_VALUE_ENCODED)
+    );
+
+    private static final URI HTTP_LOCALHOST_QUERY_EMPTY_VALUE_URI = URI.create(
+            String.format("%s?%s", HTTP_LOCALHOST_RESOURCES_URI, 
PARAMETER_NAME)
+    );
 
     @Test
     void testBuildIllegalStateException() {
@@ -69,6 +115,21 @@ class StandardHttpUriBuilderTest {
         assertEquals(HTTP_LOCALHOST_URI, uri);
     }
 
+    @Test
+    void testBuildSchemeInvalid() {
+        assertThrows(IllegalArgumentException.class, () -> new 
StandardHttpUriBuilder().scheme(String.class.getSimpleName()));
+    }
+
+    @Test
+    void testBuildPortInvalidMaximum() {
+        assertThrows(IllegalArgumentException.class, () -> new 
StandardHttpUriBuilder().port(Integer.MAX_VALUE));
+    }
+
+    @Test
+    void testBuildPortInvalidMinimum() {
+        assertThrows(IllegalArgumentException.class, () -> new 
StandardHttpUriBuilder().port(Integer.MIN_VALUE));
+    }
+
     @Test
     void testBuildSchemeHostPort() {
         final HttpUriBuilder builder = new StandardHttpUriBuilder()
@@ -94,6 +155,19 @@ class StandardHttpUriBuilderTest {
         assertEquals(HTTP_LOCALHOST_PORT_ENCODED_PATH_URI, uri);
     }
 
+    @Test
+    void testBuildSchemeHostPortEncodedPathWithSpaces() {
+        final HttpUriBuilder builder = new StandardHttpUriBuilder()
+                .scheme(HTTP_SCHEME)
+                .host(LOCALHOST)
+                .port(PORT)
+                .encodedPath(PATH_WITH_SPACES_ENCODED);
+
+        final URI uri = builder.build();
+
+        assertEquals(HTTP_LOCALHOST_PORT_ENCODED_PATH_WITH_SPACES_URI, uri);
+    }
+
     @Test
     void testBuildSchemeHostPathSegment() {
         final HttpUriBuilder builder = new StandardHttpUriBuilder()
@@ -106,6 +180,18 @@ class StandardHttpUriBuilderTest {
         assertEquals(HTTP_LOCALHOST_RESOURCES_URI, uri);
     }
 
+    @Test
+    void testBuildSchemeHostPathSegmentSeparated() {
+        final HttpUriBuilder builder = new StandardHttpUriBuilder()
+                .scheme(HTTP_SCHEME)
+                .host(LOCALHOST)
+                .addPathSegment(RESOURCES_PATH_SEGMENT_SEPARATED);
+
+        final URI uri = builder.build();
+
+        assertEquals(HTTP_LOCALHOST_RESOURCES_SEPARATED_URI, uri);
+    }
+
     @Test
     void testBuildSchemeHostPathSegmentQueryParameter() {
         final HttpUriBuilder builder = new StandardHttpUriBuilder()
@@ -119,6 +205,47 @@ class StandardHttpUriBuilderTest {
         assertEquals(HTTP_LOCALHOST_QUERY_URI, uri);
     }
 
+    @Test
+    void testBuildSchemeHostPathSegmentQueryParameterRepeated() {
+        final HttpUriBuilder builder = new StandardHttpUriBuilder()
+                .scheme(HTTP_SCHEME)
+                .host(LOCALHOST)
+                .addPathSegment(RESOURCES_PATH_SEGMENT)
+                .addQueryParameter(PARAMETER_NAME, PARAMETER_VALUE)
+                .addQueryParameter(PARAMETER_NAME, PARAMETER_VALUE);
+
+        final URI uri = builder.build();
+
+        assertEquals(HTTP_LOCALHOST_QUERY_REPEATED_URI, uri);
+    }
+
+    @Test
+    void testBuildSchemeHostPathSegmentSecondQueryParameter() {
+        final HttpUriBuilder builder = new StandardHttpUriBuilder()
+                .scheme(HTTP_SCHEME)
+                .host(LOCALHOST)
+                .addPathSegment(RESOURCES_PATH_SEGMENT)
+                .addQueryParameter(PARAMETER_NAME, PARAMETER_VALUE)
+                .addQueryParameter(SECOND_PARAMETER_NAME, null);
+
+        final URI uri = builder.build();
+
+        assertEquals(HTTP_LOCALHOST_QUERY_SECOND_PARAMETER_URI, uri);
+    }
+
+    @Test
+    void testBuildSchemeHostPathSegmentQueryParameterValueEncoded() {
+        final HttpUriBuilder builder = new StandardHttpUriBuilder()
+                .scheme(HTTP_SCHEME)
+                .host(LOCALHOST)
+                .addPathSegment(RESOURCES_PATH_SEGMENT)
+                .addQueryParameter(SECOND_PARAMETER_NAME, 
SECOND_PARAMETER_VALUE);
+
+        final URI uri = builder.build();
+
+        assertEquals(HTTP_LOCALHOST_QUERY_PARAMETER_VALUE_URI, uri);
+    }
+
     @Test
     void testBuildSchemeHostPathSegmentQueryParameterNullValue() {
         final HttpUriBuilder builder = new StandardHttpUriBuilder()
diff --git 
a/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardWebClientServiceTest.java
 
b/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardWebClientServiceTest.java
index ae0a80fa6f..4d24e34499 100644
--- 
a/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardWebClientServiceTest.java
+++ 
b/nifi-commons/nifi-web-client/src/test/java/org/apache/nifi/web/client/StandardWebClientServiceTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.web.client;
 
-import okhttp3.Credentials;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import okhttp3.mockwebserver.RecordedRequest;
@@ -44,11 +43,11 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Proxy;
-import java.net.SocketTimeoutException;
 import java.net.URI;
+import java.net.http.HttpTimeoutException;
 import java.nio.charset.StandardCharsets;
-import java.security.cert.X509Certificate;
 import java.time.Duration;
+import java.util.Base64;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -98,8 +97,6 @@ public class StandardWebClientServiceTest {
 
     private static final String TLS_PROTOCOL_UNSUPPORTED = "TLSv0";
 
-    private static final X509Certificate[] TRUSTED_ISSUERS = new 
X509Certificate[0];
-
     @Mock
     TlsContext tlsContext;
 
@@ -128,7 +125,6 @@ public class StandardWebClientServiceTest {
     void testSetTlsContext() {
         when(tlsContext.getProtocol()).thenReturn(TLS_PROTOCOL);
         when(tlsContext.getTrustManager()).thenReturn(trustManager);
-        when(trustManager.getAcceptedIssuers()).thenReturn(TRUSTED_ISSUERS);
 
         service.setTlsContext(tlsContext);
     }
@@ -142,7 +138,7 @@ public class StandardWebClientServiceTest {
     }
 
     @Test
-    void testSocketTimeoutException() throws IOException {
+    void testHttpTimeoutException() throws IOException {
         mockWebServer.shutdown();
 
         service.setConnectTimeout(FAILURE_TIMEOUT);
@@ -158,7 +154,7 @@ public class StandardWebClientServiceTest {
                         .retrieve()
         );
 
-        assertInstanceOf(SocketTimeoutException.class, exception.getCause());
+        assertInstanceOf(HttpTimeoutException.class, exception.getCause());
     }
 
     @Test
@@ -180,7 +176,10 @@ public class StandardWebClientServiceTest {
 
         final RecordedRequest proxyAuthorizationRequest = 
mockWebServer.takeRequest();
         final String proxyAuthorization = 
proxyAuthorizationRequest.getHeader(PROXY_AUTHORIZATION_HEADER);
-        final String credentials = Credentials.basic(username, password);
+
+        final String formatted = String.format("%s:%s", username, password);
+        final String encoded = 
Base64.getEncoder().encodeToString(formatted.getBytes(StandardCharsets.UTF_8));
+        final String credentials = String.format("Basic %s", encoded);
         assertEquals(credentials, proxyAuthorization);
     }
 
diff --git 
a/nifi-extension-bundles/nifi-opentelemetry-bundle/nifi-opentelemetry-processors/src/test/java/org/apache/nifi/processors/opentelemetry/ListenOTLPTest.java
 
b/nifi-extension-bundles/nifi-opentelemetry-bundle/nifi-opentelemetry-processors/src/test/java/org/apache/nifi/processors/opentelemetry/ListenOTLPTest.java
index 15cdb560aa..7491fb8cc2 100644
--- 
a/nifi-extension-bundles/nifi-opentelemetry-bundle/nifi-opentelemetry-processors/src/test/java/org/apache/nifi/processors/opentelemetry/ListenOTLPTest.java
+++ 
b/nifi-extension-bundles/nifi-opentelemetry-bundle/nifi-opentelemetry-processors/src/test/java/org/apache/nifi/processors/opentelemetry/ListenOTLPTest.java
@@ -43,8 +43,8 @@ import org.apache.nifi.web.client.StandardWebClientService;
 import org.apache.nifi.web.client.api.HttpEntityHeaders;
 import org.apache.nifi.web.client.api.HttpResponseEntity;
 import org.apache.nifi.web.client.api.HttpResponseStatus;
-import org.apache.nifi.web.client.api.WebClientService;
 import org.apache.nifi.web.client.ssl.TlsContext;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
@@ -136,11 +136,11 @@ class ListenOTLPTest {
 
     private static X509KeyManager keyManager;
 
+    private static StandardWebClientService webClientService;
+
     @Mock
     private SSLContextService sslContextService;
 
-    private WebClientService webClientService;
-
     private TestRunner runner;
 
     private ListenOTLP processor;
@@ -174,19 +174,13 @@ class ListenOTLPTest {
                 .keyPassword(generated)
                 .trustStore(trustStore)
                 .build();
-    }
-
-    @BeforeEach
-    void setRunner() {
-        processor = new ListenOTLP();
-        runner = TestRunners.newTestRunner(processor);
 
-        final StandardWebClientService standardWebClientService = new 
StandardWebClientService();
-        standardWebClientService.setReadTimeout(READ_TIMEOUT);
-        standardWebClientService.setConnectTimeout(CONNECT_TIMEOUT);
-        standardWebClientService.setWriteTimeout(READ_TIMEOUT);
+        webClientService = new StandardWebClientService();
+        webClientService.setReadTimeout(READ_TIMEOUT);
+        webClientService.setConnectTimeout(CONNECT_TIMEOUT);
+        webClientService.setWriteTimeout(READ_TIMEOUT);
 
-        standardWebClientService.setTlsContext(new TlsContext() {
+        webClientService.setTlsContext(new TlsContext() {
             @Override
             public String getProtocol() {
                 return TlsPlatform.getLatestProtocol();
@@ -202,8 +196,17 @@ class ListenOTLPTest {
                 return Optional.ofNullable(keyManager);
             }
         });
+    }
+
+    @AfterAll
+    static void closeWebClientService() {
+        webClientService.close();
+    }
 
-        webClientService = standardWebClientService;
+    @BeforeEach
+    void setRunner() {
+        processor = new ListenOTLP();
+        runner = TestRunners.newTestRunner(processor);
     }
 
     @AfterEach
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/main/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProvider.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/main/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProvider.java
index fc51ee8f1a..526f89b08f 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/main/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProvider.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-web-client-provider-bundle/nifi-web-client-provider-service/src/main/java/org/apache/nifi/web/client/provider/service/StandardWebClientServiceProvider.java
@@ -18,6 +18,7 @@ package org.apache.nifi.web.client.provider.service;
 
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnDisabled;
 import org.apache.nifi.annotation.lifecycle.OnEnabled;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.PropertyValue;
@@ -106,7 +107,7 @@ public class StandardWebClientServiceProvider extends 
AbstractControllerService
 
     private static final KeyManagerProvider keyManagerProvider = new 
StandardKeyManagerProvider();
 
-    private WebClientService webClientService;
+    private StandardWebClientService webClientService;
 
     @OnEnabled
     public void onEnabled(final ConfigurationContext context) {
@@ -143,6 +144,11 @@ public class StandardWebClientServiceProvider extends 
AbstractControllerService
         webClientService = standardWebClientService;
     }
 
+    @OnDisabled
+    public void onDisabled() {
+        webClientService.close();
+    }
+
     @Override
     public HttpUriBuilder getHttpUriBuilder() {
         return new StandardHttpUriBuilder();

Reply via email to