HTTP Client基础教程

文档翻译自Http Client官方文档,原文链接如下:
https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/fundamentals.html

第一章 基础教程

1.1 请求执行

HttpClient最基本的功能就是执行HTTP方法。HTTP方法的执行涉及到若干HTTP请求/HTTP响应的交换,通常是在HttpClient内部被处理的。使用者需要提供一个请求对象来执行,同时HttpClient需要将请求传输到目标服务器上,然后返回相应的响应对象,或者在执行不成功的时候抛出异常。

非常自然地,HttpClient API的主要入口就是HttpClient接口,它定义了结构,就像下面描述的这样。

下面是一个请求执行过程最简单的例子:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    <...>
} finally {
    response.close();
}

1.1.1 HTTP请求

所有的HTTP请求都有一个请求行,包含了一个方法名、一个请求URI和一个HTTP协议版本。

HttpClient支持在HTTP/1.1规范中定义的所有HTTP方法:GET, HEAD, POST, PUT, DELETE, TRACEOPTION。有一些特定的类来对应每个方法类型:HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTraceHttpOptions

Request-URI是Uniform Resource Identifier,可以来识别请求所需要的资源。HTTP请求的URI包含:

  • 协议模式(protocol scheme)
  • 主机名(host name)
  • 可选端口(optional port)
  • 资源路径(resource path)
  • 可选查询(optional query)
  • 可选片段(optional fragment)
HttpGet httpget = new HttpGet(
     "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

HttpClient提供了URIBuilder工具类来简化请求URI的创建和修改。

URI uri = new URIBuilder()
        .setScheme("http")
        .setHost("www.google.com")
        .setPath("/search")
        .setParameter("q", "httpclient")
        .setParameter("btnG", "Google Search")
        .setParameter("aq", "f")
        .setParameter("oq", "")
        .build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());

输出 >

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

1.1.2 HTTP响应

HTTP响应是当服务器接收并解析到请求消息后,发送回客户端的消息。消息的第一行包含协议版本,然后是数值型的状态码,然后是与状态码相关的文本短语。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
HttpStatus.SC_OK, "OK");

System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
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提供了方法来检索、添加、移除和枚举消息头。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1);
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length);

输出 >

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

获取一个给定类型所有消息头的最有效的方式就是使用HeaderIterator接口。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderIterator it = response.headerIterator("Set-Cookie");

while (it.hasNext()) {
    System.out.println(it.next());
}

输出 >

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

HttpClient同样提供了将HTTP消息转化为一个个的消息头元素的简便方法。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderElementIterator it = new BasicHeaderElementIterator(
    response.headerIterator("Set-Cookie"));

while (it.hasNext()) {
    HeaderElement elem = it.nextElement(); 
    System.out.println(elem.getName() + " = " + elem.getValue());
    NameValuePair[] params = elem.getParameters();
    for (int i = 0; i < params.length; i++) {
        System.out.println(" " + params[i]);
    }
}

输出 >

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

1.1.4 HTTP实体

HTTP消息可以携带一个与请求或者响应相关联的内容实体。实体可以在一些请求和响应中被找到,因为它们是可选的。使用了实体的请求被作为实体封装的请求。HTTP规范定义了两个实体来封装请求方法:POSTPUT。响应通常会被需要封装一个内容实体。对于这条规则有一些例外的情况,比如对HEAD方法的响应和204 No Content, 304 Not Modofied, 205 Reset Content响应。
HttpClient区分3种类型的实体,取决于它们内容来源于哪里:

  • streamed:这种内容从一个流中被接收,或者实时地生成。特别的一点是,这个类别包含从HTTP响应接收到的实体。流式的实体通常是不可重复的。
  • self-contained:这种内容在内存中,或者从其它独立于连接或者其它实体的方式中获得。自包含的实体通常是可重复的。这种类型将会更多地被那些封装HTTP请求的实体使用。
  • wrapping:这种内容从另一个实体中获得。

