7.1 自定义客户端连接
在特定条件下,也许需要来定制HTTP报文通过线路传递,越过了可能使用的HTTP参数来处理非标准不兼容行为的方式。比如,对于Web爬虫,它可能需要强制HttpClient接受格式错误的响应头部信息,来抢救报文的内容。
通常插入一个自定义的报文解析器的过程或定制连接实现需要几个步骤:
提供一个自定义LineParser/LineFormatter接口实现。如果需要,实现报文解析/格式化逻辑。
- <span style="font-family:SimSun;">class MyLineParser extends BasicLineParser {
- @Override
- public Header parseHeader(
- CharArrayBuffer buffer) throws ParseException {
- try {
- return super.parseHeader(buffer);
- } catch (ParseException ex) {
- // Suppress ParseException exception
- return new BasicHeader(buffer.toString(), null);
- }
- }
- }</span>
提过一个自定义的 HttpConnectionFactory 实现。替换需要自定义的默认请求/响应解析器,请求/响应格式化器。如果需要,实现不同的报文写入/读取代码。
- <span style="font-family:SimSun;">HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =
- new ManagedHttpClientConnectionFactory(
- new DefaultHttpRequestWriterFactory(),
- new DefaultHttpResponseParserFactory(
- new MyLineParser(), new DefaultHttpResponseFactory()));</span>
为了创建新类的连接,提供一个自定义的ClientConnectionOperator接口实现。如果需要,实现不同的套接字初始化代码。
- <span style="font-family:SimSun;">PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
- connFactory);
- CloseableHttpClient httpclient = HttpClients.custom()
- .setConnectionManager(cm)
- .build();</span>
7.2 有状态的HTTP连接
7.2.1 用户令牌处理器
如果它可以从给定的执行上下文中来获得,UserTokenHandler接口的默认实现是使用主类的一个实例来代表HTTP连接的状态对象。UserTokenHandler将会使用基于如NTLM或开启的客户端认证SSL会话认证模式的用户的主连接。如果二者都不可用,那么就不会返回令牌。
- <span style="font-family:SimSun;">CloseableHttpClient httpclient = HttpClients.createDefault();
- HttpClientContext context = HttpClientContext.create();
- HttpGet httpget = new HttpGet("http://localhost:8080/");
- CloseableHttpResponse response = httpclient.execute(httpget, context);
- try {
- Principal principal = context.getUserToken(Principal.class);
- System.out.println(principal);
- } finally {
- response.close();
- }</span>
- <span style="font-family:SimSun;">UserTokenHandler userTokenHandler = new UserTokenHandler() {
- public Object getUserToken(HttpContext context) {
- return context.getAttribute("my-token");
- }
- };
- CloseableHttpClient httpclient = HttpClients.custom()
- .setUserTokenHandler(userTokenHandler)
- .build();</span>
7.2.2 持久化有状态的连接
- <span style="font-family:SimSun;">CloseableHttpClient httpclient = HttpClients.createDefault();
- HttpClientContext context1 = HttpClientContext.create();
- HttpGet httpget1 = new HttpGet("http://localhost:8080/");
- CloseableHttpResponse response1 = httpclient.execute(httpget1, context1);
- try {
- HttpEntity entity1 = response1.getEntity();
- } finally {
- response1.close();
- }
- Principal principal = context1.getUserToken(Principal.class);
- HttpClientContext context2 = HttpClientContext.create();
- context2.setUserToken(principal);
- HttpGet httpget2 = new HttpGet("http://localhost:8080/");
- CloseableHttpResponse response2 = httpclient.execute(httpget2, context2);
- try {
- HttpEntity entity2 = response2.getEntity();
- } finally {
- response2.close();
- }</span>
7.3. 使用FutureRequestExecutionService
FutureRequestExecutionService用HttpRequestFutureTask(继承FutureTask)包装request。这个类允许你取消Task以及保持跟踪各项指标,如request duration。
futureRequestExecutionService的构造方法包括两个参数:httpClient实例和ExecutorService实例。当配置两个参数的时候,您要使用的线程数等于最大连接数是很重要的。当线程比连接多的时候,连接可能会开始超时,因为没有可用的连接。当连接多于线程时,futureRequestExecutionService不会使用所有的连接。
- <span style="font-family:SimSun;">HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build();
- ExecutorService executorService = Executors.newFixedThreadPool(5);
- FutureRequestExecutionService futureRequestExecutionService =
- new FutureRequestExecutionService(httpClient, executorService);</span>
7.3.2. 安排requests
要安排一个请求,只需提供一个HttpUriRequest,HttpContext和ResponseHandler。因为request是由executor service处理的,而ResponseHandler的是强制性的。
- <span style="font-family:SimSun;">private final class OkidokiHandler implements ResponseHandler<Boolean> {
- public Boolean handleResponse(
- final HttpResponse response) throws ClientProtocolException, IOException {
- return response.getStatusLine().getStatusCode() == 200;
- }
- }
- HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
- new HttpGet("http://www.google.com"), HttpClientContext.create(),
- new OkidokiHandler());
- // blocks until the request complete and then returns true if you can connect to Google
- boolean ok=task.get();</span>
7.3.3. 取消tasks
预定的任务可能会被取消。如果任务尚未执行,但仅仅是排队等待执行,它根本就不会执行。如果任务在执行中且mayInterruptIfRunning参数被设置为true,请求中的abort()函数将被调用;否则response会简单地忽略,但该请求将被允许正常完成。任何后续调用task.get()会产生一个IllegalStateException。应当注意到,取消任务仅可以释放客户端的资源。该请求可能实际上是在服务器端正常处理。
- <span style="font-family:SimSun;">task.cancel(true)
- task.get() // throws an Exception</span>
7.3.4. 回调
不用手动调用task.get(),您也可以在请求完成时使用FutureCallback实例获取回调。这里采用的是和HttpAsyncClient相同的接口
- <span style="font-family:SimSun;">private final class MyCallback implements FutureCallback<Boolean> {
- public void failed(final Exception ex) {
- // do something
- }
- public void completed(final Boolean result) {
- // do something
- }
- public void cancelled() {
- // do something
- }
- }
- HttpRequestFutureTask<Boolean> task = futureRequestExecutionService.execute(
- new HttpGet("http://www.google.com"), HttpClientContext.create(),
- new OkidokiHandler(), new MyCallback());</span>
7.3.5. 指标
FutureRequestExecutionService通常用于大量Web服务调用的应用程序之中。为了便于例如监视或配置调整,FutureRequestExecutionService跟踪了几个指标。
HttpRequestFutureTask会提供一些方法来获得任务时间:从被安排,开始,直到结束。此外,请求和任务持续时间也是可用的。这些指标都聚集在FutureRequestExecutionService中的FutureRequestExecutionMetrics实例,可以通过FutureRequestExecutionService.metrics()获取。
- <span style="font-family:SimSun;">task.scheduledTime() // returns the timestamp the task was scheduled
- task.startedTime() // returns the timestamp when the task was started
- task.endedTime() // returns the timestamp when the task was done executing
- task.requestDuration // returns the duration of the http request
- task.taskDuration // returns the duration of the task from the moment it was scheduled
- FutureRequestExecutionMetrics metrics = futureRequestExecutionService.metrics()
- metrics.getActiveConnectionCount() // currently active connections
- metrics.getScheduledConnectionCount(); // currently scheduled connections
- metrics.getSuccessfulConnectionCount(); // total number of successful requests
- metrics.getSuccessfulConnectionAverageDuration(); // average request duration
- metrics.getFailedConnectionCount(); // total number of failed tasks
- metrics.getFailedConnectionAverageDuration(); // average duration of failed tasks
- metrics.getTaskCount(); // total number of tasks scheduled
- metrics.getRequestCount(); // total number of requests
- metrics.getRequestAverageDuration(); // average request duration
- metrics.getTaskAverageDuration(); // average task duration</span>