All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jsunsoft.http.ClientBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2024. Benik Arakelyan
 *
 * 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.
 */

package com.jsunsoft.http;

import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.RedirectStrategy;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.net.ProxySelector;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.function.Consumer;

import static org.apache.hc.core5.http.HttpHeaders.CONTENT_TYPE;


/**
 * Builder for {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient}.
 * 

* HttpClients are heavy-weight objects that manage the client-side communication infrastructure. * Initialization as well as disposal of a {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient} instance may be a rather expensive operation. * It is therefore advised to construct only a small number of {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient} instances in the application. */ public class ClientBuilder { private final RequestConfig.Builder defaultRequestConfigBuilder = RequestConfig.custom() .setResponseTimeout(Timeout.ofSeconds(30)) .setConnectionRequestTimeout(Timeout.ofSeconds(30)); private final ConnectionConfig.Builder defaultConnectionConfigBuilder = ConnectionConfig.custom() .setConnectTimeout(Timeout.ofSeconds(10)) .setSocketTimeout(Timeout.ofSeconds(30)); private final HostPoolConfig hostPoolConfig = HostPoolConfig.create(); private RedirectStrategy redirectStrategy; private Collection> httpClientBuilderCustomizers; private Collection> defaultRequestConfigBuilderCustomizers; private Collection> defaultConnectionConfigBuilderCustomizers; private Collection

defaultHeaders; private HttpHost proxy; private boolean useDefaultProxy; private SSLConnectionSocketFactoryBuilder sslConnectionSocketFactoryBuilder; ClientBuilder() { } public static ClientBuilder create() { return new ClientBuilder(); } /** * Determines the timeout in milliseconds until a connection is established. * A timeout value of zero is interpreted as an infinite timeout. *

* A timeout value of zero is interpreted as an infinite timeout. * A negative value is interpreted as undefined (system default). *

*

* Default: {@code 10 seconds} *

* Note: Can be overridden by {@linkplain #addDefaultConnectionConfigCustomizer} * * @param connectTimeout The Connection Timeout (http.connection.timeout) – the time to establish the connection with the remote host. * @return ClientBuilder instance * @see org.apache.hc.client5.http.config.ConnectionConfig.Builder#setConnectTimeout(Timeout) */ public ClientBuilder setConnectTimeout(Timeout connectTimeout) { defaultConnectionConfigBuilder.setConnectTimeout(connectTimeout); return this; } /** * @param connectTimeout The Connection Timeout (http.connection.timeout) in milliseconds – to establish the connection with the remote host. * @return ClientBuilder instance * @see #setConnectTimeout(Timeout) */ public ClientBuilder setConnectTimeout(int connectTimeout) { return setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)); } /** * Determines the timeout until arrival of a response from the opposite * endpoint. *

* A timeout value of zero is interpreted as an infinite timeout. *

*

* Please note that response timeout may be unsupported by * HTTP transports with message multiplexing. *

*

* Default: {@code 30 seconds} *

* Note: Can be overridden by {@linkplain #addDefaultRequestConfigCustomizer} * * @param responseTimeout The timeout waiting for data – after the connection was established. * @return ClientBuilder instance * @see org.apache.hc.client5.http.config.RequestConfig.Builder#setResponseTimeout(Timeout) */ public ClientBuilder setResponseTimeout(Timeout responseTimeout) { defaultRequestConfigBuilder.setResponseTimeout(responseTimeout); return this; } /** * @param responseTimeout The timeout in milliseconds waiting for data – after the connection was established. * @return ClientBuilder instance * @see #setResponseTimeout(Timeout) */ public ClientBuilder setResponseTimeout(int responseTimeout) { return setResponseTimeout(Timeout.ofMilliseconds(responseTimeout)); } /** * The timeout in milliseconds used when requesting a connection * from the connection manager. A timeout value of zero is interpreted * as an infinite timeout. *

* A timeout value of zero is interpreted as an infinite timeout. * A negative value is interpreted as undefined (system default). *

*

* Default: {@code 30 seconds} *

*

* Note: Can be overridden by {@linkplain #addDefaultRequestConfigCustomizer} * * @param connectionRequestTimeout The Connection Manager Timeout (http.connection-manager.timeout) – * the time to wait for a connection from the connection manager/pool. * @return ClientBuilder instance * @see org.apache.hc.client5.http.config.RequestConfig.Builder#setConnectionRequestTimeout * @see #addDefaultRequestConfigCustomizer */ public ClientBuilder setConnectionRequestTimeout(Timeout connectionRequestTimeout) { defaultRequestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout); return this; } /** * @param connectionRequestTimeout The Connection Manager Timeout (http.connection-manager.timeout) in milliseconds – * the time to wait for a connection from the connection manager/pool. * @return ClientBuilder instance * @see #setConnectionRequestTimeout */ public ClientBuilder setConnectionRequestTimeout(int connectionRequestTimeout) { return setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeout)); } /** * @param defaultRequestConfigBuilderConsumer the consumer instance which provides {@link org.apache.hc.client5.http.config.RequestConfig.Builder} to customize default request config * @return ClientBuilder instance */ public ClientBuilder addDefaultRequestConfigCustomizer(Consumer defaultRequestConfigBuilderConsumer) { if (defaultRequestConfigBuilderCustomizers == null) { defaultRequestConfigBuilderCustomizers = new LinkedHashSet<>(); } defaultRequestConfigBuilderCustomizers.add(defaultRequestConfigBuilderConsumer); return this; } /** * Defines the socket timeout ({@code SO_TIMEOUT}) in milliseconds, * which is the timeout for waiting for data or, put differently, * a maximum period inactivity between two consecutive data packets. *

* A timeout value of zero is interpreted as an infinite timeout. * A negative value is interpreted as undefined (system default). *

*

* Default: {@code 30000ms} *

* Note: Can be overridden by {@linkplain #addDefaultConnectionConfigCustomizer(Consumer)} * * @param socketTimeOut The Socket Timeout (http.socket.timeout) – the time waiting for data – after the connection was established; * maximum time of inactivity between two data packets. * @return ClientBuilder instance * @see org.apache.hc.client5.http.config.ConnectionConfig.Builder#setSocketTimeout * @see #addDefaultConnectionConfigCustomizer(Consumer) */ public ClientBuilder setSocketTimeout(Timeout socketTimeOut) { defaultConnectionConfigBuilder.setSocketTimeout(socketTimeOut); return this; } /** * @param socketTimeOutMillis The Socket Timeout (http.socket.timeout) in milliseconds – the time waiting for data – * after the connection was established; * @return ClientBuilder instance * @see #setSocketTimeout */ public ClientBuilder setSocketTimeout(int socketTimeOutMillis) { setSocketTimeout(Timeout.ofMilliseconds(socketTimeOutMillis)); return this; } /** * @param defaultConnectionConfigBuilderConsumer the consumer instance * which provides * {@link org.apache.hc.client5.http.config.ConnectionConfig.Builder} to customize default request config * @return ClientBuilder instance */ public ClientBuilder addDefaultConnectionConfigCustomizer(Consumer defaultConnectionConfigBuilderConsumer) { if (defaultConnectionConfigBuilderCustomizers == null) { defaultConnectionConfigBuilderCustomizers = new LinkedHashSet<>(); } defaultConnectionConfigBuilderCustomizers.add(defaultConnectionConfigBuilderConsumer); return this; } /** * The method takes the {@link java.util.function.Consumer} instance * which gives the {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} instance to customize * the {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient} before the http-request is built * * @param httpClientCustomizer consumer instance * @return ClientBuilder instance */ public ClientBuilder addHttpClientCustomizer(Consumer httpClientCustomizer) { if (httpClientBuilderCustomizers == null) { httpClientBuilderCustomizers = new LinkedHashSet<>(); } httpClientBuilderCustomizers.add(httpClientCustomizer); return this; } /** * @param maxPoolSize see documentation of {@link com.jsunsoft.http.HostPoolConfig#setMaxPoolSize(int)} * @return ClientBuilder instance */ public ClientBuilder setMaxPoolSize(int maxPoolSize) { this.hostPoolConfig.setMaxPoolSize(maxPoolSize); return this; } /** * @param defaultMaxPoolSizePerRoute see documentation of {@link com.jsunsoft.http.HostPoolConfig#setDefaultMaxPoolSizePerRoute(int)} * @return ClientBuilder instance */ public ClientBuilder setDefaultMaxPoolSizePerRoute(int defaultMaxPoolSizePerRoute) { this.hostPoolConfig.setDefaultMaxPoolSizePerRoute(defaultMaxPoolSizePerRoute); return this; } /** * Set the connection pool default max size of concurrent connections to a specific route * * @param httpHost httpHost * @param maxRoutePoolSize maxRoutePoolSize * @return ClientBuilder instance */ public ClientBuilder setMaxPoolSizePerRoute(HttpHost httpHost, int maxRoutePoolSize) { hostPoolConfig.setMaxPoolSizePerRoute(httpHost, maxRoutePoolSize); return this; } /** * @return ClientBuilder instance * @see org.apache.hc.client5.http.impl.DefaultRedirectStrategy */ public ClientBuilder enableDefaultRedirectStrategy() { this.redirectStrategy = DefaultRedirectStrategy.INSTANCE; return this; } /** *

By default disabled. * * @param redirectStrategy RedirectStrategy instance * @return ClientBuilder instance * @see org.apache.hc.client5.http.protocol.RedirectStrategy */ public ClientBuilder redirectStrategy(RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; return this; } /** * Header needs to be the same for all requests which go through the built CloseableHttpClient * * @param name name of header. Can't be null * @param value value of header * @return ClientBuilder instance */ public ClientBuilder addDefaultHeader(String name, String value) { ArgsCheck.notNull(name, "name"); addDefaultHeader(new BasicHeader(name, value)); return this; } /** * Header needs to be the same for all requests which go through the built CloseableHttpClient * * @param header header instance. Can't be null * @return ClientBuilder instance */ public ClientBuilder addDefaultHeader(Header header) { ArgsCheck.notNull(header, "header"); if (defaultHeaders == null) { defaultHeaders = new ArrayList<>(); } defaultHeaders.add(new BasicHeader(header.getName(), header.getValue())); return this; } /** * Headers need to be the same for all requests which go through the built CloseableHttpClient * * @param headers varargs of headers. Can't be null * @return ClientBuilder instance */ public ClientBuilder addDefaultHeaders(Header... headers) { ArgsCheck.notNull(headers, "headers"); Arrays.stream(headers).forEach(this::addDefaultHeader); return this; } /** * Headers need to be the same for all requests which go through the built CloseableHttpClient * * @param headers collections of headers * @return ClientBuilder instance */ public ClientBuilder addDefaultHeaders(Collection headers) { ArgsCheck.notNull(headers, "headers"); headers.forEach(this::addDefaultHeader); return this; } /** * Sets content type to header * * @param contentType content type of request header * @return ClientBuilder instance */ public ClientBuilder addContentType(ContentType contentType) { addDefaultHeader(CONTENT_TYPE, contentType.toString()); return this; } /** * Added proxy host. By default is null. * If has proxy instance method {@link #useDefaultProxy()} will be ignored * * @param proxy {@link org.apache.hc.core5.http.HttpHost} instance to proxy * @return ClientBuilder instance */ public ClientBuilder proxy(HttpHost proxy) { this.proxy = proxy; return this; } /** * Added proxy by proxyUri. By default is null. * If has proxy instance method {@link #useDefaultProxy()} will be ignored. * * @param proxyUri {@link java.net.URI} instance to proxy * @return ClientBuilder instance */ public ClientBuilder proxy(URI proxyUri) { return proxy( new HttpHost( proxyUri.getScheme(), proxyUri.getHost(), proxyUri.getPort() ) ); } /** * @param host host of proxy * @param port port of proxy * @return ClientBuilder instance */ public ClientBuilder proxy(String host, int port) { return proxy(new HttpHost(host, port)); } /** * Instruct HttpClient to use the standard JRE proxy selector to obtain proxy. * * @return ClientBuilder instance */ public ClientBuilder useDefaultProxy() { useDefaultProxy = true; return this; } /** * Sets {@link javax.net.ssl.SSLContext} * * @param sslContext SSLContext instance * @return ClientBuilder instance */ public ClientBuilder sslContext(SSLContext sslContext) { initializeSslConnectionSocketFactoryBuilder(); sslConnectionSocketFactoryBuilder.setSslContext(sslContext); return this; } /** * Sets {@link javax.net.ssl.HostnameVerifier} * * @param hostnameVerifier HostnameVerifier instance * @return ClientBuilder instance */ public ClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) { initializeSslConnectionSocketFactoryBuilder(); sslConnectionSocketFactoryBuilder.setHostnameVerifier(hostnameVerifier); return this; } /** * Accept all certificates * * @return ClientBuilder instance * @throws HttpRequestBuildException when can't build ssl. */ public ClientBuilder trustAllCertificates() { initializeSslConnectionSocketFactoryBuilder(); try { SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build(); sslConnectionSocketFactoryBuilder.setSslContext(sslContext); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { throw new HttpRequestBuildException(e); } return this; } /** * Accept all hosts * * @return ClientBuilder instance */ public ClientBuilder trustAllHosts() { initializeSslConnectionSocketFactoryBuilder(); sslConnectionSocketFactoryBuilder.setHostnameVerifier(NoopHostnameVerifier.INSTANCE); return this; } /** * Build CloseableHttpClient * * @return {@link org.apache.hc.client5.http.impl.classic.CloseableHttpClient} instance by build parameters */ public CloseableHttpClient build() { return buildClientWithContext().getClient(); } ClientContextHolder buildClientWithContext() { if (defaultRequestConfigBuilderCustomizers != null) { defaultRequestConfigBuilderCustomizers.forEach(defaultRequestConfigBuilderConsumer -> defaultRequestConfigBuilderConsumer.accept(defaultRequestConfigBuilder)); } RequestConfig requestConfig = defaultRequestConfigBuilder.build(); if (defaultConnectionConfigBuilderCustomizers != null) { defaultConnectionConfigBuilderCustomizers.forEach(defaultrequestconfigbuilderconsumer -> defaultrequestconfigbuilderconsumer.accept(defaultConnectionConfigBuilder)); } ConnectionConfig connectionConfig = defaultConnectionConfigBuilder.build(); PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create(); if (sslConnectionSocketFactoryBuilder != null) { cmBuilder.setSSLSocketFactory(sslConnectionSocketFactoryBuilder.build()); } PoolingHttpClientConnectionManager connectionManager = cmBuilder .setMaxConnPerRoute(hostPoolConfig.getDefaultMaxPoolSizePerRoute()) .setMaxConnTotal(hostPoolConfig.getMaxPoolSize()) .setDefaultConnectionConfig(connectionConfig) .build(); hostPoolConfig.getHttpHostToMaxPoolSize().forEach((httpHost, maxPerRoute) -> { HttpRoute httpRoute = new HttpRoute(httpHost); connectionManager.setMaxPerRoute(httpRoute, maxPerRoute); }); HttpRoutePlanner routePlanner = null; if (proxy != null) { routePlanner = new DefaultProxyRoutePlanner(proxy); } else if (useDefaultProxy) { routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault()); } HttpClientBuilder clientBuilder = HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .disableCookieManagement() .disableAutomaticRetries(); if (routePlanner != null) { clientBuilder.setRoutePlanner(routePlanner); } if (defaultHeaders != null && !defaultHeaders.isEmpty()) { clientBuilder.setDefaultHeaders(defaultHeaders); } if (redirectStrategy == null) { clientBuilder.disableRedirectHandling(); } else { clientBuilder.setRedirectStrategy(redirectStrategy); } if (httpClientBuilderCustomizers != null) { httpClientBuilderCustomizers.forEach(httpClientBuilderConsumer -> httpClientBuilderConsumer.accept(clientBuilder)); } return new ClientContextHolder( clientBuilder.build(), connectionManager ); } private void initializeSslConnectionSocketFactoryBuilder() { if (sslConnectionSocketFactoryBuilder == null) { sslConnectionSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder.create(); } } static class ClientContextHolder { private final CloseableHttpClient client; private final HttpClientConnectionManager connectionManager; ClientContextHolder(CloseableHttpClient client, HttpClientConnectionManager connectionManager) { this.client = client; this.connectionManager = connectionManager; } public CloseableHttpClient getClient() { return client; } public HttpClientConnectionManager getConnectionManager() { return connectionManager; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy