httpclient的使用(三)
一、HTTP链接管理
HttpClient中有一个链接管理者(HttpClientConnectionManager)来管理链接,它使用工厂模式来产生新链接、管理链接的生命周期和同步对长链接的使用。其内部有个ManagedHttpClientConnection(采用代理模式)来处理真正的链接和控制I/O操作。如果链接被释放或者被使用主动关闭,底层的链接将会从代理中去掉并返回给管理者。下面是个例子来获得一个链接
1 HttpClientContext context = HttpClientContext.create(); 2 HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager(); 3 HttpRoute route = new HttpRoute(new HttpHost("localhost", 80)); 4 // 请求一个新的链接 5 ConnectionRequest connRequest = connMrg.requestConnection(route, null); 6 // 等待10s 7 HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS); 8 try { 9 // 链接没有被打开 10 if (!conn.isOpen()) { 11 // 通过路由信息建立链接 12 connMrg.connect(conn, route, 1000, context); 13 // 标记路由完成 14 connMrg.routeComplete(conn, route, context); 15 } 16 // TODO 17 } finally { 18 connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES); 19 }
二、HTTP链接池使用
在链接池有许多建立好的长链接,当需要发起请求时可以从链接池里直接获取链接,而不是马上创建一个新的链接。
1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 2 // 最大链接数为200 3 cm.setMaxTotal(200); 4 // 每个路由上的最大链接数为20 5 cm.setDefaultMaxPerRoute(20); 6 // 到目标主机的最大链接数为50 7 HttpHost host = new HttpHost("targetHost", 80); 8 cm.setMaxPerRoute(new HttpRoute(host), 50); 9 CloseableHttpClient httpClient = HttpClients.custom() 10 .setConnectionManager(cm) 11 .build();
三、多线程的请求执行
在使用多线程发起请求时,可以通过链接池来实现
1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 2 CloseableHttpClient httpClient = HttpClients.custom() 3 .setConnectionManager(cm) 4 .build(); 5 // URIs to perform GETs on 6 String[] urisToGet = { 7 "http://www.domain1.com/", 8 "http://www.domain2.com/", 9 "http://www.domain3.com/", 10 "http://www.domain4.com/" 11 }; 12 // create a thread for each URI 13 GetThread[] threads = new GetThread[urisToGet.length]; 14 for (int i = 0; i < threads.length; i++) { 15 HttpGet httpget = new HttpGet(urisToGet[i]); 16 threads[i] = new GetThread(httpClient, httpget); 17 } 18 // start the threads 19 for (int j = 0; j < threads.length; j++) { 20 threads[j].start(); 21 } 22 // join the threads 23 for (int j = 0; j < threads.length; j++) { 24 threads[j].join(); 25 } 26 static class GetThread extends Thread { 27 private final CloseableHttpClient httpClient; 28 private final HttpContext context; 29 private final HttpGet httpget; 30 public GetThread(CloseableHttpClient httpClient, HttpGet httpget) { 31 this.httpClient = httpClient; 32 this.context = HttpClientContext.create(); 33 this.httpget = httpget; 34 } 35 @Override 36 public void run() { 37 try { 38 CloseableHttpResponse response = httpClient.execute( 39 httpget, context); 40 try { 41 HttpEntity entity = response.getEntity(); 42 } finally { 43 response.close(); 44 } 45 } catch (ClientProtocolException ex) { 46 // Handle protocol errors 47 } catch (IOException ex) { 48 // Handle I/O errors 49 } 50 } 51 }
四、链接回收策略
当一个链接被释放后,这个链接有可能是新鲜的,仍能被使用,又可能是过时(服务器一端关闭了链接)的,但是链接管理者是无法知道链接状态的。一种比较可行的策略就是创建一个守护线程用户回收过期的链接。
1 public static class IdleConnectionMonitorThread extends Thread { 2 3 private final HttpClientConnectionManager connMgr; 4 private volatile boolean shutdown; 5 6 public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { 7 super(); 8 this.connMgr = connMgr; 9 } 10 @Override 11 public void run() { 12 try { 13 while (!shutdown) { 14 synchronized (this) { 15 wait(5000); 16 // Close expired connections 17 connMgr.closeExpiredConnections(); 18 // Optionally, close connections 19 // that have been idle longer than 30 sec 20 connMgr.closeIdleConnections(30, TimeUnit.SECONDS); 21 } 22 } 23 } catch (InterruptedException ex) { 24 // terminate 25 } 26 } 27 28 public void shutdown() { 29 shutdown = true; 30 synchronized (this) { 31 notifyAll(); 32 } 33 } 34 35 }
五、定制自己的Keep-Alive策略
一些HTTP服务器在应答头中虽然使用了keep-Alive,但是出于资源的考虑,可能会在一定时间之后关闭不活跃的链接但并不会告知用户。对于这种情况,我们可以采取下面的方式来定制自己的链接
1 ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { 2 public long getKeepAliveDuration(HttpResponse response, HttpContext context) { 3 // Honor 'keep-alive' header 4 HeaderElementIterator it = new BasicHeaderElementIterator( 5 response.headerIterator(HTTP.CONN_KEEP_ALIVE)); 6 while (it.hasNext()) { 7 HeaderElement he = it.nextElement(); 8 String param = he.getName(); 9 String value = he.getValue(); 10 if (value != null && param.equalsIgnoreCase("timeout")) { 11 try { 12 return Long.parseLong(value) * 1000; 13 } catch(NumberFormatException ignore) { 14 } 15 } 16 } 17 HttpHost target = (HttpHost) context.getAttribute( 18 HttpClientContext.HTTP_TARGET_HOST); 19 if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) { 20 // Keep alive for 5 seconds only 21 return 5 * 1000; 22 } else { 23 // otherwise keep alive for 30 seconds 24 return 30 * 1000; 25 } 26 } 27 }; 28 CloseableHttpClient client = HttpClients.custom() 29 .setKeepAliveStrategy(myStrategy) 30 .build();
六、定制自己的socket链接
HttpClient提供了PlainConnectionSocketFactory工厂接口,可以让用户获取一个空白链接从而定义自己的链接。
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);
七、定义自己的链接管理者
以上的套接字都是为HTTP协议服务的,如果我需要加入SSL/TSL协议的从而形成HTTPS的话,就必须对套接字再分层。HttpClient中提供了套接字分层工厂,能够对已存在套接字进行分层。下面是实例代码
1 ConnectionSocketFactory plainsf = <...> 2 LayeredConnectionSocketFactory sslsf = <...> 3 Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create() 4 .register("http", plainsf) 5 .register("https", sslsf) 6 .build(); 7 HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r); 8 HttpClients.custom() 9 .setConnectionManager(cm) 10 .build();
八、SSL/TLS的定制
通过使用HttpClient中的SSLConnectionSocketFactory创建SSL链接,但是需要使用到SSLContext(参考JSSE以及javax.net.ssl.SSLContext文档描述)
1 KeyStore myTrustStore = <...> 2 SSLContext sslContext = SSLContexts.custom() 3 .loadTrustMaterial(myTrustStore) 4 .build(); 5 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
九、代理配置
在实际应用中我们可能会用到代理来访问网页,下面三种方式介绍如何用httpClient来设置代理
方法一:最简单的方式
HttpHost proxy = new HttpHost("Host/IP", 8080); DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build();
方法二:使用JRE代理选择器
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner( ProxySelector.getDefault()); CloseableHttpClient httpclient = HttpClients.custom() .setRoutePlanner(routePlanner) .build();
方法三:自定义路由策略
1 HttpRoutePlanner routePlanner = new HttpRoutePlanner() { 2 public HttpRoute determineRoute( 3 HttpHost target, 4 HttpRequest request, 5 HttpContext context) throws HttpException { 6 return new HttpRoute(target, null, new HttpHost("someproxy", 8080), 7 "https".equalsIgnoreCase(target.getSchemeName())); 8 } 9 }; 10 CloseableHttpClient httpclient = HttpClients.custom() 11 .setRoutePlanner(routePlanner) 12 .build(); 13 } 14 }

浙公网安备 33010602011771号