原文链接 译者:flystarfly
在某些情况下,有必要自定义HTTP消息传输的方式来扩展HTTP参数的可使用性,以便能够处理非标准的作业。 例如,对于网络爬虫,可能需要强制HttpClient接受格式不正确的响应头,来捕捉消息的内容。
通常,插入自定义消息解析器或自定义连接实现的过程涉及几个步骤:
-
提供自定义的“LineParser”/“LineFormatter”接口实现类。 根据需要实现消息解析/格式化逻辑。
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); } } }
-
提供自定义的“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();
虽然HTTP规范假设会话状态信息总是以HTTP cookie的形式嵌入HTTP消息中,因此HTTP连接始终是无状态的,但这种假设在现实生活中并不总是成立。有些情况下,使用特定身份的用户或在特定的安全上下文内创建的HTTP连接是不能与其他用户共享的,只能由相同用户重复使用。有状态HTTP连接的例子是“NTLM”认证的连接和具有客户端证书认证的SSL连接。
HttpClient依赖于’UserTokenHandler’接口来确定给定的执行上下文是否为用户特定的。如果是,则该处理程序返回的令牌对象应该唯一标识当前用户,如果上下文不包含当前用户特有的任何资源或详细信息,则该对象将为null。 用户令牌将用于确保用户特定资源不会被其他用户共享或重复使用。
‘UserTokenHandler’接口的默认实现类作为Principal类的实例来表示HTTP连接的状态对象,如果它可以从给定的执行上下文获得的话。
‘DefaultUserTokenHandler’将使用基于连接的认证方案(例如’NTLM’)或使用客户端认证的SSL会话的用户主体。 如果两者都不可用,将返回NULL令牌。
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(); }
如果默认设置不能满足需要,用户可以提供自定义实现:
UserTokenHandler userTokenHandler = new UserTokenHandler() { public Object getUserToken(HttpContext context) { return context.getAttribute("my-token"); } }; CloseableHttpClient httpclient = HttpClients.custom() .setUserTokenHandler(userTokenHandler) .build();
请注意,只有当执行请求时相同的状态对象绑定到执行上下文时,携带状态对象的持久连接才可重复使用。因此,确保用户执行后续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(); }
使用FutureRequestExecutionService,您可以有计划的调用http并且在未来做出响应。 这是很有用的,例如, 对Web服务进行多个调用。使用FutureRequestExecutionService的优点是,您可以使用多个线程来并发调度请求,也可以设置这些任务超时,或在不再需要响应的时候取消它们。
FutureRequestExecutionService使用扩展了FutureTask的HttpRequestFutureTask类来封装请求。该类允许您取消任务以及追踪各个数据,如请求持续时间。
futureRequestExecutionService类的构造函数接受任何已有的httpClient实例和ExecutorService实例。 当配置这两者时,重要的是将最大连接数与要使用的线程数一致。当线程数超过了最大连接数时,因为没有了可用的连接,连接将会进入超时。而出现相反的情况时,futureRequestExecutionService将不会使用它们
HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(5).build(); ExecutorService executorService = Executors.newFixedThreadPool(5); FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpClient, executorService);
要计划请求,只需提供一个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();
计划任务有可能被取消。如果任务一直就在队列中等待执行,那么它将永远不会执行。如果它正在执行中并且mayInterruptIfRunning参数为true,那么将在请求上调用abort(); 否则虽然会正常完成请求,但是会忽略响应。任何对task.get()的后续调用都将失败并返回IllegalStateException异常。 应当注意,取消任务仅会释放客户端资源。 该请求实际上可以在服务器端正常处理。
task.cancel(true) task.get() // throws an Exception
如果要代替手动调用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());
FutureRequestExecutionService通常用于进行大量Web服务调用的应用程序中。 为了方便监控或配置调整等,FutureRequestExecutionService会追踪多项数据。
每个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
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/113885.html