/* * Copyright 2014, The Sporting Exchange Limited * Copyright 2015, Simon Matić Langford * * 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.ContextEmitter; 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.tracing.Tracer; import com.betfair.cougar.transport.api.protocol.http.HttpServiceBindingDescriptor; import com.betfair.cougar.util.KeyStoreManagement; import com.betfair.cougar.util.jmx.JMXControl; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.UserTokenHandler; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.jmx.export.annotation.ManagedResource; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; import java.util.List; import java.util.Locale; /** * Implementation of client executable using synchronous implementation of HTTP ReScript protocol. */ @ManagedResource public class HttpClientExecutable extends AbstractHttpExecutable<HttpUriRequest> implements BeanNameAware { private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientExecutable.class); private HttpClient client; private HttpRequestRetryHandler retryHandler; private CougarClientConnManager clientConnectionManager; private String beanName; private JMXControl jmxControl; private UserTokenHandler userTokenHandler; private HttpClientTransportMetrics metrics; public HttpClientExecutable(final HttpServiceBindingDescriptor bindingDescriptor, final ContextEmitter emission, final Tracer tracer) { this(bindingDescriptor, emission, tracer, new CougarClientConnManager()); } public HttpClientExecutable(final HttpServiceBindingDescriptor bindingDescriptor, ContextEmitter emission, Tracer tracer, CougarClientConnManager clientConnectionManager) { super(bindingDescriptor, new HttpClientCougarRequestFactory(emission), tracer); this.clientConnectionManager = clientConnectionManager; } @Override public void setBeanName(String name) { this.beanName = name; } public void init() throws Exception { super.init(); // create client if not been set externally (e.g for testing) if (client == null) { client = new DefaultHttpClient(clientConnectionManager); ((DefaultHttpClient)client).setUserTokenHandler(userTokenHandler); } // configure retryhandler if set if (retryHandler != null) { ((AbstractHttpClient) client).setHttpRequestRetryHandler(retryHandler); } // configure timeout if set if (connectTimeout != -1) { HttpParams params = client.getParams(); HttpConnectionParams.setConnectionTimeout(params, connectTimeout); HttpConnectionParams.setSoTimeout(params, connectTimeout); } //Configure SSL - if relevant if (transportSSLEnabled) { KeyStoreManagement keyStore = KeyStoreManagement.getKeyStoreManagement(httpsKeystoreType, httpsKeystore, httpsKeyPassword); if (jmxControl != null && keyStore != null) { jmxControl.registerMBean("CoUGAR:name=HttpClientKeyStore,beanName="+beanName, keyStore); } KeyStoreManagement trustStore = KeyStoreManagement.getKeyStoreManagement(httpsTruststoreType, httpsTruststore, httpsTrustPassword); if (jmxControl != null) { jmxControl.registerMBean("CoUGAR:name=HttpClientTrustStore,beanName="+beanName, trustStore); } SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore != null ? keyStore.getKeyStore() : null, keyStore != null ? httpsKeyPassword : null, trustStore.getKeyStore()); if (hostnameVerificationDisabled) { socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); LOGGER.warn("CRITICAL SECURITY CHECKS ARE DISABLED: server SSL certificate hostname " + "verification is turned off."); } Scheme sch = new Scheme("https", extractPortFromAddress(), socketFactory); client.getConnectionManager().getSchemeRegistry().register(sch); } metrics = new HttpClientTransportMetrics(); if (jmxControl != null) { jmxControl.registerMBean("CoUGAR:name=HttpClientExecutable,beanName="+beanName, this); } } @Override protected void sendRequest(HttpUriRequest httpMethod, ExecutionObserver obs, OperationDefinition operationDefinition) { try { final HttpResponse response = client.execute(httpMethod); if (LOGGER.isDebugEnabled()) { final int statusCode = response.getStatusLine().getStatusCode(); LOGGER.debug("Received http response code of " + statusCode + " in reply to request to " + httpMethod.getURI()); } processResponse(new CougarHttpResponse(response), obs, operationDefinition); } catch (Exception e) { processException(obs, e, httpMethod.getURI().toString()); } } public void setRetryHandler(final HttpRequestRetryHandler retryHandler) { this.retryHandler = retryHandler; } public void setClient(final HttpClient client) { this.client = client; } public void setJmxControl(JMXControl jmxControl) { this.jmxControl = jmxControl; } public void setUserTokenHandler(UserTokenHandler userTokenHandler) { this.userTokenHandler = userTokenHandler; } private static final class CougarHttpResponse implements AbstractHttpExecutable.CougarHttpResponse { private final HttpResponse delegate; private CougarHttpResponse(HttpResponse delegate) { this.delegate = delegate; } @Override public InputStream getEntity() throws IOException { return delegate.getEntity() == null ? null : delegate.getEntity().getContent(); } @Override public List<String> getContentEncoding() { List<String> codecList = new LinkedList<String>(); Header ceheader = delegate.getEntity().getContentEncoding(); if (ceheader != null) { HeaderElement[] codecs = ceheader.getElements(); for (HeaderElement codec : codecs) { codecList.add(codec.getName().toLowerCase(Locale.US)); } } return codecList; } @Override public int getResponseStatus() { return delegate.getStatusLine().getStatusCode(); } @Override public String getServerIdentity() { if (delegate.containsHeader("Server")) { return "" + delegate.getFirstHeader("Server").getValue(); } return null; } @Override public long getResponseSize() { if (delegate.getEntity() != null) { return delegate.getEntity().getContentLength(); } return 0; } } @Override public TransportMetrics getTransportMetrics() { return metrics; } public void setMaxTotalConnections(int maxTotalConnections) { clientConnectionManager.setMaxTotal(maxTotalConnections); } public void setMaxPerRouteConnections(int maxPerRouteConnections) { clientConnectionManager.setDefaultMaxPerRoute(maxPerRouteConnections); } final class HttpClientTransportMetrics implements TransportMetrics { @Override public int getOpenConnections() { return clientConnectionManager.getConnectionsInPool(); } @Override public int getMaximumConnections() { return clientConnectionManager.getDefaultMaxPerRoute(); } @Override public int getFreeConnections() { return clientConnectionManager.getFreeConnections(); } @Override public int getCurrentLoad() { return (getOpenConnections() - getFreeConnections()) * 100 / getMaximumConnections(); } } }