流利的API
5.1。易于使用的外观API
从4.2版本开始,HttpClient自带了一个易于使用的外观API,基于流畅界面的概念。流畅的外观API仅暴露了HttpClient的最基本功能,旨在用于不需要HttpClient的完全灵活性的简单用例。例如,流畅的外观API可以减轻用户不必处理连接管理和资源释放。
以下是通过HC流畅API执行的HTTP请求的几个示例
// Execute a GET with timeout settings and return response content as String.
Request.Get("http://somehost/")
.connectTimeout(1000)
.socketTimeout(1000)
.execute().returnContent().asString();
// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
// containing a request body as String and return response content as byte array.
Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.version(HttpVersion.HTTP_1_1)
.bodyString("Important stuff", ContentType.DEFAULT_TEXT)
.execute().returnContent().asBytes();
// Execute a POST with a custom header through the proxy containing a request body
// as an HTML form and save the result to the file
Request.Post("http://somehost/some-form")
.addHeader("X-Custom-header", "stuff")
.viaProxy(new HttpHost("myproxy", 8080))
.bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
.execute().saveContent(new File("result.dump"));
人们还可以Executor
直接使用以在特定安全上下文中执行请求,从而缓存认证细节并重新使用后续请求。
Executor executor = Executor.newInstance()
.auth(new HttpHost("somehost"), "username", "password")
.auth(new HttpHost("myproxy", 8080), "username", "password")
.authPreemptive(new HttpHost("myproxy", 8080));
executor.execute(Request.Get("http://somehost/"))
.returnContent().asString();
executor.execute(Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.bodyString("Important stuff", ContentType.DEFAULT_TEXT))
.returnContent().asString();
5.1.1。响应处理
流畅的外观API通常减轻用户不必处理连接管理和资源释放。然而,在大多数情况下,这是以缓冲内存中的响应消息内容为代价的。强烈建议使用ResponseHandler
HTTP响应处理,以避免缓冲内存中的内容。
Document result = Request.Get("http://somehost/content")
.execute().handleResponse(new ResponseHandler<Document>() {
public Document handleResponse(final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
throw new HttpResponseException(
statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
if (entity == null) {
throw new ClientProtocolException("Response contains no content");
}
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
ContentType contentType = ContentType.getOrDefault(entity);
if (!contentType.equals(ContentType.APPLICATION_XML)) {
throw new ClientProtocolException("Unexpected content type:" +
contentType);
}
String charset = contentType.getCharset();
if (charset == null) {
charset = HTTP.DEFAULT_CONTENT_CHARSET;
}
return docBuilder.parse(entity.getContent(), charset);
} catch (ParserConfigurationException ex) {
throw new IllegalStateException(ex);
} catch (SAXException ex) {
throw new ClientProtocolException("Malformed XML document", ex);
}
}
});
第6章HTTP缓存
6.1。一般概念
HttpClient Cache提供了与HttpClient一起使用的符合HTTP / 1.1的缓存层 - 与浏览器缓存的Java相当。实施遵循“责任链”设计模式,其中缓存HttpClient实现可以为默认非缓存HttpClient实现提供替代; 可以从缓存中完全满足的请求不会导致实际的源请求。使用条件GET和If-Modified-Since和/或If-None-Match请求标头,可以在可能的情况下使用原点自动验证过时高速缓存条目。
HTTP / 1.1缓存一般设计为语义透明 ; 也就是说,缓存不应该改变客户端和服务器之间的请求 - 响应交换的含义。因此,将HttpClient缓存到现有的兼容客户端 - 服务器关系中应该是安全的。虽然缓存模块是从HTTP协议的角度来看的客户端的一部分,但实现的目标是与透明缓存代理的要求兼容。
最后,缓存HttpClient包括支持由RFC 5861指定的Cache-Control扩展(stale-if-error和stale-while-revalidate)。
当缓存HttpClient执行请求时,它将通过以下流程:
-
检查基本符合HTTP 1.1协议的请求,并尝试更正请求。
-
刷新任何将被该请求无效的缓存条目。
-
确定当前请求是否可以从缓存中执行。如果没有,直接通过请求到源服务器并返回响应,缓存后,如果合适。
-
如果它是一个可缓存可执行的请求,它将尝试从缓存中读取它。如果不在缓存中,请调用源服务器并缓存响应(如果适用)。
-
如果缓存响应适合作为响应,则构造一个包含ByteArrayEntity的BasicHttpResponse并返回它。否则,尝试根据原始服务器重新验证缓存条目。
-
在无法重新验证的缓存响应的情况下,如果适用,请调用源服务器并缓存响应。
当缓存HttpClient收到响应时,它将通过以下流程:
-
检查协议符合性的响应
-
确定响应是否可缓存
-
如果它是可缓存的,请尝试读取配置中允许的最大大小并将其存储在缓存中。
-
如果缓存的响应太大,重建部分消耗的响应并直接返回,而不缓存它。
值得注意的是,缓存HttpClient本身不是HttpClient的不同实现,而是通过将自身作为附加处理组件插入到请求执行管道中来工作。
6.2。RFC-2616合规性
我们相信HttpClient的缓存是无条件地遵从与RFC-2616。也就是说,无论规范是否必须,必须不应该,应该或不应该用于HTTP高速缓存,缓存层会尝试以满足这些要求的方式进行操作。这意味着缓存模块在放入时不会产生不正确的行为。
6.3。使用示例
这是一个简单的例子,说明如何设置一个基本的缓存HttpClient。根据配置,它将最多存储1000个缓存对象,每个对象的最大主体大小为8192个字节。这里选择的数字仅作为示例,并不意图作为建议。
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(1000)
.setMaxObjectSize(8192)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000)
.setSocketTimeout(30000)
.build();
CloseableHttpClient cachingClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setDefaultRequestConfig(requestConfig)
.build();
HttpCacheContext context = HttpCacheContext.create();
HttpGet httpget = new HttpGet("http://www.mydomain.com/content/");
CloseableHttpResponse response = cachingClient.execute(httpget, context);
try {
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
switch (responseStatus) {
case CACHE_HIT:
System.out.println("A response was generated from the cache with " +
"no requests sent upstream");
break;
case CACHE_MODULE_RESPONSE:
System.out.println("The response was generated directly by the " +
"caching module");
break;
case CACHE_MISS:
System.out.println("The response came from an upstream server");
break;
case VALIDATED:
System.out.println("The response was generated from the cache " +
"after validating the entry with the origin server");
break;
}
} finally {
response.close();
}
6.4。组态
缓存HttpClient继承了默认非缓存实现的所有配置选项和参数(包括设置选项,如超时和连接池大小)。对于特定于缓存的配置,您可以提供一个CacheConfig
实例来自定义以下方面的行为:
缓存大小。如果后端存储支持这些限制,则可以指定缓存条目的最大数量以及最大可缓存的响应体大小。
公共/私人缓存。默认情况下,缓存模块认为自己是一个共享(公共)缓存,例如,不会使用“Cache-Control:private”标记的Authorization标头或响应来缓存对请求的响应。但是,如果缓存仅由一个逻辑“用户”使用(与浏览器缓存类似),那么您将要关闭共享缓存设置。
启发式缓存。根据RFC2616,即使没有明确的高速缓存控制头由源设置,缓存也可以缓存某些缓存条目。默认情况下,此行为是关闭的,但如果您使用的是不设置适当标题但仍希望缓存响应的原点,则可能需要打开此操作。您将需要启用启发式缓存,然后指定默认的新鲜生命周期和/或资源上次修改后的一小部分时间。有关启发式缓存的更多细节,请参阅HTTP / 1.1 RFC的第13.2.2和13.2.4节。
背景验证。缓存模块支持RFC5861的stale-while-revalidate伪指令,允许在后台发生某些缓存条目重新验证。您可能需要调整最小和最大数量的后台工作线程的设置,以及在回收之前可以空闲的最长时间。当没有足够的工作人员跟上需求时,您还可以控制用于重新验证的队列的大小。
6.5。存储后端
缓存HttpClient的默认实现将缓存条目和缓存响应体存储在应用程序的JVM中。虽然这提供了高性能,但由于大小的限制或缓存条目是短暂的,并且在应用程序重新启动后无法生存,因此可能不适合您的应用程序。当前版本包括使用EhCache和memcached实现存储缓存条目的支持,这些实现允许将缓存条目溢出到磁盘或将它们存储在外部进程中。
如果这些选项都不适合您的应用程序,可以通过实施HttpCacheStorage接口提供自己的存储后端,然后在构建时提供缓存HttpClient。在这种情况下,缓存条目将使用您的方案进行存储,但您将重新使用围绕HTTP / 1.1兼容性和高速缓存处理的所有逻辑。一般来说,应该可以从支持键/值存储(类似于Java Map接口)的任何东西创建HttpCacheStorage实现,并具有应用原子更新的能力。
最后,通过一些额外的努力,完全可以设置多层缓存层次结构; 例如,按照类似于虚拟内存,L1 / L2处理器缓存等的模式,将内存缓存HttpClient包装在围绕磁盘上存储高速缓存条目或在memcached中远程存储的缓存中。
第7章高级主题
第7章高级主题
7.1。定制客户端连接
在某些情况下,可能需要定制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();
7.2。状态HTTP连接
虽然HTTP规范假定会话状态信息始终以HTTP Cookie的形式嵌入到HTTP消息中,因此HTTP连接始终是无状态的,但这种假设并不总是在现实生活中成立。有些情况下,使用特定用户身份或特定安全上下文创建HTTP连接,因此无法与其他用户共享,并且只能由同一用户重复使用。这种有状态HTTP连接的示例是经过NTLM
身份验证的连接和具有客户端证书身份验证的SSL连接。
7.2.1。用户令牌处理程序
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();
}
如果默认的用户不能满足他们的需求,用户可以提供自定义的实现:
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。计划请求
要安排一个请求,只需提供一个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。回调
而不是手动调用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。度量
FutureRequestExecutionService通常用于进行大量Web服务调用的应用程序。为了便于监控或配置调整,FutureRequestExecutionService跟踪了几个指标。
每个HttpRequestFutureTask提供方法来获取任务被安排,启动和结束的时间。此外,请求和任务持续时间也可用。这些指标汇总在FutureRequestExecutionService中的FutureRequestExecutionMetrics实例中,可以通过FutureRequestExecutionService.metrics()访问。
task.scheduledTime() // 返回任务被调度的时间戳
task.startedTime() //返回任务启动时的时间戳
task.endedTime() // 返回执行任务时的时间戳
task.requestDuration //返回http请求
task.taskDuration //从调度时返回任务的持续时间
FutureRequestExecutionMetrics metrics = futureRequestExecutionService.metrics()
metrics.getActiveConnectionCount() //当前活动连接
metrics.getScheduledConnectionCount(); //当前计划的连接
metrics.getSuccessfulConnectionCount(); //成功请求的总数
metrics.getSuccessfulConnectionAverageDuration(); //平均请求持续时间
metrics.getFailedConnectionCount(); //失败任务
metrics.getFailedConnectionAverageDuration(); //失败任务的平均持续时间
metrics.getTaskCount(); //计划的任务总数
metrics.getRequestCount(); // /请求
metrics.getRequestAverageDuration(); // 平均请求持续时间
metrics.getTaskAverageDuration(); // 平均任务持续时间