package com.philemonworks.critter; import com.jamonapi.MonitorFactory; import com.philemonworks.critter.httpclient.NoRedirectStrategy; import com.philemonworks.critter.httpclient.ProxyRoutePlanner; import com.sun.jersey.api.core.HttpContext; import com.sun.jersey.spi.container.ContainerRequest; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.pool.PoolStats; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.net.UnknownHostException; import java.util.List; import java.util.Map.Entry; @Singleton public class HttpClient { private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class); public static final int DEFAULT_MAX_CONNECTIONS = 100; public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 100; private PoolingHttpClientConnectionManager connectionManager; protected org.apache.http.client.HttpClient httpClient; @Inject public HttpClient(ProxyRoutePlanner proxyRoutePlanner) { LOG.info("Preparing for using Http connections"); connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(DEFAULT_MAX_CONNECTIONS); connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE); //@formatter:off httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .setRedirectStrategy(new NoRedirectStrategy()) .setRoutePlanner(proxyRoutePlanner) .build(); //@formatter:on } public Response forward(HttpContext ctx, HttpRequestBase forwardRequest, URI forwardURI, byte[] requestEntityContent) { // get ContainerRequest containerRequest = (ContainerRequest) ctx.getRequest(); forwardRequest.setURI(forwardURI); if (forwardRequest instanceof HttpEntityEnclosingRequestBase) { HttpEntityEnclosingRequestBase encloser = (HttpEntityEnclosingRequestBase) forwardRequest; encloser.setEntity(new InputStreamEntity(new ByteArrayInputStream(requestEntityContent), requestEntityContent.length)); } // copy headers for (Entry<String, List<String>> each : containerRequest.getRequestHeaders().entrySet()) { // omit proxy headers if ("Proxy-Connection.Host.Content-Length".indexOf(each.getKey()) == -1) { for (String other : each.getValue()) { LOG.trace("Forward copy header:{}", each); forwardRequest.addHeader(each.getKey(), other); } } else { LOG.trace("Skip forward header:{}", each); } } HttpResponse forwardResponse = null; try { forwardResponse = httpClient.execute(forwardRequest); } catch (UnknownHostException uhe) { return Response.status(404).entity("Unknown host: " + uhe.getMessage()).build(); } catch (Exception ex) { LOG.error(forwardRequest.getRequestLine().toString()); // not the whole stack LOG.trace(forwardRequest.getRequestLine().toString(), ex); // with the whole stack return Response.serverError().entity(ex.getMessage()).build(); } Response.ResponseBuilder containerResponse = Response.status(forwardResponse.getStatusLine().getStatusCode()); // copy headers for (Header each : forwardResponse.getAllHeaders()) { if ("Transfer-Encoding".indexOf(each.getName()) == -1) { LOG.trace("Backward copy header:" + each.toString()); containerResponse.header(each.getName(), each.getValue()); } else { LOG.trace("Skip backward header:" + each.toString()); } } try { if (forwardResponse.getEntity() != null) { containerResponse.entity(forwardResponse.getEntity().getContent()); } return containerResponse.build(); } catch (Exception ex) { try { EntityUtils.consume(forwardResponse.getEntity()); } catch (IOException ioex) { LOG.error("Consuming content from response failed", ioex); } LOG.error("Reading response failed", ex); return Response.serverError().entity(ex.getMessage()).build(); } finally { PoolStats totalStats = connectionManager.getTotalStats(); LOG.trace("Connections in pool:{}", totalStats); MonitorFactory.add("--critter.http.pool.size", "count", (totalStats.getAvailable() + totalStats.getLeased()) * 1.0d); } } }