基础

1.1 请求执行

执行HTTP的方法是HTTPClient最基本的功能,执行这些操作涉及到HTTP请求和响应的交互,而这一过程通常是HTTPClient内部处理的。用户期望提供一种执行请求对象的方法,并且HTTPClient将该请求传送给目标服务器并返回相应响应结果,如果执行失败则抛出异常。 HttpClient接口是HTTPClient主要的起点,详细的接口协议如下所述 演示HTTP请求处理过程的示例

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpGet httpget = new HttpGet("http://localhost/");
  3. CloseableHttpResponse response = httpclient.execute(httpget);
  4. try {
  5. <...>
  6. } finally {
  7. response.close();
  8. }

1.1.1 HTTP 请求

所有的HTTP请求都有包含方法名、请求URI、协议版本号的行。HttpClient支持开箱即用的HTTP/1.1规范中定义的所有HTTP方法,有GET, HEAD, POST, PUT, DELETE, TRACE and OPTIONS,与之对应的类是HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace, and HttpOptions。 Request-URI是统一资源定位符的简称,定义了请求资源所在的位置。组成部分是协议专案, 主机名, 可选端口, 资源路径, 可选查询字符串和可选HTTP禎.

  1. HttpGet httpget = new HttpGet(
  2. "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

HTTPClient提供URIBuilder工具类简化请求URI的创建和修改

  1. URI uri = new URIBuilder()
  2. .setScheme("http")
  3. .setHost("www.google.com")
  4. .setPath("/search")
  5. .setParameter("q", "httpclient")
  6. .setParameter("btnG", "Google Search")
  7. .setParameter("aq", "f")
  8. .setParameter("oq", "")
  9. .build();
  10. HttpGet httpget = new HttpGet(uri);
  11. System.out.println(httpget.getURI());

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2 HTTP 响应

HTTP响应是由服务器收到请求处理解析后返回给客户端的消息包。包首行组成部分有 协议版本、状态数字、正文短语。

  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
  2. HttpStatus.SC_OK, "OK");
  3. System.out.println(response.getProtocolVersion());
  4. System.out.println(response.getStatusLine().getStatusCode());
  5. System.out.println(response.getStatusLine().getReasonPhrase());
  6. System.out.println(response.getStatusLine().toString());

HTTP/1.1 200 OK HTTP/1.1 200 OK

1.1.3 处理消息头

