HttpClient 4.3详细教程之高级主题

来源:互联网 发布:js 保存图片到手机 编辑:程序博客网 时间:2024/04/30 19:49

7.1 自定义客户端连接

在特定条件下,也许需要来定制HTTP报文通过线路传递,越过了可能使用的HTTP参数来处理非标准不兼容行为的方式。比如,对于Web爬虫,它可能需要强制HttpClient接受格式错误的响应头部信息,来抢救报文的内容。

通常插入一个自定义的报文解析器的过程或定制连接实现需要几个步骤:

提供一个自定义LineParser/LineFormatter接口实现。如果需要,实现报文解析/格式化逻辑。


[java] view plaincopy
  1. <span style="font-family:SimSun;">class MyLineParser extends BasicLineParser {  
  2.   
  3.     @Override  
  4.     public Header parseHeader(  
  5.             CharArrayBuffer buffer) throws ParseException {  
  6.         try {  
  7.             return super.parseHeader(buffer);  
  8.         } catch (ParseException ex) {  
  9.             // Suppress ParseException exception  
  10.             return new BasicHeader(buffer.toString(), null);  
  11.         }  
  12.     }  
  13.   
  14. }</span>  

提过一个自定义的 HttpConnectionFactory 实现。替换需要自定义的默认请求/响应解析器,请求/响应格式化器。如果需要,实现不同的报文写入/读取代码。


[java] view plaincopy
  1. <span style="font-family:SimSun;">HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =  
  2.         new ManagedHttpClientConnectionFactory(  
  3.             new DefaultHttpRequestWriterFactory(),  
  4.             new DefaultHttpResponseParserFactory(  
  5.                     new MyLineParser(), new DefaultHttpResponseFactory()));</span>  

为了创建新类的连接,提供一个自定义的ClientConnectionOperator接口实现。如果需要,实现不同的套接字初始化代码。

[java] view plaincopy
  1. <span style="font-family:SimSun;">PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(  
  2.     connFactory);  
  3. CloseableHttpClient httpclient = HttpClients.custom()  
  4.         .setConnectionManager(cm)  
  5.         .build();</span>  

7.2 有状态的HTTP连接

HTTP规范假设session状态信息通常是以HTTP cookie格式嵌入在HTTP报文中的,因此HTTP连接通常是无状态的,这个假设在现实生活中通常是不对的。也有一些情况,当HTTP连接使用特定的用户标识或特定的安全上下文来创建时,因此不能和其它用户共享,只能由该用户重用。这样的有状态的HTTP连接的示例就是NTLM认证连接和使用客户端证书认证的SSL连接。

7.2.1 用户令牌处理器

HttpClient依赖UserTokenHandler接口来决定给定的执行上下文是否是用户指定的。如果这个上下文是用户指定的或者如果上下文没有包含任何资源或关于当前用户指定详情而是null,令牌对象由这个处理器返回,期望唯一地标识当前的用户。用户令牌将被用来保证用户指定资源不会和其它用户来共享或重用。

如果它可以从给定的执行上下文中来获得,UserTokenHandler接口的默认实现是使用主类的一个实例来代表HTTP连接的状态对象。UserTokenHandler将会使用基于如NTLM或开启的客户端认证SSL会话认证模式的用户的主连接。如果二者都不可用,那么就不会返回令牌。

