HttpClientContext context = HttpClientContext.create(); HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager(); HttpRoute route = new HttpRoute(new HttpHost("localhost", 80)); // Request new connection. This can be a long process ConnectionRequest connRequest = connMrg.requestConnection(route, null); // Wait for connection up to 10 sec HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS); try { // If not open if (!conn.isOpen()) { // establish connection based on its route info connMrg.connect(conn, route, 1000, context); // and mark it as route complete connMrg.routeComplete(conn, route, context); } // Do useful things with the connection. } finally { connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES); }如果需要的话这个连接可以被ConnectionRequest#cancel()提早中止。这将使得在ConnectionRequest#get()方法内会解除线程阻塞。
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // Increase max total connection to 200 cm.setMaxTotal(200); // Increase default max connection per route to 20 cm.setDefaultMaxPerRoute(20); // Increase max connections for localhost:80 to 50 HttpHost localhost = new HttpHost("locahost", 80); cm.setMaxPerRoute(new HttpRoute(localhost), 50); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build();
CloseableHttpClient httpClient = <...> httpClient.close();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build(); // URIs to perform GETs on String[] urisToGet = { "http://www.domain1.com/", "http://www.domain2.com/", "http://www.domain3.com/", "http://www.domain4.com/" }; // create a thread for each URI GetThread[] threads = new GetThread[urisToGet.length]; for (int i = 0; i < threads.length; i++) { HttpGet httpget = new HttpGet(urisToGet[i]); threads[i] = new GetThread(httpClient, httpget); } // start the threads for (int j = 0; j < threads.length; j++) { threads[j].start(); } // join the threads for (int j = 0; j < threads.length; j++) { threads[j].join(); }HttpClient实例是线程安全的并且可以在执行着的多线程间共享,并强烈推荐每个线程维护自己的HttpContext专用实例。
static class GetThread extends Thread { private final CloseableHttpClient httpClient; private final HttpContext context; private final HttpGet httpget; public GetThread(CloseableHttpClient httpClient, HttpGet httpget) { this.httpClient = httpClient; this.context = HttpClientContext.create(); this.httpget = httpget; } @Override public void run() { try { CloseableHttpResponse response = httpClient.execute( httpget, context); try { HttpEntity entity = response.getEntity(); } finally { response.close(); } } catch (ClientProtocolException ex) { // Handle protocol errors } catch (IOException ex) { // Handle I/O errors } } }
public static class IdleConnectionMonitorThread extends Thread { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { super(); this.connMgr = connMgr; } @Override public void run() { try { while (!shutdown) { synchronized (this) { wait(5000); // Close expired connections connMgr.closeExpiredConnections(); // Optionally, close connections // that have been idle longer than 30 sec connMgr.closeIdleConnections(30, TimeUnit.SECONDS); } } } catch (InterruptedException ex) { // terminate } } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } }
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { public long getKeepAliveDuration(HttpResponse response, HttpContext context) { // Honor 'keep-alive' header HeaderElementIterator it = new BasicHeaderElementIterator( response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout")) { try { return Long.parseLong(value) * 1000; } catch(NumberFormatException ignore) { } } } HttpHost target = (HttpHost) context.getAttribute( HttpClientContext.HTTP_TARGET_HOST); if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) { // Keep alive for 5 seconds only return 5 * 1000; } else { // otherwise keep alive for 30 seconds return 30 * 1000; } } }; CloseableHttpClient client = HttpClients.custom() .setKeepAliveStrategy(myStrategy) .build();
HTTP连接使用内部java.net.Socket对象去处理从电线传输过来的数据,然而他们依靠接口去创建、初始化和连接socket。在运行时允许HttpClient用户装备指定的socket初始化代码。PlainConnectionSocketFactory是一个默认的工作,用于创建和初始化平坦(未加密的)socket。
创建socket的过程和将它连接去一个主机是脱钩的,所以当正在一个连接操作里阻塞的时候,应该闭关掉socket。
HttpClientContext clientContext = HttpClientContext.create(); PlainConnectionSocketFactory sf = PlainConnectionSocketFactory.getSocketFactory(); Socket socket = sf.createSocket(clientContext); int timeout = 1000; //ms HttpHost target = new HttpHost("localhost"); InetSocketAddress remoteAddress = new InetSocketAddress( InetAddress.getByAddress(new byte[] {127,0,0,1}), 80); sf.connectSocket(timeout, socket, target, remoteAddress, null, clientContext);
LayeredConnectionSocketFactory 是一个ConnectionSocketFactory 接口的扩展。分层的socket工厂有能力在一个现存的平坦socket上创建分层的socket。分层的socket会首先会被代理服务使用来创建安全的socket。HttpClient包含了SSLSocketFactory,以实现SSL/TLS分层。请注意,HttpClient不会使用任何自定义的加密功能。它是完全依赖于标准的Java Cryptography (JCE) and Secure Sockets (JSEE)扩展。
ConnectionSocketFactory plainsf = <...> LayeredConnectionSocketFactory sslsf = <...> Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", plainsf) .register("https", sslsf) .build(); HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r); HttpClients.custom() .setConnectionManager(cm) .build();
HttpClient利用SSLConnectionSocketFactory来创建SSL连接。SSLConnectionSocketFactory允许高度的定制。它可以把javax.net.ssl.SSLContext的实例看作是一个参数,并用它来创建自定义的SSL连接配置。
KeyStore myTrustStore = <...> SSLContext sslContext = SSLContexts.custom() .useTLS() .loadTrustMaterial(myTrustStore) .build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
定制SSLConnectionSocketFactory要对SSL/TLS协议有更深入的掌握,这已超出了本文档的说明范围。javax.net.ssl.SSLContext详细的说明和相关的工具使用,请参考Java Secure Socket Extension(链接:http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html)。
除了在SSL/TLS协议级上托管检验和客户身份鉴定外,一旦连接被创建,HttpClient还能选择性地检验是否目标主机名与存放在服务器上的X.509证书相匹配。这个检验可以为服务器相信材料的可靠性提供额外的保证。X509HostnameVerifier 接口代表一个用于主机名检验的策略。HttpClient包含了三个 X509HostnameVerifier实现。注意:主机名检验不要被SSL托管检验给搞混淆了。
StrictHostnameVerifier(精确主机名的检验器):精确的主机名检验器工作在类似于Sun Java 1.4, Sun Java 5, Sun Java 6里。它与IE6的关系也相当紧密。这个实现符合RFC 2818,因为要处理通配符。主机名必须要么匹配第一个CN,要么匹配任意的subject-alts。通配符可以出现在CN里,和任意的subject-alts里。
BrowserCompatHostnameVerifier(浏览器兼容主机名的检验器):这个主机名检验器工作在类似于Curl和火狐浏览器里。主机名必须要么匹配第一个CN,要么匹配任意的subject-alts。通配符可以出现在CN里,和任意的subject-alts里。BrowserCompatHostnameVerifier和 StrictHostnameVerifier唯一的不同是,BrowserCompatHostnameVerifier的通配符(如"*.foo.com")会匹配所有的子域,包括"a.b.foo.com"。
AllowAllHostnameVerifier(充许所有主机名的检验器):这个主机名检验器在本质上会关掉主机检验。这个实现是无操作的(no-op),并且永远会抛出javax.net.ssl.SSLException异常。
默认的HttpClient使用BrowserCompatHostnameVerifier实现。如要需要,你可以指定不同的主机名检验器。
SSLContext sslContext = SSLContexts.createSystemDefault(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext, SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
尽管HttpClient知道复杂的路由体系和代理服务链接,但它只支持简单的定位或一个离开的跳跃(hop)代理连接。
HttpRoutePlanner routePlanner = new HttpRoutePlanner() { public HttpRoute determineRoute( HttpHost target, HttpRequest request, HttpContext context) throws HttpException { return new HttpRoute(target, null, new HttpHost("someproxy", 8080), "https".equalsIgnoreCase(target.getSchemeName())); } }; CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build(); } }
译者:lianghongge