HTTP消息含有许多头元素,用来描述消息的属性,比如 content length, content type 等,HttpClient提供工具方法来检索,添加,删除,枚举头元素。

  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
  2. HttpStatus.SC_OK, "OK");
  3. response.addHeader("Set-Cookie",
  4. "c1=a; path=/; domain=localhost");
  5. response.addHeader("Set-Cookie",
  6. "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
  7. Header h1 = response.getFirstHeader("Set-Cookie");
  8. System.out.println(h1);
  9. Header h2 = response.getLastHeader("Set-Cookie");
  10. System.out.println(h2);
  11. Header[] hs = response.getHeaders("Set-Cookie");
  12. System.out.println(hs.length);

Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path=”/“, c3=c; domain=”localhost” 2

使用HeaderIterator接口获取给定类型的所有头元素是最高效的方式。

  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
  2. HttpStatus.SC_OK, "OK");
  3. response.addHeader("Set-Cookie",
  4. "c1=a; path=/; domain=localhost");
  5. response.addHeader("Set-Cookie",
  6. "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
  7. HeaderIterator it = response.headerIterator("Set-Cookie");
  8. while (it.hasNext()) {
  9. System.out.println(it.next());
  10. }

Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path=”/“, c3=c; domain=”localhost”

它还提供了方便的方法操作HTTP消息中的报头元素。

  1. HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
  2. HttpStatus.SC_OK, "OK");
  3. response.addHeader("Set-Cookie",
  4. "c1=a; path=/; domain=localhost");
  5. response.addHeader("Set-Cookie",
  6. "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
  7. HeaderElementIterator it = new BasicHeaderElementIterator(
  8. response.headerIterator("Set-Cookie"));
  9. while (it.hasNext()) {
  10. HeaderElement elem = it.nextElement();
  11. System.out.println(elem.getName() + " = " + elem.getValue());
  12. NameValuePair[] params = elem.getParameters();
  13. for (int i = 0; i < params.length; i++) {
  14. System.out.println(" " + params[i]);
  15. }
  16. }

c1 = a path=/ domain=localhost c2 = b path=/ c3 = c domain=localhost

1.1.4 HTTP实体

HTTP 消息携带有关请求或响应的内容实体,由于实体可选特性,不一定会出现在所有的请求响应包里面。带有实体的请求被称为实体封装请求。HTTP规范地定义了2种实体封装请求:POST 和 PUT。通常响应应该包含内容实体。但也有例外情况,比如对HEAD方法和204无内容,304未修改,205重置内容响应的响应。 HttpClient根据它们的内容来源来区分三种实体:

  • 流化(streamed) 内容来源于流或即时生成。尤其该类别包括从HTTP响应接收的实体。流化实体通常不可重复
  • 自包含(self-contained) 内容在内存中或通过独立于连接或其他实体的手段获得。这种类型的实体将主要用于包含HTTP请求的实体。
  • 包装(wrapping) 内容来源另外的实体。

当从HTTP响应流出内容时,实体的区别对于连接管理很重要。对于由应用程序创建并且仅使用HttpClient发送的请求实体,流化传输和自包含实体之间的差异不重要。在这种情况下,建议将不可重复的实体视为流式,将那些可重复的实体视为自包含的。

1.1.4.1 可重复实体

可重复实体,意味着可多次读取其内容。而这只有自包含实体才可能。(像 ByteArrayEntity or StringEntity)

1.1.4.2 使用HTTP实体

由于实体可以表示二进制和字符内容,因此它支持字符编码。 实体在执行具有封闭内容的请求时或在请求成功时创建,并且响应主体用于将结果发送回客户端。 要从实体读取内容,可以通过HttpEntity#getContent()方法检索输入流,该方法返回一个java.io.InputStream,或者可以向HttpEntity#writeTo(OutputStream)方法提供输出流, 一旦所有内容已经写入给定流,它将返回。 当实体已经接收到传入消息时,方法HttpEntity#getContentType()和HttpEntity#getContentLength()方法可以用于读取诸如Content-Type和Content-Length头部(如果它们可用)的公共元数据。由于Content-Type头部可以包含文本mime类型的字符编码,如text / plain或text / html,因此使用HttpEntity#getContentEncoding()方法来读取此信息。 如果标题不可用,将返回长度为-1,内容类型为NULL。如果Content-Type头可用,将返回一个Header对象。 当为传出消息创建实体时,该元数据必须由实体的创建者提供。

  1. StringEntity myEntity = new StringEntity("important message",
  2. ContentType.create("text/plain", "UTF-8"));
  3. System.out.println(myEntity.getContentType());
  4. System.out.println(myEntity.getContentLength());
  5. System.out.println(EntityUtils.toString(myEntity));
  6. System.out.println(EntityUtils.toByteArray(myEntity).length);

Content-Type: text/plain; charset=utf-8 17 important message 17

1.1.5 确保底层资源释放

为了确保系统资源的正确释放,使用者必须关闭与实体相关联的内容流或对应的响应体。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpGet httpget = new HttpGet("http://localhost/");
  3. CloseableHttpResponse response = httpclient.execute(httpget);
  4. try {
  5. HttpEntity entity = response.getEntity();
  6. if (entity != null) {
  7. InputStream instream = entity.getContent();
  8. try {
  9. // do something useful
  10. } finally {
  11. instream.close();
  12. }
  13. }
  14. } finally {
  15. response.close();
  16. }

关闭内容流和关闭响应之间的区别是前者将尝试通过消耗实体内容来保持底层连接活动,而后者立即关闭并丢弃连接 请注意,一旦实体被完全写出,需要确保HttpEntity#writeTo(OutputStream)方法正确释放系统资源。 当使用流化实体时,可以使用EntityUtils#consume(HttpEntity)方法来确保实体内容已完全消耗,并且底层流已关闭。 然而,可能存在这样的情况,即当整个响应内容的仅仅一小部分需要被检索并且用于消耗剩余内容并且使得连接可重用的性能损失太高时,在这种情况下,可以通过关闭响应来终止内容流。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpGet httpget = new HttpGet("http://localhost/");
  3. CloseableHttpResponse response = httpclient.execute(httpget);
  4. try {
  5. HttpEntity entity = response.getEntity();
  6. if (entity != null) {
  7. InputStream instream = entity.getContent();
  8. int byteOne = instream.read();
  9. int byteTwo = instream.read();
  10. // Do not need the rest
  11. }
  12. } finally {
  13. response.close();
  14. }

连接将不会被重用,但它持有的所有级别资源将被正确地释放。

1.1.6 消费实体内容

消费实体内容推荐使用HttpEntity#getContent() or HttpEntity#writeTo(OutputStream) 方法。HttpClient也附带了EntityUtils类,它暴露了几个静态方法,以便方便地从实体读取内容或信息。不是直接读取java.io.InputStream,而是可以通过使用此类中的方法来检索字符串/字节数组中的整个内容体。但是,强烈建议不要使用EntityUtils,除非响应实体来自受信任的HTTP服务器,并且已知其长度有限。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpGet httpget = new HttpGet("http://localhost/");
  3. CloseableHttpResponse response = httpclient.execute(httpget);
  4. try {
  5. HttpEntity entity = response.getEntity();
  6. if (entity != null) {
  7. long len = entity.getContentLength();
  8. if (len != -1 && len < 2048) {
  9. System.out.println(EntityUtils.toString(entity));
  10. } else {
  11. // Stream content out
  12. }
  13. }
  14. } finally {
  15. response.close();
  16. }

在某些情况下,可能需要多次读取实体内容。这个时候,实体内容必须以某种方式在内存或磁盘上进行缓存。最简单的方法是使用BufferedHttpEntity类包装原始的实体。这将导致原始实体的内容被读入内存中缓冲区。在所有其他方式中,实体包装器将含有原始包装器。

  1. CloseableHttpResponse response = <...>
  2. HttpEntity entity = response.getEntity();
  3. if (entity != null) {
  4. entity = new BufferedHttpEntity(entity);
  5. }

1.1.7 生成实体内容

HttpClient提供了几个类,用于通过HTTP连接有效地流出内容。 这些类的实例可以与诸如POST和PUT的实体封装请求相关联,以便将实体内容封装到传出HTTP请求中。 HttpClient为最常见的数据容器提供了几个类,例如针对字符串,字节数组,输入流和文件 :StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。

  1. File file = new File("somefile.txt");
  2. FileEntity entity = new FileEntity(file,
  3. ContentType.create("text/plain", "UTF-8"));
  4. HttpPost httppost = new HttpPost("http://localhost/action.do");
  5. httppost.setEntity(entity);

请注意InputStreamEntity是不可重复的,因为它只能从底层数据流读取一次。 通常,建议实现一个自定义自包含的HttpEntity类,而不是使用通用的InputStreamEntity。 FileEntity可以是一个很好的起点。

1.1.7.1

许多应用程序需要模拟提交HTML表单的过程,例如,登录到Web应用程序或提交输入数据。 HttpClient提供了实体类UrlEncodedFormEntity供进程方便使用。

  1. List<NameValuePair> formparams = new ArrayList<NameValuePair>();
  2. formparams.add(new BasicNameValuePair("param1", "value1"));
  3. formparams.add(new BasicNameValuePair("param2", "value2"));
  4. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
  5. HttpPost httppost = new HttpPost("http://localhost/handler.do");
  6. httppost.setEntity(entity);

UrlEncodedFormEntity实例将使用所谓的URL编码来对参数进行编码,并生成以下内容:

param1=value1&param2=value2

1.1.7.2 内容分块

通常建议让HttpClient根据要传输的HTTP消息的属性选择最合适的传输编码。然而,告诉HttpClient通过将HttpEntity#setChunked()设置为true来优选块编码是可能实现的。 请注意,HttpClient将仅使用此标志作为提示。当使用不支持块编码的HTTP协议版本(例如HTTP / 1.0)时,将忽略此值。

  1. StringEntity entity = new StringEntity("important message",
  2. ContentType.create("plain/text", Consts.UTF_8));
  3. entity.setChunked(true);
  4. HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
  5. httppost.setEntity(entity);

1.1.8 响应处理

处理响应的最简单方便的方法是使用含有handleResponse(HttpResponse响应)方法的ResponseHandler接口。处理响应的最简单和最方便的方法是使用ResponseHandler接口,其中包括handleResponse(HttpResponse响应)方法。这种方法完全解脱了用户对连接管理的担忧。 当使用ResponseHandler时,HttpClient将负责确保连接自动释放回连接管理器,而不管请求执行是成功与否。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpGet httpget = new HttpGet("http://localhost/json");
  3. ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
  4. @Override
  5. public JsonObject handleResponse(
  6. final HttpResponse response) throws IOException {
  7. StatusLine statusLine = response.getStatusLine();
  8. HttpEntity entity = response.getEntity();
  9. if (statusLine.getStatusCode() >= 300) {
  10. throw new HttpResponseException(
  11. statusLine.getStatusCode(),
  12. statusLine.getReasonPhrase());
  13. }
  14. if (entity == null) {
  15. throw new ClientProtocolException("Response contains no content");
  16. }
  17. Gson gson = new GsonBuilder().create();
  18. ContentType contentType = ContentType.getOrDefault(entity);
  19. Charset charset = contentType.getCharset();
  20. Reader reader = new InputStreamReader(entity.getContent(), charset);
  21. return gson.fromJson(reader, MyJsonObject.class);
  22. }
  23. };
  24. MyJsonObject myjson = client.execute(httpget, rh);