当从一个HTTP响应中流出内容的时候,这种区分对于连接管理来说是重要的。对于一个由应用创建,并且只使用HttpClient发送的请求实体来说,streamedself-contained这两种之间的区别并不那么重要。在这种情况下,建议考虑将不可重复的实体作为streamed,将可重复的实体作为self-contained

1.1.4.1 可重复的实体

一个实体可以是可重复的,意味着它的内容可以不止一次地被读取。只有self-contained的实体才可以是可重复的(比如ByteArrayEntity或者StringEntity)。

1.1.4.2 使用HTTP实体

由于一个实体可以表示二进制和字符内容,所以它对字符编码有一定的支持。

为了从实体中读取内容,可以通过HttpEntity#getContent()方法从输入流中检索,进而返回一个java.io.InputStream,或者为HttpEntity#writeTo(OutputStream)方法提供一个输出流,进而将所有已写入到给定的流的内容一次性地返回。

当实体已经接收到一个进来的消息,HttpEntity#getContentType()方法和HttpEntity#getContentLength()方法可以被用来读取共同的元数据,比如Content-TypeContent-Length消息头(如果它们可用的话)。由于Content-Type消息头可以包含一个字符编码来指定文本的mime-type,比如:text/plain或者text/htmlHttpEntity#getContentEncoding()方法被用来读取这个信息。如果消息头不可用,那么一个为-1Content-Length将被返回,并且Content-Type的值将为NULL。如果Content-Type消息头可用,那么一个Header对象将被返回。

当为出去的消息创建一个实体的时候,这个元数据必须被实体的创建者提供。

StringEntity myEntity = new StringEntity("important message", 
   ContentType.create("text/plain", "UTF-8"));

System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);

输出 >

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

1.1.5 确保释放底层资源

为了确保适当地释放系统资源,必须关闭与实体相关的内容流或者响应本身。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        try {
            // do something useful
        } finally {
            instream.close();
        }
    }
} finally {
    response.close();
}

关闭内容流和关闭响应的区别在于,前者将在消费实体内容的时候来尝试保持底层的连接是活动的,而后者将立即关闭并抛弃连接。

请留意,在确保“一旦实体被完全写出,系统资源能够被适当地释放”的时候,HttpEntity#writeTo(OutputStream)方法是同样需要的。如果这个方法通过调用HttpEntity#getContent()来获取一个java.io.InputStream实例,那么在finally语句块中关闭流同样是需要的。

在使用流实体的时候,可以使用EntityUtils#consume(HttpEntity)方法来确保实体的内容已经被完全地消耗,并且底层的流已经被关闭。

然而,有一些情况是,当只有全部响应内容中的一小部分需要被检索的时候,消费其它的内容和使连接可重用的性能代价是很高的。在这种情况下,可以通过关闭响应来终止内容流。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        int byteOne = instream.read();
        int byteTwo = instream.read();
        // Do not need the rest
    }
} finally {
    response.close();
}

连接将不可被重用,但是它所持有的所有级别的资源都将被正确地回收。

1.1.6 消费实体内容

消费一个实体内容所推荐的方式是使用它的HttpEntity#getContent()或者HttpEntity#writeTo(OutputStream)方法。HttpClient也使用EntityUtils类来暴露若干静态的方法,使从一个实体中读取内容或者信息变得更容易。可以通过食用这个类中的方法来检索整个内容体到一个string或者byte数组,而不是直接去读java.io.InputStream。然而,对EntityUtils的使用是强烈不推荐的,除非响应实体来源于可信任的HTTP服务器,并且知道长度是有限的。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        long len = entity.getContentLength();
        if (len != -1 && len < 2048) {
            System.out.println(EntityUtils.toString(entity));
        } else {
            // Stream content out
        }
    }
} finally {
    response.close();
}