如果默认的不能满足它们的需要,用户可以提供一个自定义的实现:
[java] view plaincopy
  1. <span style="font-family:SimSun;">CloseableHttpClient httpclient = HttpClients.createDefault();  
  2. HttpClientContext context = HttpClientContext.create();  
  3. HttpGet httpget = new HttpGet("http://localhost:8080/");  
  4. CloseableHttpResponse response = httpclient.execute(httpget, context);  
  5. try {  
  6.     Principal principal = context.getUserToken(Principal.class);  
  7.     System.out.println(principal);  
  8. finally {  
  9.     response.close();  
  10. }</span>  
如果默认的不能满足需求,用户可以提供一个特定的实现:

[java] view plaincopy
  1. <span style="font-family:SimSun;">UserTokenHandler userTokenHandler = new UserTokenHandler() {  
  2.   
  3.     public Object getUserToken(HttpContext context) {  
  4.         return context.getAttribute("my-token");  
  5.     }  
  6.   
  7. };  
  8. CloseableHttpClient httpclient = HttpClients.custom()  
  9.         .setUserTokenHandler(userTokenHandler)  
  10.         .build();</span>  

7.2.2 持久化有状态的连接

请注意带有状态对象的持久化连接仅当请求被执行时,相同状态对象被绑定到执行上下文时可以被重用。所以,保证相同上下文重用于执行随后的相同用户,或用户令牌绑定到之前请求执行上下文的HTTP请求是很重要的。

[java] view plaincopy
  1. <span style="font-family:SimSun;">CloseableHttpClient httpclient = HttpClients.createDefault();  
  2. HttpClientContext context1 = HttpClientContext.create();  
  3. HttpGet httpget1 = new HttpGet("http://localhost:8080/");  
  4. CloseableHttpResponse response1 = httpclient.execute(httpget1, context1);  
  5. try {  
  6.     HttpEntity entity1 = response1.getEntity();  
  7. finally {  
  8.     response1.close();  
  9. }  
  10. Principal principal = context1.getUserToken(Principal.class);  
  11.   
  12. HttpClientContext context2 = HttpClientContext.create();  
  13. context2.setUserToken(principal);  
  14. HttpGet httpget2 = new HttpGet("http://localhost:8080/");  
  15. CloseableHttpResponse response2 = httpclient.execute(httpget2, context2);  
  16. try {  
  17.     HttpEntity entity2 = response2.getEntity();  
  18. finally {  
  19.     response2.close();  
  20. }</span>  

7.3. 使用FutureRequestExecutionService

通过使用FutureRequestExecutionService,你可以调度HTTP调用以及把response当作一个Future。这是非常有用的,比如当多次调用一个Web服务。使用FutureRequestExecutionService的优势在于您可以使用多个线程来调度请求同时,对任务设置超时或取消,当response不再需要的时候。

FutureRequestExecutionService用HttpRequestFutureTask(继承FutureTask)包装request。这个类允许你取消Task以及保持跟踪各项指标,如request duration。

7.3.1. 构造FutureRequestExecutionService

futureRequestExecutionService的构造方法包括两个参数:httpClient实例和ExecutorService实例。当配置两个参数的时候,您要使用的线程数等于最大连接数是很重要的。当线程比连接多的时候,连接可能会开始超时,因为没有可用的连接。当连接多于线程时,futureRequestExecutionService不会使用所有的连接。

[java] view plaincopy
  1. <span style="font-family:SimSun;">HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build();  
  2. ExecutorService executorService = Executors.newFixedThreadPool(5);  
  3. FutureRequestExecutionService futureRequestExecutionService =  
  4.     new FutureRequestExecutionService(httpClient, executorService);</span>  

7.3.2. 安排requests

要安排一个请求,只需提供一个HttpUriRequest,HttpContext和ResponseHandler。因为request是由executor service处理的,而ResponseHandler的是强制性的。

[java] view plaincopy
  1. <span style="font-family:SimSun;">private final class OkidokiHandler implements ResponseHandler<Boolean> {  
  2.     public Boolean handleResponse(  
  3.             final HttpResponse response) throws ClientProtocolException, IOException {  
  4.         return response.getStatusLine().getStatusCode() == 200;  
  5.     }  
  6. }  
  7.   
  8. HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(  
  9.     new HttpGet("http://www.google.com"), HttpClientContext.create(),  
  10.     new OkidokiHandler());  
  11. // blocks until the request complete and then returns true if you can connect to Google  
  12. boolean ok=task.get();</span>  

7.3.3. 取消tasks

预定的任务可能会被取消。如果任务尚未执行,但仅仅是排队等待执行,它根本就不会执行。如果任务在执行中且mayInterruptIfRunning参数被设置为true,请求中的abort()函数将被调用;否则response会简单地忽略,但该请求将被允许正常完成。任何后续调用task.get()会产生一个IllegalStateException。应当注意到,取消任务仅可以释放客户端的资源。该请求可能实际上是在服务器端正常处理。

[java] view plaincopy
  1. <span style="font-family:SimSun;">task.cancel(true)  
  2. task.get() // throws an Exception</span>  

7.3.4. 回调

不用手动调用task.get(),您也可以在请求完成时使用FutureCallback实例获取回调。这里采用的是和HttpAsyncClient相同的接口

[java] view plaincopy
  1. <span style="font-family:SimSun;">private final class MyCallback implements FutureCallback<Boolean> {  
  2.   
  3.     public void failed(final Exception ex) {  
  4.         // do something  
  5.     }  
  6.   
  7.     public void completed(final Boolean result) {  
  8.         // do something  
  9.     }  
  10.   
  11.     public void cancelled() {  
  12.         // do something  
  13.     }  
  14. }  
  15.   
  16. HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(  
  17.     new HttpGet("http://www.google.com"), HttpClientContext.create(),  
  18.     new OkidokiHandler(), new MyCallback());</span>  

7.3.5. 指标

FutureRequestExecutionService通常用于大量Web服务调用的应用程序之中。为了便于例如监视或配置调整,FutureRequestExecutionService跟踪了几个指标。

HttpRequestFutureTask会提供一些方法来获得任务时间:从被安排,开始,直到结束。此外,请求和任务持续时间也是可用的。这些指标都聚集在FutureRequestExecutionService中的FutureRequestExecutionMetrics实例,可以通过FutureRequestExecutionService.metrics()获取。

[java] view plaincopy
  1. <span style="font-family:SimSun;">task.scheduledTime() // returns the timestamp the task was scheduled  
  2. task.startedTime() // returns the timestamp when the task was started  
  3. task.endedTime() // returns the timestamp when the task was done executing  
  4. task.requestDuration // returns the duration of the http request  
  5. task.taskDuration // returns the duration of the task from the moment it was scheduled  
  6.   
  7. FutureRequestExecutionMetrics metrics = futureRequestExecutionService.metrics()  
  8. metrics.getActiveConnectionCount() // currently active connections  
  9. metrics.getScheduledConnectionCount(); // currently scheduled connections  
  10. metrics.getSuccessfulConnectionCount(); // total number of successful requests  
  11. metrics.getSuccessfulConnectionAverageDuration(); // average request duration  
  12. metrics.getFailedConnectionCount(); // total number of failed tasks  
  13. metrics.getFailedConnectionAverageDuration(); // average duration of failed tasks  
  14. metrics.getTaskCount(); // total number of tasks scheduled  
  15. metrics.getRequestCount(); // total number of requests  
  16. metrics.getRequestAverageDuration(); // average request duration  
  17. metrics.getTaskAverageDuration(); // average task duration</span>  
原创粉丝点击