前言
超文本传输协议(HTTP)可能是当今Internet上使用的最重要的协议。Web服务、网络支持的设备和网络计算的增长继续扩展了HTTP协议在用户驱动的Web浏览器之外的作用,同时增加了需要HTTP支持的应用程序的数量。尽管java.net包提供了通过HTTP访问资源的基本功能,但它并没有提供许多应用程序所需的全部灵活性或功能。HttpClient通过提供高效的、最新的和特性来填补这一空白。
HttpClient 不是浏览器。它是客户端 HTTP 传输库。HttpClient 的目的是传输和接收 HTTP 消息。 如果没有明确设置, 或者重新格式化请求(request)/重写location URIs, 或其他与 HTTP 传输无关的功能,HttpClient 不会尝试处理内容, 执行嵌入在 HTML 页面中的 javascript, 尝试猜测内容类型(content type),
Chapter 1. Fundamentals
1.1 执行请求
HttpClient的主要功能是执行HTTP方法。通常HttpClient的内部处理执行的HTTP方法包括一个或多个HTTP request / HTTP response交换。用户提供请求对象,HttpClient将请求发送到目标服务器,成功则返回对应的应答对象,失败则抛出一个异常。
很显然,HttpClient API主入口点的是定义了上述请求协议的HttpClient接口。
HttpClient接口源码定义:
//此接口仅表示 HTTP 请求的最基本协议
//它没有对请求施加任何限制或具体细节
//具体细节由各个实现类来完成
public interface 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规格中的定义的方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。HttpClient中有一个具体对于每个类型的方法:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace,和HttpOptions。
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());
stdout (输出结果)>
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());
stdout >
HTTP/1.1
200
OK
HTTP/1.1 200 OK
1.1.3. 处理消息请求头(headers)
HTTP 消息可以包含许多描述消息属性内容:如内容长度、内容类型等。HttpClient提供了检索、添加、移除和枚举headers(请求头)的方法。笔者使用的火狐开发者版的F12(大部分浏览器都是F12)查看这方面信息的。
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);
stdout >
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());
}
stdout >
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]);
}
}
stdout >
c1 = a
path=/
domain=localhost
c2 = b
path=/
c3 = c
domain=localhost
1.1.4. HTTP entity
HTTP 消息可以携带与请求或响应关联的内容实体。实体可以在某些请求和某些响应中找到,因为它们是可选的。使用实体的请求称为实体封闭请求。HTTP请求实体规范定义了两种封闭方法:POST和PUT。
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 () 方法来读取公共元数据(common metadata), 如内容类型(Content-Type )和内容长度(Content-Length) (如果可用)。由于内容类型标头可以包含文本 mime 类型 (如文本/普通或文本/html) 的字符编码, 因此使用 HttpEntity # getContentEncoding () 方法来读取此信息。如果标头不可用, 将返回-1 的长度, 内容类型为 NULL。如果内容类型标头可用, 则将返回一个标头对象。
当为传出消息创建实体时, 该元数据必须由该实体的创建者提供。
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);
stdout >
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。但是, 除非响应实体源自受信任的 HTTP 服务器并且已知长度有限, 否则强烈劝阻使用 EntityUtils。
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) 相关联, 并将实体内容包含在传出的 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¶m2=value2
1.1.7.1. 内容分块
通常, 建议让 HttpClient 根据传输的 HTTP 消息的属性选择最合适的传输编码。但是, 可以通过将 HttpEntity # 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. 处理响应
处理响应的最简单和最方便的方法是使用包括 handleResponse(HttpResponse response)方法的 ResponseHandler接口。这种方法完全免除了用户对连接管理的担心。使用 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 请求。如果在连续请求之间重用相同的上下文, 则多个逻辑上相关的请求可以参与逻辑会话。HTTP 上下文的功能类似于 java.util.Map 的
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 在出现 i/o 故障 (如套接字超时) 或套接字重置时引发和 HttpException (如违反 http 协议) 的情况下引发。通常, i/o 错误被认为是非致命的和可恢复的, 而 HTTP 协议错误被认为是致命的, 无法自动恢复。请注意, HttpClient 实现引发 HttpExceptions 为 ClientProtocolException, 它是 java.io.IOException 的子类。这使 HttpClient 的使用者能够处理来自单个 catch 子句的 i/o 错误和协议冲突。
1.5.1 HTTP 传输安全
理解HTTP 协议不适合于所有类型的应用程序是很重要的。HTTP 是一种简单的请求/响应协议, 最初是为支持静态或动态生成的内容检索而设计的。它从未打算支持事务性操作。例如, 如果 HTTP 服务器成功地接收和处理请求、生成响应并将状态代码发送回客户端,则它将考虑其履行协议的一部分。如果客户端由于读取超时、请求取消或系统崩溃而无法接收整个响应, 则服务器将不尝试回滚事务。如果客户端决定重试同一请求,服务器将不可避免地会多次执行同一个事务。在某些情况下,这可能导致应用程序数据损坏或应用程序状态不一致。(PS:Spring框架中的事务管理就是为了解决这个问题)
尽管 HTTP 从未被设计为支持事务性处理,但它仍然可以用作任务关键型应用程序的传输协议, 前提是满足某些条件。为了确保 http 传输层的安全, 系统必须确保应用程序层上的 http 方法。
1.5.2 幂等方法(个人不是很理解)
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而不是默认使用的HandlerRequestRetryHandler,以便通过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 规范明文禁止的那些重定向必须要求用户干预。可以使用自定义重定向策略来放宽对HTTP规范强加的POST方法的自动重定向的限制。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(redirectStrategy)
.build();
HttpClient 通常必须在执行过程中重写请求消息。默认情况下,HTTP/1.0和HTTP/1.1通常使用相对请求URI。 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();
}