在一些情况下,能够不止一次地读取实体的内容是需要的。在这种情况下,实体内容必须被以某种方式缓存起来,或者在内存中或者在硬盘上。最简单的做法就是通过BufferedHttpEntity类来包装原始的实体。这将会使原始实体的内容被读入到内存缓存中。对于其它的任何方式,实体包装都会拥有原始的那个实体。

CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
    entity = new BufferedHttpEntity(entity);
}

1.1.7 生产实体内容

HttpClient提供若干的可使用的类来更高效地通过HTTP连接来流出内容。那些类的实体可以与实体包装的请求(比如POST和PUT)相关联,来将实体内容包装到出去的HTTP请求中。HttpClient为最常见的数据容器提供了若干的类,比如string, byte数组, 输入流和文件:StringEntity, ByteArrayEntity, InputStreamEntityFileEntity

File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, 
    ContentType.create("text/plain", "UTF-8"));        

HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);

请留意InputStreamEntity是不可重复的,因为它只能被一次性地从底层数据流中读取。通常来讲,实现一个自定义的self-contained的HttpEntity类而不使用通用InputStreamEntity是被推荐的做法。FileEntity会是个很好的起点。

1.1.7.1 HTML表单

许多的应用都需要模拟HTML表单提交的过程,比如,为了登录一个web应用或者提交input数据。HttpClient提供了实体类UrlEncodedEntity来帮助处理。

List formparams = new ArrayList();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);

UrlEncodedFormEntity实例将使用URL encoding来编码参数,并产生如下的内容:

param1=value1¶m2=value2

1.1.7.2 内容分片

通常来说,最好让HttpClient来选择最适合的传输编码,它会基于传输的HTTP消息的属性。然而,也可以通过设置HttpEntity#setChunked()为true来告诉HttpClient,我们倾向于使用chunk编码。请注意,HttpClient将仅把这个标识作为一个提示。在不支持chunk编码的HTTP协议版本(比如HTTP/1.0),这个值将被忽略。

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

1.1.8 响应处理器

处理响应最简单和最方便的方式就是使用ResponseHandler接口,它包含handleResponse(HttpResponse response)方法。这个方法将使用者从连接管理的担心中完全解脱出来。当使用一个ResponseHandler的时候,HttpClient将会自动地搞定这些,确保释放的连接返回到连接管理器中,而不管请求是否执行成功或者引发了异常。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");

ResponseHandler rh = new ResponseHandler() {

    @Override
    public JsonObject 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");
        }
        Gson gson = new GsonBuilder().create();
        ContentType contentType = ContentType.getOrDefault(entity);
        Charset charset = contentType.getCharset();
        Reader reader = new InputStreamReader(entity.getContent(), charset);
        return gson.fromJson(reader, MyJsonObject.class);
    }
};
MyJsonObject myjson = client.execute(httpget, rh);

1.2 HttpClient接口

HttpClient接口展示了HTTP请求执行最基本的结构。它不给请求的执行过程强加任何的限制或者特别的细节,并且对连接管理、状态管理、认证和重定向的处理使用各自独立的实现。这将使得使用额外的功能(比如响应内容缓存)去装饰接口变得更加简单。

通常来讲,HttpClient的实现表现的像许多特殊目的处理器的切面,或者策略接口的实现用于负责处理HTTP协议的一个特定的方面(比如,重定向、认证处理、对连接持久化的决定、保持活动持久)。这使使用者可以有选择的替换这些方面的默认实现,成为自定义的,应用特有的。

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {

    @Override
    public long getKeepAliveDuration(
            HttpResponse response,
            HttpContext context) {
        long keepAlive = super.getKeepAliveDuration(response, context);
        if (keepAlive == -1) {
            // Keep connections alive 5 seconds if a keep-alive value
            // has not be explicitly set by the server
            keepAlive = 5000;
        }
        return keepAlive;
    }

};
CloseableHttpClient httpclient = HttpClients.custom()
        .setKeepAliveStrategy(keepAliveStrat)
        .build();