1.2 HttpClient 接口

HttpClient接口代表HTTP请求执行的最重要的协定。它不对请求执行过程施加限制或特定细节,并且将连接管理,状态管理,认证和重定向处理的细节保留到各自实现。这样也许更容易使用附加功能(例如响应内容缓存)来装饰接口。 通常HttpClient实现充当许多特殊目的处理程序或策略接口实现的门面,负责处理HTTP协议的特定方面,例如重定向或认证处理或关于连接持久性和保活持续时间的决定。

  1. ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
  2. @Override
  3. public long getKeepAliveDuration(
  4. HttpResponse response,
  5. HttpContext context) {
  6. long keepAlive = super.getKeepAliveDuration(response, context);
  7. if (keepAlive == -1) {
  8. // Keep connections alive 5 seconds if a keep-alive value
  9. // has not be explicitly set by the server
  10. keepAlive = 5000;
  11. }
  12. return keepAlive;
  13. }
  14. };
  15. CloseableHttpClient httpclient = HttpClients.custom()
  16. .setKeepAliveStrategy(keepAliveStrat)
  17. .build();

1.2.1 HttpClient 线程安全

HttpClient实现是线程安全的。建议将此类的相同实例重复用于多个请求执行。

1.2.2 HttpClient 资源释放

当一个实例CloseableHttpClient不再需要并且即将超出范围时,与其相关联的连接管理器必须通过调用CloseableHttpClient#close()方法关闭。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. try {
  3. <...>
  4. } finally {
  5. httpclient.close();
  6. }

