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 }
View Code

 四、链接回收策略


当一个链接被释放后,这个链接有可能是新鲜的,仍能被使用,又可能是过时(服务器一端关闭了链接)的,但是链接管理者是无法知道链接状态的。一种比较可行的策略就是创建一个守护线程用户回收过期的链接。

 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);
View Code

 九、代理配置


在实际应用中我们可能会用到代理来访问网页,下面三种方式介绍如何用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 }

 

 

posted @ 2016-03-01 14:27  被罚站的树  阅读(184)  评论(0)    收藏  举报