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

com.turbospaces.plugins.HttpClientBootstrapInitializer Maven / Gradle / Ivy

There is a newer version: 2.0.33
Show newest version
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 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