1.3 HTTP 执行上下文

最初HTTP被设计为无状态的,基于响应请求的协议。然而,现实世界的应用程序通常需要能够通过几个逻辑相关的请求-响应交换来保持状态信息。为了使应用程序维持处理状态,HttpClient允许在特定执行上下文(称为HTTP上下文)中执行HTTP请求。如果在连续请求之间重用相同的上下文,则多个逻辑相关请求可以参与同一会话。类似像java.util.Map 的HTTP上下文函数,只是一个任意命名值的集合。应用程序可以在请求执行之前填充上下文属性,或者在执行完成后检查上下文。 HttpContext可以包含任意对象,因此可能在多个线程之间共享是不安全,建议每个执行线程维护自己的上下文。 在执行HTTP请求过程中,HttpClient将以下属性添加到执行上下文中:

  • HttpConnection实例表示到目标服务器的实际连接。
  • HttpHost实例代表目标服务器。
  • HttpRoute实例代表完整的连接路由。
  • HttpRequest实例表示实际的HTTP请求。执行上下文中的最终HttpRequest对象始终表示消息的状态,与发送到目标服务器的状态完全相同。默认的HTTP1.0和HTTP/1.1使用相对请求URI。 然而,如果通过代理的请求以非隧道模式发送时,则URI将是绝对的。
  • HttpResponse实例表示真是的响应
  • java.lang.Boolean对象标识实际请求是否已完全传输到连接目标服务器的标志。
  • RequestConfig对象代表真实的请求配置。
  • java.util.List 对象表示在请求执行过程中接收到的所有重定向位置的集合。 可以使用HttpClientContext适配器类来简化与上下文状态的交互。
    1. HttpContext context = <...>
    2. HttpClientContext clientContext = HttpClientContext.adapt(context);
    3. HttpHost target = clientContext.getTargetHost();
    4. HttpRequest request = clientContext.getRequest();
    5. HttpResponse response = clientContext.getResponse();
    6. RequestConfig config = clientContext.getRequestConfig();
    为确保在HTTP请求之间的对话上下文和状态信息的自动传播,表示逻辑相关会话的多个请求序列应当使用相同的HttpContext实例来执行。 在以下示例中演示了初始请求的配置将保存在执行上下文中,并传播到共享上下文的连续请求中。 ```java CloseableHttpClient httpclient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom()
    1. .setSocketTimeout(1000)
    2. .setConnectTimeout(1000)
    3. .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. ## 1.4 HTTP协议拦截器
  2. HTTP协议拦截器是实现HTTP协议的特定方面的例程。通常协议拦截器被期望作用在输入消息的一个特定报头或一组相关的报文,或将这些报文填充进输出消息。协议拦截器还可以处理包含消息的内容实体 - 透明内容压缩/解压缩是一个很好的例子。通常这是通过使用“装饰器”模式实现的,其中包装器实体类用于装饰原始实体。几个协议拦截器可以组合形成一个逻辑单元。协议拦截器可以通过通过HTTP执行上下文共享信息(例如处理状态)来协作。 协议拦截器可以使用HTTP上下文来存储一个请求或几个连续请求的处理状态。
  3. 通常,只要拦截器不依赖于执行上下文的特定状态, 它的执行顺序并不重要。 如果协议拦截器具有相互依赖性并因此必须以特定顺序执行,则它们应当按照与它们的预期执行顺序相同的顺序添加到协议处理器。如果由于协议拦截器具有相互依赖性导致必须以特定顺序执行,这时候就应该按照预期的顺序添加到协议处理器。
  4. 协议拦截器必须实现为线程安全的。与servlet类似,协议拦截器不应使用实例变量,除非对这些变量的访问进行同步。
  5. 以下实例演示了如何使用本地上下文跟踪连续请求的处理状态。
  6. ```java
  7. CloseableHttpClient httpclient = HttpClients.custom()
  8. .addInterceptorLast(new HttpRequestInterceptor() {
  9. public void process(
  10. final HttpRequest request,
  11. final HttpContext context) throws HttpException, IOException {
  12. AtomicInteger count = (AtomicInteger) context.getAttribute("count");
  13. request.addHeader("Count", Integer.toString(count.getAndIncrement()));
  14. }
  15. })
  16. .build();
  17. AtomicInteger count = new AtomicInteger(1);
  18. HttpClientContext localContext = HttpClientContext.create();
  19. localContext.setAttribute("count", count);
  20. HttpGet httpget = new HttpGet("http://localhost/");
  21. for (int i = 0; i < 10; i++) {
  22. CloseableHttpResponse response = httpclient.execute(httpget, localContext);
  23. try {
  24. HttpEntity entity = response.getEntity();
  25. } finally {
  26. response.close();
  27. }
  28. }

