您的位置:首页 > 理论基础 > 计算机网络

HttpClient第一章(三)

2015-12-13 23:03 507 查看
1.3 HTTP执行上下文
最初的HTTP被设计成无状态面向请求-响应的协议,然而,现实的环境经常需要通过几个逻辑相关联的请求-响应交换来维持状态信息,为了使应用可以维持处理状态,HttpClient允许HTTP请求在一个被称为HTTP上下文的特殊环境内被执行。如果一些上下文可以在连续的请求中被重用,那么多个逻辑相关的请求就可以形成一个逻辑会话。HTTP上下文的功能和java.util.Map<String, Object>相似,它是一个任意命名值的简单集合,一个应用可以在执行请求之前设置上下文属性也可以在执行完成之后检查上下文。
HttpContext可以包含任意对象,因此在多个线程之间共享的时候可能是不安全的,建议每个执行线程包含一个自己的上下文。
在执行HTTP请求的基础上HttpClient增加了如下属性到执行上下文:
HttpConnection实例代表到实际目标服务器的链接。
HttpHost实例代表链接目标
HttpRoute实例代表完整链接路由
HttpRequest实例代表实际的HTTP请求。在执行上下文中最终的HttpRequest对象总是代表发送到目标服务器的信息的状态。默认情况下每一个HTTP1.0和HTTP1.1版本协议的请求使用相对URI,然而如果请求是通过代理并且使用非通道模式那么就会使用绝对URI。
HttpResponse实例代表实际的HTTP响应。
java.lang.Boolean对象代表预示着实际请求是否已经被完全传输到链接目标的标志
RequesConfig对象代表实际的请求配置。
java.util.List<URI>对象代表在请求执行过程中接受到的所有重定向路径的集合。
也可以使用HttpClientContext适配器类简化和上下文状态的交互。
HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();
多请求序列代表一个逻辑相关的会话,此回话应该在相同的HttpContext实例中执行以确保交互上下文和状态信息的在各个请求之间自动传播。
下面的例子中,在初始的请求设置的请求配置将会被保持在执行上下文中,并且在连续的请求中得以传播,而且会被共享在相同的上下文中。
CloseableHttpClient httpclient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).
setConnectTimeout(1000).build();
HttpGet httpget1 = new HttpGet("http://localhost/1");
httpget1.setConfig(requestConfig);
CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
try {
HttpEntity entity1 = response1.getEntity();
} finally {
response1.close();
}
HttpGet httpget2 = new HttpGet("http://localhost/2");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
try {
HttpEntity entity2 = response2.getEntity();
} finally {
response2.close();
}
1.4 HTTP协议拦截器
HTTP协议拦截器是一个实现了HTTP协议具体某一方面的普通程序。通常,协议拦截器被期望对具体或者一组相关的响应消息头操作,或者用这么一组消息头填充请求消息。协议拦截器也能处理内容实体,封装了透明消息内容实体的压缩/解压就是一个很好的例子,通常这些处理通过装饰者模式完成,在装饰者模式中,一个封装实体类用来装饰原始实体。另外,几个协议拦截器合并在一起可以构成一个逻辑单元。
协议拦截器可以通过共享信息工作,例如通过HTTP执行上下文共享处理状态。协议拦截器可以使用HTTP上下文为一个或者几个连续的请求存储处理状态。
一般情况下,只要不依赖一个特殊的执行上下文状态,拦截器的执行顺序都是没关系的。如果协议拦截器是相互依赖的并且因此需按照一个特殊的顺序执行,那么他们应该按照一定的顺序被添加到协议处理器中。
协议拦截器必须以线程安全的方式被实现。这点儿类似servlet,协议拦截器不应该使用实例变量除非访问这些变量是线程同步的。
下面是一个本地上下文如何在连续的请求之间被用来持续保留处理状态的例子:
CloseableHttpClient httpclient = HttpClients.custom().addInterceptorLast(
new HttpRequestInterceptor() {
public void process(final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
AtomicInteger count = (AtomicInteger)context.getAttribute("count");
request.addHeader("Count",Integer.toString(count.getAndIncrement()));
}
}).build();
AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);
HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
CloseableHttpResponse response = httpclient.execute(httpget, localContext);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
1.5.1 HTTP传输安全
理解HTTP协议并不适合所有类型的应用是重要的。HTTP只是简单的面向请求响应的协议,最初它被设计成支持静态或动态生成内容检索,它从来没有被考虑过用来支持事务操作,例如,作为HTTP服务器如果接受、处理请求、生成响应和发送状态码到客户端成功但是客户端因为读取超时、请求取消或者系统宕机而导致接受响应失败,那么服务器不会尝试回滚事务。如果客户端重试相同的请求,那么服务器不再执行相同的事务。有时这会导致应用数据污染或者不一致的应用状态。
尽管HTTP从来没有被考虑过支持事务处理,但它仍然作为关键应用的传输协议,为了确保HTTP传输层的安全系统必须确保在应用层的HTTP方法的幂等性。
1.5.2 幂等方法
HTTP/1.1 指导说明这样定义一个幂等方法:
【方法也可以有幂等性,其幂等性(忽略错误和过期问题)表现在N(N>0)次相同的请求所带来的影响和一次请求带来的影响相同】
换句话说,应用程序应该确保准备好应对同一方法被多次执行所带来的影响。其实这可以解决,例如通过提供一个唯一的事务ID,或者通过其他方法避免相同逻辑操作的执行。
请注意,这一问题并不是HttpClient特有的,基于浏览器的应用当涉及到HTTP方法的非幂等性时也有相同的问题。
默认情况下,HttpClient仅保证形如GET、HEAD等非实体包含方法的幂等性,这也是形如POST、PUT等实体包含方法不兼容的原因。
1.5.3 自动异常恢复
默认情况下,HttpClient尝试自动从I/O异常中恢复,默认的自动恢复功能只限制于几个安全的异常。
HttpClient不会从任何逻辑的或者HTTP协议错误(继承于HttpException类的异常)中恢复。 HttpClient会自动重试幂等性的方法。
HttpClient会重试那些因传输异常而失败但是HTTP请求仍然正在被传输至目标服务器的方法(例如,请求还没有完全被传输至服务器)。
1.5.4 请求重试处理器
为了确保一个定制的异常恢复机制,应该提供一个HttpRequestRetryHandler接口的实现。
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception,int executionCount,HttpContext context{
if (executionCount >= 5) {
// Do not retry if over max retry count
return false;
}
if (exception instanceof InterruptedIOException) {
// Timeout
return false;
}
if (exception instanceof UnknownHostException) {
// Unknown host
return false;
}
if (exception instanceof ConnectTimeoutException) {
// Connection refused
return false;
}
if (exception instanceof SSLException) {
// SSL handshake exception
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// Retry if the request is considered idempotent
return true;
}
return false;
}
};
CloseableHttpClient httpclient = HttpClients.custom().setRetryHandler(myRetryHandler)
.build();
请注意,可使用StandardHttpRequestRetryHandler代替默认的用于把这些被RFC-2616定义为幂等的请求方法当做自动尝试安全的方法,这些方法包括:GET,HEAD,PUT,DELETE,OPTIONS和TRACE。
1.6 终止请求
在某些情况下由于目标服务器负载过高或者客户端出现太多并发请求而导致HTTP请求执行在预期时间内没有完成,在这种情况下,尽早终止请求并且清除阻塞在I/O操作上的线程的执行是必要的。被HttpClient执行的HTTP请求可以通过调用HttpUriRequest的abort()方法在执行的任何阶段被终止。此方法是线程安全的并且可以被任何线程调用。当一个HTTP请求被终止了他的线程,即使该线程正阻塞在I/O操作上也能通过抛出InterruptedIOException异常的方式保证线程被终止。
1.7 重定向处理
HttpClient自动处理所有类型的重定向,除了那些因需用户干预而明确被HTTP规范禁止的重定向,参见其他(状态码为303)重定向,如POST和PUT的请求被转换成GET请求因HTTP规范要求禁止重定向。可以使用一个定制的重定向策略来松懈被HTTP规范强制限制的POST方法的自动重定向。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient =HttpClients.custom().
setRedirectStrategy(redirectStrategy).build();
HttpClient不得不经常在执行过程中重写请求信息,每一个默认的HTTP/1.0和HTTP/1.1版本的请求通常使用相对URI,同样地,原始请求可能会从一个地址多次重定向到另一个地址。最终的绝对HTTP地址可以通过使用原始请求和上下文构建。工具方法URIUtils的resolve()方法可以被用来构建用来生成最终请求的绝对URI。此方法包含来自重定向请求或者原始请求的最后定位符片段。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
HttpHost target = context.getTargetHost();
List<URI> redirectLocations = context.getRedirectLocations();
URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
System.out.println("Final HTTP location: " + location.toASCIIString());
// Expected to be an absolute URI
} finally {
response.close();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: