HttpClient 4.5教程学习与翻译——第1章:基础

原文链接

第1章:基础

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,TRACE和OPTIONS。 每种方法类型都有一个特定的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。

Request-URI是统一资源标识符,用于标识应用请求的资源。 HTTP请求URI由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成。

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消息可以包含许多描述消息属性的消息头,例如内容长度,内容类型等。 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"

它还提供了将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消息可以携带与请求或响应相关联的内容实体。 实体可以在某些请求和某些响应中找到,因为它们是可选的。 使用实体的请求称为封闭请求的实体(entity enclosing requests)。 HTTP规范定义了两个封闭请求方法的实体:POST和PUT。 通常期望响应包含内容实体。 此规则有例外,例如对HEAD方法的响应和204 No Content,304 Not Modified,205 Reset Content响应。

HttpClient区分三种实体,具体取决于其内容的来源:

  • 流式:内容从流中接收,或在运行中生成。 特别是,此类别包括从HTTP响应接收的实体。 流式实体通常不可重复。
  • 自包含:内容在内存中或通过独立于连接或其他实体的方式获得。 自包含实体通常是可重复的。 这种类型的实体主要用于封闭HTTP请求的实体。
  • 包装:内容从另一个实体获得。

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

1.1.4.1. 可重复实体

可重复实体意味着其内容可以被多次读取。 这仅适用于自包含实体(如ByteArrayEntity或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对象。

在为传出消息创建实体时,该元数据必须由实体的创建者提供。

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类,它公开了几种静态方法,以便更容易地从实体中读取内容或信息。 可以使用此类中的方法检索字符串/字节数组中的整个内容主体,而不是直接读取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类包装原始实体。 这将导致原始实体的内容被读入内存缓冲区。 在所有其他方式中,实体包装器(entity wrapper)将具有原始的实体包装器。

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

1.1.7. 创建实体内容

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

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不可重复,因为它只能从基础数据流中读取一次。 通常,建议实现自定义的HttpEntity类,而不是使用通用的InputStreamEntity。 FileEntity可以是一个很好的起点。

1.1.7.1. HTML 表单

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

List<NameValuePair> formparams = new ArrayList<NameValuePair>();
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编码对参数进行编码并生成以下内容:
param1=value1&param2=value2

1.1.7.2. 内容分块

通常,建议让HttpClient根据要传输的HTTP消息的属性选择最合适的传输编码。 但是,通过将HttpEntit#setChunked()设置为true,可以通知HttpClient优先使用块编码。 请注意,HttpClient将仅使用此标志作为提示。 使用不支持块编码的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<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {

    @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()方法关闭与其关联的连接管理器。

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    <...>
} finally {
    httpclient.close();
}

1.3. HTTP 执行上下文

最初,HTTP被设计为无状态,面向响应请求的协议。 但是,真实世界的应用程序通常需要能够通过几个逻辑上相关的请求-响应交换来持久保存状态信息。 为了使应用程序能够维持处理状态,HttpClient允许在特定的执行上下文中执行HTTP请求,称为HTTP上下文(HttpContext)。 如果在连续请求之间重用相同的上下文,则多个逻辑相关的请求可以参与逻辑会话。 HTTP上下文函数与java.util.Map<String, Object>类似。 它一个简单的名字-值的集合。 应用程序可以在请求执行之前填充上下文属性,也可以在执行完成后检查上下文。

HttpContext可以包含任意对象,因此在多个线程之间共享可能不安全。 建议每个执行线程都维护自己的上下文。
在HTTP请求执行过程中,HttpClient将以下属性添加到执行上下文中:

  • HttpConnection 实例表示与目标服务器的实际连接。
  • HttpHost 实例表示连接的目标。
  • HttpRoute 实例表示完整的连接路由
  • HttpReques t实例表示实际HTTP请求。 执行上下文中的最终HttpRequest对象始终表示消息的状态它与发送到目标服务器的状态完全相同。 默认HTTP/1.0和HTTP/1.1使用相对请求URI。 但是,如果请求是通过代理以非隧道模式发送的,那么URI将是绝对的。
  • HttpResponse实例表示实际的HTTP响应。
  • java.lang.Boolean对象,表示指示实际请求是否已完全传输到连接目标的标志。
  • RequestConfig对象表示实际的请求配置。
  • 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协议的特定方面的例程。通常,期望协议拦截器作用于传入消息的一个特定报头或一组相关报头,或者用一个特定报头或一组相关报头填充传出消息。协议拦截器也可以操纵用消息附带的内容实体-透明内容压缩/解压缩就是一个很好的例子。通常这是通过使用'Decorator'模式来完成的,其中包装器实体类用于装饰原始实体。可以组合几个协议拦截器以形成一个逻辑单元。

协议拦截器可以通过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在I/O失败的情况下抛出,例如套接字超时或套接字重置和HttpException,它表示HTTP故障,例如违反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> 0个相同请求的副作用与单个请求相同]

换句话说,应用程序应该确保它准备好处理同一方法的多次执行的含义。 例如,这可以通过提供唯一的事务id和通过避免执行相同逻辑操作的其他手段来实现。

请注意,此问题并非特定于HttpClient。 基于浏览器的应用程序与HTTP方法非幂等性相关的问题完全相同。

默认情况下,HttpClient假定只有非实体封闭方法(如GET和HEAD)是幂等的,而实体封闭方法(如POST和PUT)出于兼容性原因则不是。

1.5.3. 自动异常恢复

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

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

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而不是HttpClient 默认使用的那个,以便将RFC-2616定义为幂等的请求方法视为安全自动重试:GET,HEAD,PUT,DELETE,OPTIONS和TRACE。

1.6. 中止请求

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

1.7. 重定向处理程序

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

LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
        .setRedirectStrategy(redirectStrategy)
        .build();

HttpClient通常必须在执行过程中重写请求消息。 默认情况下,HTTP/1.0和HTTP/1.1通常使用相对请求URI。 同样,原始请求可能会多次从一个位置重定向到另一个位置。可以使用原始请求和上下文构建最终解释的绝对HTTP位置。实用程序URIUtils#resolution可构建用于生成最终请求的解释绝对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();
}

上一章: 前言
下一章: 链接管理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容