1.5 异常处理

HTTP协议处理器可以抛出两种类型的异常:I/O失败则抛出java.io.IOException,如套接字超时或套接字重置, HTTP失败则抛出HttpException,如违反HTTP协议。通常,I/O错误被认为是非致命的和可恢复的,而HTTP协议错误被认为是致命的且不能自动恢复。请注意,HttpClient实现将HttpExceptions重新抛出为ClientProtocolException,它是java.io.IOException的子类。这使HttpClient的用户能够处理来自单个catch子句的I/O错误和协议违例。

1.5.1 HTTP传输安全

了解HTTP协议不是完美适合所有类型的应用程序是很重要的。HTTP是一个简单的面向请求/响应协议。它最初设计为支持静态或动态生成的内容检索。从没有打算支持事务操作。例如,如果HTTP服务器成功地接收和处理请求,生成响应并将状态代码发送回客户端,则HTTP服务器将认为履行的部分协定职责。如果客户端由于读取超时,请求取消或系统崩溃而无法完全接收响应,则服务器不会尝试回滚事务。如果客户端决定重试相同的请求,则服务器将不可避免地多次执行相同的事务。在某些情况下,这可能导致应用程序数据损坏或应用程序状态不一致。 尽管HTTP从未设计为支持事务处理,但如果在满足某些条件的情况下,它仍然可以用作关键任务应用程序的传输协议。为了确保HTTP传输层安全,系统必须确保HTTP方法在应用层上的幂等性。

1.5.2 幂等方法

HTTP / 1.1规范定义了幂等方法 [方法也可以具有“幂等性”的属性(除了错误或到期问题),N次(N>0)相同请求与单次请求的副作用相同] 换句话说,应用程序应该确保它准备处理多个执行相同方法的影响。这可以通过例如提供唯一的事务id或通过避免执行相同的逻辑操作的其他手段来实现。 请注意,此问题不是特定于HttpClient。基于浏览器的应用程序都会遇到与HTTP方法非幂等性完全相同的问题。 默认情况下,HttpClient假设只有非实体封装方法(如GET和HEAD)是幂等的,而诸如POST和PUT的实体封装方法(例如POST和PUT)出于兼容性原因不是幂等。