1.2.1 HttpClient线程安全

HttpClient的实现被认为是线程安全的。推荐对于多请求的执行重用同一个这个类的实例。

1.2.2 HttpClient资源释放

当一个CloseableHttpClient实例不再被需要,并且将要从与之关联的连接管理器的范围内剔除,那么它一定要通过调用CloseableHttpClient#close()方法被关闭。

1.3 HTTP执行上下文

原始的HTTP被设计成为一个无状态的、面向“请求-响应”的协议。然而,真实世界的应用经常需要能够通过若干逻辑相关的“请求-响应”交换来保持信息的状态。为了使应用能够去维护一个处理状态,HttpClient允许HTTP请求在一个特定的执行环境中被执行,这个环境叫做HTTP上下文。多个逻辑相关的请求可以参与到一个逻辑会话中,如果连贯的请求重用了相同的上下文。HTTP上下文和java.util.Map很像。它简单来看就是一组任意的有名称的值的集合。一个应用可以将上下文属性置于请求执行之前或者在执行完成之后检查上下文。

HttpContext可以包含任意的对象,因此在多线程共享的时候是不安全的。建议为每个线程的执行维护一个它自己的上下文。

在HTTP请求执行的过程中,HttpClient为执行上下文添加了如下的属性:

  • HttpConnection实例表示真正的到目标服务器的连接
  • HttpHost实例表示连接目标
  • HttpRoute实例表示完整的连接路由
  • HttpRequest实例表示真正的HTTP请求。在执行上下文中最终的HttpRequest对象总是精确地指示着消息的状态,它被发送到目标服务器。按照默认的,HTTP/1.0和HTTP/1.1使用各自的请求URI。然而,如果请求在一个非隧道模式通过一个代理发送,那么URI将是绝对的。
  • HttpResponse实例表示真正的HTTP响应
  • java.lang.Boolean对象是一个标识,来表示真实的请求是否完全传输到连接的目标
  • RequestConfig对象表示真正的请求配置
  • java.util.List对象表示在请求处理过程中接收到的所有重定向位置的集合。

可以使用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 异常处理

HTTP协议处理器可以抛出两种类型的异常:java.io.IOException比如socket超时或重置导致的I/O失败情况,和HttpException比如违反HTTP协议的HTTP失败。通常I/O错误被认为是非致命和可恢复的,而HTTP协议错误被认为是致命的和不能自动回复的。请注意HttpClient实现将HttpException作为ClientProtocolException重新抛出,而它是java.io.IOException的一个子类。这使HttpClient的使用者可以通过一个catch语句块,处理I/O错误和协议犯规。

1.5.1 HTTP传输安全

去理解HTTP协议不适用于所有应用的类型这点是重要的。HTTP是一个简单的面向“请求/响应”的协议,最初被设计成支持静态或者动态生成的内容检索。它从未企图被设计成支持事务的操作。比如,HTTP服务器将认为协议的部分已完成,如果它连续地接收和处理请求,生成一个响应,并回传状态码到客户端。如果客户端由于读超时、请求取消或者系统崩溃,在它整个生命周期中都没有成功地接收响应,那么服务器不会去尝试回滚事务。如果客户端决定重试相同的请求,服务器将会不可避免地不止一次地终止执行同样的事务。在一些情况下,这将导致应用数据污染,或者应用状态的不一致。

虽然HTTP没有被设计成支持事务的处理,它仍旧可以在重要任务的应用中被作为一个传输协议来使用。为了确保HTTP传输层的安全,系统必须确保在应用层确保HTTP方法的幂等性。

1.5.2 幂等性方法

HTTP/1.1规范是这样定义一个幂等性方法:

[Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request]

换句话说,应用应该确保已经有准备处理同样方法多次执行所产生的影响。这可以被存档,比如通过提供一个唯一的事务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();

请注意,

你可能感兴趣的:(HTTP Client基础教程)