7.1. 自定义客户端连接
在某些情况下,可能需要自定义超过可以使用HTTP参数的HTTP消息来设置线路传输方式,以便能够处理非标准,不符合规定的行为。例如,对于网络爬虫,可能需要强制HttpClient接受格式错误的响应头,以便挽救消息的内容。
通常,插入自定义消息解析器或自定义连接实现的过程涉及几个步骤:
-
提供一个自定义LineParser / LineFormatter接口实现。根据需要实现消息解析/格式化逻辑。
lass 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); } } }
-
提供一个自定义的HttpConnectionFactory实现。根据需要将默认请求写入器和/或响应解析器替换为自定义请求。
HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory( new DefaultHttpRequestWriterFactory(), new DefaultHttpResponseParserFactory( new MyLineParser(), new DefaultHttpResponseFactory()));
-
配置HttpClient以使用自定义连接工厂。
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager( connFactory); CloseableHttpClient httpclient = HttpClients.custom() .setConnectionManager(cm) .build();
7.2. 有状态HTTP 连接
虽然HTTP规范假定会话状态信息始终以HTTP Cookie的形式嵌入到HTTP消息中,因此HTTP连接始终是无状态的,但这种假设并不总是在现实生活中成立。 有些情况下,使用特定用户身份或特定安全上下文创建HTTP连接,因此无法与其他用户共享,并且只能由同一用户重复使用。 这种有状态HTTP连接的示例是具有客户端证书认证的NTLM认证连接和SSL连接。
7.2.1. User token handler
HttpClient依赖于UserTokenHandler接口来确定给定的执行上下文是否是用户特定的。 如果上下文是用户特定的,则此处理程序返回的令牌对象将被预期唯一标识当前用户,如果上下文不包含特定于当前用户的任何资源或详细信息,则为空。 用户令牌将用于确保用户特定的资源不会被其他用户共享或重用。
UserTokenHandler接口的默认实现使用Principal类的实例来表示HTTP连接的状态对象,如果可以从给定的执行上下文中获取。 DefaultUserTokenHandler将使用基于连接的身份验证方案(如NTLM)或启用客户机身份验证的SSL会话的用户主体。 如果两者都不可用,将返回空令牌。
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(); }
如果默认处理不能满足Users的需求可以提供一个自定义的实现 :UserTokenHandler userTokenHandler = new UserTokenHandler() { public Object getUserToken(HttpContext context) { return context.getAttribute("my-token"); } }; CloseableHttpClient httpclient = HttpClients.custom() .setUserTokenHandler(userTokenHandler) .build();
7.2.2. 持久化有状态连接
请注意,只有在执行请求时将相同的状态对象绑定到执行上下文,才能重用携带状态对象的持久连接。 因此,确保相同的上下文被重用以执行相同用户的后续HTTP请求,或者在请求执行之前将用户令牌绑定到上下文是非常重要的。
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(); }
7.3. 使用 FutureRequestExecutionService
使用FutureRequestExecutionService,您可以调度http呼叫并将响应视为未来。 这在例如 对Web服务进行多次调用。 使用FutureRequestExecutionService的优点是您可以使用多个线程同时调度请求,设置任务超时,或者在不再需要响应时取消它们。
FutureRequestExecutionService使用HttpRequestFutureTask包装请求,这扩展了FutureTask。 该类允许您取消任务,并跟踪各种指标,例如请求持续时间。
7.3.1. 创建 FutureRequestExecutionService
futureRequestExecutionService的构造函数接受任何现有的httpClient实例和ExecutorService实例。 配置两者时,重要的是将最大连接数与要使用的线程数对齐。 当线程多于连接时,由于没有可用连接,连接可能会启动超时。 当连线数超过线程时,futureRequestExecutionService将不会使用它们
HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService);
7.3.2. Scheduling requests
要调度一个请求,只需提供一个HttpUriRequest,HttpContext和一个ResponseHandler。 由于请求由执行程序服务处理,所以ResponseHandler是必需的。
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();
7.3.3. 取消调度任务
计划的任务可能会被取消。 如果任务尚未执行,但仅排队等待执行,则将永远不会执行。 如果正在执行,并且将mayInterruptIfRunning参数设置为true,则会在请求中调用abort(); 否则响应将被忽略,但请求将被允许正常完成。 对task.get()的任何后续调用都将失败并带有IllegalStateException。 应该注意的是,取消任务只会释放客户端资源。 请求实际上可以在服务器端正常处理。
task.cancel(true) task.get() // throws an Exception
7.3.4. Callbacks
而不是手动调用task.get(),您也可以使用一个FutureCallback实例,该实例在请求完成时获取回调。 这与HttpAsyncClient中使用的界面相同
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());
7.3.5. Metrics
FutureRequestExecutionService通常用于进行大量Web服务调用的应用程序。 为了方便例如 监视或配置调整,FutureRequestExecutionService跟踪几个mertric。
每个HttpRequestFutureTask提供方法来获取任务被安排,启动和结束的时间。 此外,请求和任务持续时间也可用。 这些指标汇总在FutureRequestExecutionService中的FutureRequestExecutionMetrics实例中,可以通过FutureRequestExecutionService.metrics()访问。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