1.5.3 自动异常回复

默认情况下,HttpClient尝试自动从I/O异常恢复。默认的自动恢复机制仅限于几个已知安全的异常。

  • HttpClient不会尝试从任何逻辑或HTTP协议错误中恢复
  • HttpClient将自动重试那些假定为幂等的方法
  • 当HTTP请求仍在传输到目标服务器(即请求尚未完全传输到服务器)时,HttpClient将自动重试那些因传输异常而失败的方法。

1.5.4 请求重试方法

为了启用自定义异常恢复机制,应该提供HttpRequestRetryHandler接口的实现。

  1. HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
  2. public boolean retryRequest(
  3. IOException exception,
  4. int executionCount,
  5. HttpContext context) {
  6. if (executionCount >= 5) {
  7. // Do not retry if over max retry count
  8. return false;
  9. }
  10. if (exception instanceof InterruptedIOException) {
  11. // Timeout
  12. return false;
  13. }
  14. if (exception instanceof UnknownHostException) {
  15. // Unknown host
  16. return false;
  17. }
  18. if (exception instanceof ConnectTimeoutException) {
  19. // Connection refused
  20. return false;
  21. }
  22. if (exception instanceof SSLException) {
  23. // SSL handshake exception
  24. return false;
  25. }
  26. HttpClientContext clientContext = HttpClientContext.adapt(context);
  27. HttpRequest request = clientContext.getRequest();
  28. boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
  29. if (idempotent) {
  30. // Retry if the request is considered idempotent
  31. return true;
  32. }
  33. return false;
  34. }
  35. };
  36. CloseableHttpClient httpclient = HttpClients.custom()
  37. .setRetryHandler(myRetryHandler)
  38. .build();

请注意,可以使用StandardHttpRequestRetryHandler而不是默认情况下使用的方法(有:GET,HEAD,PUT,DELETE,OPTIONS和TRACE),以便将由RFC-2616定义为幂等的请求方法视为安全自动重试。

1.6 取消请求

在某些情况下,由于目标服务器上的高负载或客户端上发出的太多并发请求,HTTP请求执行无法在预期时间范围内完成。在这种情况下,可能需要提前终止请求并解除阻塞在I/O操作中的阻塞线程。 HttpClient正在执行的HTTP请求可以通过调用HttpUriRequest#abort()方法在任何执行阶段中止。这个方法是线程安全的,可以在任何线程中调用。当HTTP请求被中止时,它的执行线程(即使阻塞在当前在I/O操作中)通过抛出一个InterruptedIOException来确保解除阻塞。

1.7 重定向处理

HttpClient自动处理所有类型的重定向,除了HTTP规范明确禁止的要求用户干预的那些。 请参阅其他(状态代码303)POST上的重定向,PUT请求根据HTTP规范的要求转换为GET请求。可以使用自定义重定向策略来放松由HTTP规范强加的对POST方法的自动重定向的限制。

  1. LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
  2. CloseableHttpClient httpclient = HttpClients.custom()
  3. .setRedirectStrategy(redirectStrategy)
  4. .build();

HttpClient通常必须在其执行过程中重写请求消息。默认情况下,HTTP/1.0和HTTP/1.1通常使用相对请求URI。同样,原始请求可能会多次从位置重定向到另一个。最终解析出来的绝对HTTP位置可以使用原始请求和上下文来构建。实用程序方法URIUtils#resolve可用于构建用于生成最终请求的绝对URI。此方法包括来自重定向请求或原始请求的最后一个片段标识符。

  1. CloseableHttpClient httpclient = HttpClients.createDefault();
  2. HttpClientContext context = HttpClientContext.create();
  3. HttpGet httpget = new HttpGet("http://localhost:8080/");
  4. CloseableHttpResponse response = httpclient.execute(httpget, context);
  5. try {
  6. HttpHost target = context.getTargetHost();
  7. List<URI> redirectLocations = context.getRedirectLocations();
  8. URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
  9. System.out.println("Final HTTP location: " + location.toASCIIString());
  10. // Expected to be an absolute URI
  11. } finally {
  12. response.close();
  13. }