com.turbospaces.plugins.HttpClientBootstrapInitializer Maven / Gradle / Ivy
package com.turbospaces.plugins;
import com.google.common.net.HostAndPort;
import com.netflix.archaius.api.Property;
import com.turbospaces.boot.AbstractBootstrapAware;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.SSL;
import com.turbospaces.http.HttpClientDisposableBean;
import com.turbospaces.http.MockHttpProxyConfiguration;
import com.turbospaces.http.ProxyConfiguration;
import com.turbospaces.ups.PlainServiceInfo;
import io.micrometer.core.instrument.binder.httpcomponents.MicrometerHttpRequestExecutor;
import io.micrometer.core.instrument.binder.httpcomponents.PoolingHttpClientConnectionManagerMetricsBinder;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.BootstrapContextClosedEvent;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.SmartApplicationListener;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
public class HttpClientBootstrapInitializer extends AbstractBootstrapAware implements BootstrapRegistryInitializer {
private final Set unsafe = new LinkedHashSet<>();
private HttpClientBuilder builder;
public HttpClientBootstrapInitializer() throws Exception {
this( Collections.emptyList() );
}
public HttpClientBootstrapInitializer(Collection upss) throws Exception {
if ( upss != null ) {
for ( PlainServiceInfo ups : upss ) {
HostAndPort hap = HostAndPort.fromHost( ups.getHost() );
if ( ups.getPort() > 0 ) {
hap = hap.withDefaultPort( ups.getPort() );
}
unsafe.add( hap );
}
}
}
@Override
public void initialize(BootstrapRegistry registry) {
builder = new HttpClientBuilder() {
@Override
protected ClientExecChain decorateProtocolExec(final ClientExecChain protocolExec) {
return (route, request, clientContext, execAware) -> {
try {
Optional mockProxy = MockHttpProxyConfiguration.proxyRequest(bootstrap.props(), request.getRequestLine().getUri());
if (mockProxy.isPresent()) {
HttpUriRequest proxyReq = RequestBuilder.copy(request.getOriginal()).setUri(mockProxy.get()).build();
URI proxyUri = new URI(bootstrap.props().MOCK_HTTP_PROXY.get());
HttpRoute proxyRoute = new HttpRoute(new HttpHost(proxyUri.getHost(), proxyUri.getPort(), proxyUri.getScheme()));
return protocolExec.execute(proxyRoute, HttpRequestWrapper.wrap(proxyReq), clientContext, execAware);
}
Optional proxy = ProxyConfiguration.proxyRequest(bootstrap.props(), request.getRequestLine().getUri());
if (proxy.isPresent()) {
HttpUriRequest proxyReq = RequestBuilder.copy(request.getOriginal()).setUri(proxy.get()).build();
URI proxyUri = new URI(bootstrap.props().HTTP_PROXY.get());
HttpRoute proxyRoute = new HttpRoute(new HttpHost(proxyUri.getHost(), proxyUri.getPort(), proxyUri.getScheme()));
return protocolExec.execute(proxyRoute, HttpRequestWrapper.wrap(proxyReq), clientContext, execAware);
}
} catch (Exception e) {
logger.error("Failed to proxy request", e);
}
return protocolExec.execute(route, HttpRequestWrapper.wrap(request), clientContext, execAware);
};
}
};
// ~ metrics
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setDefaultMaxPerRoute( bootstrap.props().TCP_POOL_MAX_PER_ROUTE_SIZE.get() );
connectionManager.setMaxTotal( bootstrap.props().TCP_POOL_MAX_SIZE.get() );
connectionManager.setDefaultSocketConfig( socketConfig( bootstrap.props() ).build() );
builder.setConnectionManager(connectionManager)
.setRequestExecutor(MicrometerHttpRequestExecutor.builder(bootstrap.meterRegistry()).uriMapper(this::uriMapper).build());
new PoolingHttpClientConnectionManagerMetricsBinder(connectionManager, "httpclient-pool").bindTo(bootstrap.meterRegistry());
// ~ only in DEV mode
if ( bootstrap.isDevMode() ) {
if ( bootstrap.props().APP_BEHIND_PROXY.get() ) {
SSL ssl = new SSL();
ssl.addUntrustedCertificates( unsafe );
SSLContext sslContext = ssl.build();
builder.setSSLContext( sslContext );
builder.setSSLHostnameVerifier( new NoopHostnameVerifier() );
}
}
builder.setKeepAliveStrategy( keepAliveStrategy( bootstrap.props() ) );
builder.setDefaultRequestConfig( requestConfig( bootstrap.props() ).build() );
CloseableHttpClient httpClient = builder.build();
registry.register( CloseableHttpClient.class, InstanceSupplier.of( httpClient ) );
registry.addCloseListener( new ApplicationListener() {
@Override
public void onApplicationEvent(BootstrapContextClosedEvent event) {
ConfigurableApplicationContext applicationContext = event.getApplicationContext();
applicationContext.addApplicationListener( new SmartApplicationListener() {
@Override
public void onApplicationEvent(ApplicationEvent e) {
try {
httpClient.close();
}
catch ( IOException err ) {
logger.error( err.getMessage(), err );
}
}
@Override
public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
return eventType.equals( ApplicationFailedEvent.class );
}
} );
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.registerSingleton( "http-client", httpClient );
// ~ shutdown gracefully
GenericBeanDefinition bean = new GenericBeanDefinition();
bean.setBeanClass( HttpClientDisposableBean.class );
bean.setAutowireCandidate( true );
beanFactory.registerBeanDefinition( "http-client-lifecycle", bean );
}
} );
}
private String uriMapper(HttpRequest httpRequest) {
String uri = httpRequest.getRequestLine().getUri();
try {
Optional mapping = bootstrap.props().METRICS_HTTP_OUT_PATH_MAPPINGS.get().stream()
.filter(p -> p.asPredicate().test(uri))
.map(Pattern::pattern)
.findAny();
if (mapping.isPresent()) {
return mapping.get();
}
return new URI(uri).getPath();
} catch (URISyntaxException e) {
logger.error("Can't parse URI {}", uri, e);
}
return "UNKNOWN";
}
public static DefaultConnectionKeepAliveStrategy keepAliveStrategy(ApplicationProperties props) {
return new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
long keepAliveDuration = super.getKeepAliveDuration( response, context );
if ( keepAliveDuration <= 0 ) {
keepAliveDuration = TimeUnit.SECONDS.toMillis( props.TCP_KEEP_ALIVE_TIMEOUT.get() );
}
return keepAliveDuration;
}
};
}
public static RequestConfig.Builder requestConfig(ApplicationProperties props) {
return requestConfig( props.TCP_CONNECTION_TIMEOUT, props.TCP_SOCKET_TIMEOUT );
}
public static RequestConfig.Builder requestConfig(Property connectTimeout, Property soTimeout) {
int connectionTimeout = (int) TimeUnit.SECONDS.toMillis( connectTimeout.get() );
int socketTimeout = (int) TimeUnit.SECONDS.toMillis( soTimeout.get() );
RequestConfig.Builder requestCfg = RequestConfig.custom();
requestCfg.setConnectTimeout( connectionTimeout );
requestCfg.setSocketTimeout( socketTimeout );
requestCfg.setCookieSpec( CookieSpecs.STANDARD );
return requestCfg;
}
public static SocketConfig.Builder socketConfig(ApplicationProperties props) {
int socketTimeout = (int) TimeUnit.SECONDS.toMillis( props.TCP_SOCKET_TIMEOUT.get() );
SocketConfig.Builder socketCfg = SocketConfig.custom();
socketCfg.setSoKeepAlive( props.TCP_KEEP_ALIVE.get() );
socketCfg.setSoReuseAddress( props.TCP_REUSE_ADDRESS.get() );
socketCfg.setTcpNoDelay( props.TCP_NODELAY.get() );
socketCfg.setBacklogSize( props.TCP_SOCKET_BACKLOG.get() );
socketCfg.setSoTimeout( socketTimeout );
return socketCfg;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy