Package com.betfair.cougar.client

Source Code of com.betfair.cougar.client.AsyncHttpExecutable$JettyTransportMetrics

/*
* Copyright 2013, The Sporting Exchange Limited
*
* 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.betfair.cougar.client;

import com.betfair.cougar.client.api.GeoLocationSerializer;
import com.betfair.cougar.core.api.client.TransportMetrics;
import com.betfair.cougar.core.api.ev.ExecutionObserver;
import com.betfair.cougar.core.api.ev.OperationDefinition;
import com.betfair.cougar.core.api.exception.CougarFrameworkException;
import com.betfair.cougar.core.api.exception.ServerFaultCode;
import com.betfair.cougar.transport.api.protocol.http.HttpServiceBindingDescriptor;
import com.betfair.cougar.util.KeyStoreManagement;
import com.betfair.cougar.util.jmx.JMXControl;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jmx.export.annotation.ManagedResource;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

/**
* Implementation of client executable using async implementation of HTTP ReScript protocol.
*/
@ManagedResource
public class AsyncHttpExecutable extends AbstractHttpExecutable<Request> implements BeanNameAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientExecutable.class);

    private HttpClient client;

    private ExecutorService threadPool;
    private final ExecutorService responseThreadPool;
    private volatile boolean clientCreated;

    private String beanName;
    private JMXControl jmxControl;

    private JettyTransportMetrics metrics;
    private int maxConnectionsPerDestination;
    private int maxRequestsQueuedPerDestination;

    public AsyncHttpExecutable(HttpServiceBindingDescriptor bindingDescriptor, GeoLocationSerializer serializer,
                               ExecutorService threadPool, ExecutorService responseThreadPool) {
        this(bindingDescriptor, serializer, DEFAULT_REQUEST_UUID_HEADER, threadPool, responseThreadPool);
    }

    public AsyncHttpExecutable(HttpServiceBindingDescriptor bindingDescriptor, GeoLocationSerializer serializer,
                               String requestUUIDHeader,
                               ExecutorService threadPool, ExecutorService responseThreadPool) {
        super(bindingDescriptor, new JettyCougarRequestFactory(serializer, requestUUIDHeader));
        ((JettyCougarRequestFactory)super.requestFactory).setExecutable(this);
        this.threadPool = threadPool;
        this.responseThreadPool = responseThreadPool;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    @Override
    public void init() throws Exception {
        super.init();

        if (client == null) {
            client = new HttpClient(new SslContextFactory());
            client.setExecutor(new ExecutorThreadPool(threadPool));
            // configure timeout if set
            if (connectTimeout != -1) {
                client.setConnectTimeout(connectTimeout);
            }
            if (idleTimeout != -1) {
                client.setIdleTimeout(idleTimeout);
            }
            client.setMaxConnectionsPerDestination(maxConnectionsPerDestination);
            client.setMaxRequestsQueuedPerDestination(maxRequestsQueuedPerDestination);

            //Configure SSL - if relevant
            if (transportSSLEnabled) {
                KeyStoreManagement keyStore = KeyStoreManagement.getKeyStoreManagement(httpsKeystoreType, httpsKeystore, httpsKeyPassword);
                if (jmxControl != null && keyStore != null) {
                    jmxControl.registerMBean("CoUGAR:name=AsyncHttpClientKeyStore,beanName="+beanName, keyStore);
                }
                KeyStoreManagement trustStore = KeyStoreManagement.getKeyStoreManagement(httpsTruststoreType, httpsTruststore, httpsTrustPassword);
                if (jmxControl != null) {
                    jmxControl.registerMBean("CoUGAR:name=AsyncHttpClientTrustStore,beanName="+beanName, trustStore);
                }
                if (trustStore == null) {
                    throw new IllegalStateException("This configuration ostensibly supports TLS, yet doesn't provide valid truststore configuration");
                }

                final SslContextFactory sslContextFactory = client.getSslContextFactory();

                com.betfair.cougar.netutil.SslContextFactory factory = new com.betfair.cougar.netutil.SslContextFactory();
                factory.setTrustManagerFactoryKeyStore(trustStore.getKeyStore());
                if (keyStore != null) {
                    factory.setKeyManagerFactoryKeyStore(keyStore.getKeyStore());
                    factory.setKeyManagerFactoryKeyStorePassword(httpsKeyPassword);
                }
                SSLContext context = factory.newInstance();

                if (hostnameVerificationDisabled) {
                    context.getDefaultSSLParameters().setEndpointIdentificationAlgorithm(null);
                    LOGGER.warn("CRITICAL SECURITY CHECKS ARE DISABLED: server SSL certificate hostname " +
                            "verification is turned off.");
                }
                else {
                    context.getDefaultSSLParameters().setEndpointIdentificationAlgorithm("https");
                }

                sslContextFactory.setSslContext(context);
            }
            client.start();
            clientCreated = true;
        }

        metrics = new JettyTransportMetrics();

        if (jmxControl != null) {
            jmxControl.registerMBean("CoUGAR:name=AsyncHttpClientExecutable,beanName=" + beanName, this);
        }
    }

    public void shutdown() throws Exception {
        if (clientCreated) {
            client.stop();
        }
    }

    public void setJmxControl(JMXControl jmxControl) {
        this.jmxControl = jmxControl;
    }

    public void setMaxConnectionsPerDestination(int maxConnectionsPerDestination) {
        this.maxConnectionsPerDestination = maxConnectionsPerDestination;
    }

    public void setMaxRequestsQueuedPerDestination(int maxRequestsQueuedPerDestination) {
        this.maxRequestsQueuedPerDestination = maxRequestsQueuedPerDestination;
    }

    @Override
    protected void sendRequest(final Request request, final ExecutionObserver obs,
                               final OperationDefinition operationDefinition) {
        final String url = String.valueOf(request.getURI());
        final long startTime = System.currentTimeMillis();

        InputStreamResponseListener listener = new InputStreamResponseListener() {
            @Override
            public void onHeaders(final Response response) {
                super.onHeaders(response);

                // can only get this once, so let's makes sure..
                final InputStream inputStream = getInputStream();
                // this needs to be done in a seperate thread pool - to avoid deadlocks it needs to be the same size as or larger than the client pool
                responseThreadPool.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            processResponse(new CougarHttpResponse() {
                                @Override
                                public InputStream getEntity() throws IOException {
                                    return inputStream;
                                }

                                @Override
                                public List<String> getContentEncoding() {
                                    return new ArrayList<>(response.getHeaders().getValuesCollection(HttpHeader.CONTENT_ENCODING.asString()));
                                }

                                @Override
                                public int getResponseStatus() {
                                    return response.getStatus();
                                }

                                @Override
                                public String getServerIdentity() {
                                    return response.getHeaders().get(HttpHeader.SERVER);
                                }

                                @Override
                                public long getResponseSize() {
                                    String s = response.getHeaders().get(HttpHeader.CONTENT_LENGTH);
                                    return s != null ? Long.parseLong(s) : -1;
                                }
                            }, obs, operationDefinition);
                        }
                        catch (Exception e) {
                            LOGGER.warn("COUGAR: HTTP internal ERROR - URL [" + url + "] time [" + elapsed(startTime) + "mS]", e);
                            processException(obs, e, url);
                        }
                    }
                });
            }
        };
        request.onResponseFailure(new Response.FailureListener() {
            @Override
            public void onFailure(Response response, Throwable failure) {
                ServerFaultCode faultCode = ServerFaultCode.RemoteCougarCommunicationFailure;
                if (failure instanceof TimeoutException) {
                    failure = new CougarFrameworkException("Read timed out", failure);
                    faultCode = ServerFaultCode.Timeout;
                }
                LOGGER.warn("COUGAR: HTTP communication ERROR - URL [" + url + "] time [" + elapsed(startTime) + "mS]", failure);
                processException(obs, failure, url, faultCode);
            }
        }).onRequestFailure(new Request.FailureListener() {
            @Override
            public void onFailure(Request request, Throwable failure) {
                LOGGER.warn("COUGAR: HTTP connection FAILED - URL [" + url + "] time [" + elapsed(startTime) + " mS]", failure);
                processException(obs, failure, url);
            }
        }).send(listener);
    }

    private long elapsed(long startTime) {
        return System.currentTimeMillis() - startTime;
    }

    protected boolean gzipHandledByTransport() {
        return true;
    }

    @Override
    public TransportMetrics getTransportMetrics() {
        return metrics;
    }

    public void setClient(HttpClient client) {
        this.client = client;
    }

    //@VisibleForTesting
    HttpClient getClient() {
        return client;
    }

    final class JettyTransportMetrics implements TransportMetrics {
        private int port;
        private String host;
        private boolean https;

        private Method HttpDestination_getActiveConnections;
        private Method HttpDestination_getIdleConnections;

        JettyTransportMetrics() {
            try {
                URI uri = new URI(getRemoteAddress());
                https = "https".equalsIgnoreCase(uri.getScheme());
                port = uri.getPort() < 0 ? (https ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT) : uri.getPort();

                host = uri.getHost();
            } catch (URISyntaxException e) {
                throw new RuntimeException("Unable to determine address and port for service address '" + getRemoteAddress() + "'");//NOSONAR
            }
            try {
                HttpDestination_getActiveConnections = HttpDestination.class.getDeclaredMethod("getActiveConnections");
                HttpDestination_getActiveConnections.setAccessible(true);
                HttpDestination_getIdleConnections = HttpDestination.class.getDeclaredMethod("getIdleConnections");
                HttpDestination_getIdleConnections.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }

        }

        private HttpDestination getDestination() {
            return (HttpDestination) client.getDestination(https ? "https" : "http", host, port);
        }

        @Override
        public int getOpenConnections() {
            try {
                if (host != null) {
                    return ((BlockingQueue)HttpDestination_getActiveConnections.invoke(getDestination())).size();
                }
            } catch (InvocationTargetException | IllegalAccessException ite) {
                LOGGER.warn("Could not determine open connections for '" + getRemoteAddress() + "'");
            }

            return 0;
        }

        @Override
        public int getMaximumConnections() {
            return maxConnectionsPerDestination;
        }

        @Override
        public int getFreeConnections() {
            try {
                if (host != null) {
                    return ((BlockingQueue)HttpDestination_getIdleConnections.invoke(getDestination())).size();
                }
            } catch (InvocationTargetException | IllegalAccessException ite) {
                LOGGER.warn("Could not determine open connections for '" + getRemoteAddress() + "'");
            }
            return 0;
        }

        @Override
        public int getCurrentLoad() {
            return (getOpenConnections() - getFreeConnections()) * 100 / getMaximumConnections();
        }
    }
}
TOP

Related Classes of com.betfair.cougar.client.AsyncHttpExecutable$JettyTransportMetrics

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.