前言
超文本传输协议(HTTP)也许是当今互联网上使用的最重要的协议了。Web服务,有网络功能的设备和网络计算的发展,都持续扩展了HTTP协议的角色,超越了用户使用的Web浏览器范畴,同时,也增加了需要HTTP协议支持的应用程序的数量。
尽管java.net包提供了基本通过HTTP访问资源的功能,但它没有提供全面的灵活性和其它很多应用程序需要的功能。HttpClient就是寻求弥补这项空白的组件,通过提供一个有效的,保持更新的,功能丰富的软件包来实现客户端最新的HTTP标准和建议。
为扩展而设计,同时为基本的HTTP协议提供强大的支持,HttpClient组件也许就是构建HTTP客户端应用程序,比如web浏览器,web服务端,利用或扩展HTTP协议进行分布式通信的系统的开发人员的关注点。
1. HttpClient的范围
基于HttpCore[http://hc.apache.org/httpcomponents-core/index.html]的客户端HTTP运输实现库
基于经典(阻塞)I/O
内容无关
2. 什么是HttpClient不能做的
HttpClient不是一个浏览器。它是一个客户端的HTTP通信实现库。HttpClient的目标是发送和接收HTTP报文。HttpClient不会去缓存内容,执行嵌入在HTML页面中的javascript代码,猜测内容类型,重新格式化请求/重定向URI,或者其它和HTTP运输无关的功能。
第一章 基础
1.1 执行请求
HttpClient最重要的功能是执行HTTP方法。一个HTTP方法的执行包含一个或多个HTTP请求/HTTP响应交换,通常由HttpClient的内部来处理。而期望用户提供一个要执行的请求对象,而HttpClient期望传输请求到目标服务器并返回对应的响应对象,或者当执行不成功时抛出异常。
很自然地,HttpClient API的主要切入点就是定义描述上述规约的HttpClient接口。
这里有一个很简单的请求执行过程的示例:
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int l;
byte[] tmp = new byte[2048];
while ((l = instream.read(tmp)) != -1) {
}
}
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。
请求的URI是统一资源定位符,它标识了应用于哪个请求之上的资源。HTTP请求URI包含一个协议模式,主机名称,可选的端口,资源路径,可选的查询和可选的片段。
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供很多工具方法来简化创建和修改执行URI。
URI也可以编程来拼装:
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
"q=httpclient&btnG=Google+Search&aq=f&oq=", null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
输出内容为:
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
查询字符串也可以从独立的参数中来生成:
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
qparams.add(new BasicNameValuePair("q", "httpclient"));
qparams.add(new BasicNameValuePair("btnG", "Google Search"));
qparams.add(new BasicNameValuePair("aq", "f"));
qparams.add(new BasicNameValuePair("oq", null));
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
URLEncodedUtils.format(qparams, "UTF-8"), null);
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"
获得给定类型的所有头部信息最有效的方式是使用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报文可以携带和请求或响应相关的内容实体。实体可以在一些请求和响应中找到,因为它们也是可选的。使用了实体的请求被称为封闭实体请求。HTTP规范定义了两种封闭实体的方法:POST和PUT。响应通常期望包含一个内容实体。这个规则也有特例,比如HEAD方法的响应和204 No Content,304 Not Modified和205 Reset Content响应。
HttpClient根据其内容出自何处区分三种类型的实体:
streamed流式:内容从流中获得,或者在运行中产生。特别是这种分类包含从HTTP响应中获取的实体。流式实体是不可重复生成的。
self-contained自我包含式:内容在内存中或通过独立的连接或其它实体中获得。自我包含式的实体是可以重复生成的。这种类型的实体会经常用于封闭HTTP请求的实体。
wrapping包装式:内容从另外一个实体中获得。
当从一个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",
"UTF-8");
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.getContentCharSet(myEntity));
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);
输出内容为
Content-Type: text/plain; charset=UTF-8
17
UTF-8
important message
17
1.1.5 确保低级别资源释放
当完成一个响应实体,那么保证所有实体内容已经被完全消耗是很重要的,所以连接可以安全的放回到连接池中,而且可以通过连接管理器对后续的请求重用连接。处理这个操作的最方便的方法是调用HttpEntity#consumeContent()方法来消耗流中的任意可用内容。HttpClient探测到内容流尾部已经到达后,会立即会自动释放低层连接,并放回到连接管理器。HttpEntity#consumeContent()方法调用多次也是安全的。
也可能会有特殊情况,当整个响应内容的一小部分需要获取,消耗剩余内容而损失性能,还有重用连接的代价太高,则可以仅仅通过调用HttpUriRequest#abort()方法来中止请求。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
httpget.abort();
}
连接不会被重用,但是由它持有的所有级别的资源将会被正确释放。
1.1.6 消耗实体内容
推荐消耗实体内容的方式是使用它的HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。HttpClient也自带EntityUtils类,这会暴露出一些静态方法,这些方法可以更加容易地从实体中读取内容或信息。代替直接读取java.io.InputStream,也可以使用这个类中的方法以字符串/字节数组的形式获取整个内容体。然而,EntityUtils的使用是强烈不鼓励的,除非响应实体源自可靠的HTTP服务器和已知的长度限制。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
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
}
}
在一些情况下可能会不止一次的读取实体。此时实体内容必须以某种方式在内存或磁盘上被缓冲起来。最简单的方法是通过使用BufferedHttpEntity类来包装源实体完成。这会引起源实体内容被读取到内存的缓冲区中。在其它所有方式中,实体包装器将会得到源实体。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}
1.1.7 生成实体内容
HttpClient提供一些类,它们可以用于生成通过HTTP连接获得内容的有效输出流。为了封闭实体从HTTP请求中获得的输出内容,那些类的实例可以和封闭如POST和PUT请求的实体相关联。HttpClient为很多公用的数据容器,比如字符串,字节数组,输入流和文件提供了一些类:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, "text/plain; charset=\"UTF-8\"");
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
请注意InputStreamEntity是不可重复的,因为它仅仅能从低层数据流中读取一次内容。通常来说,我们推荐实现一个定制的HttpEntity类,这是自我包含式的,用来代替使用通用的InputStreamEntity。FileEntity也是一个很好的起点。
1.1.7.1 动态内容实体
通常来说,HTTP实体需要基于特定的执行上下文来动态地生成。通过使用EntityTemplate实体类和ContentProducer接口,HttpClient提供了动态实体的支持。内容生成器是按照需求生成它们内容的对象,将它们写入到一个输出流中。它们是每次被请求时来生成内容。所以用EntityTemplate创建的实体通常是自我包含而且可以重复的。
ContentProducer cp = new ContentProducer() {
public void writeTo(OutputStream outstream) throws IOException {
Writer writer = new OutputStreamWriter(outstream, "UTF-8");
writer.write("<response>");
writer.write(" <content>");
writer.write(" important stuff");
writer.write(" </content>");
writer.write("</response>");
writer.flush();
}
};
HttpEntity entity = new EntityTemplate(cp);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
1.1.7.2 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, "UTF-8");
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
UrlEncodedFormEntity实例将会使用URL编码来编码参数,生成如下的内容:
param1=value1¶m2=value2
1.1.7.3 内容分块
通常,我们推荐让HttpClient选择基于被传递的HTTP报文属性的最适合的编码转换。这是可能的,但是,设置HttpEntity#setChunked()方法为true是通知HttpClient分块编码的首选。请注意HttpClient将会使用标识作为提示。当使用的HTTP协议版本,如HTTP/1.0版本,不支持分块编码时,这个值会被忽略。
StringEntity entity = new StringEntity("important message",
"text/plain; charset=\"UTF-8\"");
entity.setChunked(true);
HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
httppost.setEntity(entity);
1.1.8 响应控制器
控制响应的最简便和最方便的方式是使用ResponseHandler接口。这个方法完全减轻了用户关于连接管理的担心。当使用ResponseHandler时,HttpClient将会自动关闭并保证释放连接到连接管理器中去,而不管请求执行是否成功或引发了异常。
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
public byte[] handleResponse(
HttpResponse response) throws ClientProtocolException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
return EntityUtils.toByteArray(entity);
} else {
return null;
}
}
};
byte[] response = httpclient.execute(httpget, handler);
1.2
HTTP客户端接口展示了最基本的HTTP请求执行规范。他没有限定请求执行的具体的细节,把连接管理,状态管理,鉴权和重定向处理的细节留给了个人实现。这样就很容易去给接口加一些额外功能,例如响应缓存。
通常HTTP客户端充当一个处理大量具体用途(负责处理HTTP协议的某个具体方面,比如重定向、鉴权处理、连接持久化、保活时间)的处理程序的外壳。这使用户能够有选择的个性化的去替换某些方面的实现,为应用做定制。
1.2.1. HttpClient thread safety
HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions.
HttpClient实现是线程安全的。推荐这个类的实例被多线程的请求重复使用。
1.2.2. HttpClient resource deallocation
When an instance CloseableHttpClient is no longer needed and is about to go out of scope the connection manager associated with it must be shut down by calling the CloseableHttpClient#close() method.
当一个CloseableHttpClient实例没用了,或者不再用连接管理器管理了,必须调用close方式关闭他。
1.3 HTTP执行的环境
最初,HTTP是被设计成无状态的,面向请求-响应的协议。然而,真实的应用程序经常需要通过一些逻辑相关的请求-响应交换来持久状态信息。为了开启应用程序来维持一个过程状态,HttpClient允许HTTP请求在一个特定的执行环境中来执行,简称为HTTP上下文。如果相同的环境在连续请求之间重用,那么多种逻辑相关的请求可以参与到一个逻辑会话中。HTTP上下文功能和java.util.Map<String,Object>很相似。它仅仅是任意命名参数值的集合。应用程序可以在请求之前或在检查上下文执行完成之后来填充上下文属性。
在HTTP请求执行的这一过程中,HttpClient添加了下列属性到执行上下文中:
‘http.connection‘:HttpConnection实例代表了连接到目标服务器的真实连接。
‘http.target_host‘:HttpHost实例代表了连接目标。
‘http.proxy_host‘:如果使用了,HttpHost实例代表了代理连接。
‘http.request‘:HttpRequest实例代表了真实的HTTP请求。
‘http.response‘:HttpResponse实例代表了真实的HTTP响应。
‘http.request_sent‘:java.lang.Boolean对象代表了暗示真实请求是否被完全传送到目标连接的标识。
比如,为了决定最终的重定向目标,在请求执行之后,可以检查http.target_host属性的值:
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpHost target = (HttpHost) localContext.getAttribute(
ExecutionContext.HTTP_TARGET_HOST);
System.out.println("Final target: " + target);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity.consumeContent();
}
输出内容为:
Final target: http://www.google.ch
1.4 异常处理
HttpClient能够抛出两种类型的异常:在I/O失败时,如套接字连接超时或被重置的java.io.IOException异常,还有标志HTTP请求失败的信号,如违反HTTP协议的HttpException异常。通常I/O错误被认为是非致命的和可以恢复的,而HTTP协议错误则被认为是致命的而且是不能自动恢复的。
1.4.1 HTTP运输安全
要理解HTTP协议并不是对所有类型的应用程序都适合的,这一点很重要。HTTP是一个简单的面向请求/响应的协议,最初被设计用来支持取回静态或动态生成的内容。它从未向支持事务性操作方向发展。比如,如果成功收到和处理请求,HTTP服务器将会考虑它的其中一部分是否完成,生成一个响应并发送一个状态码到客户端。如果客户端因为读取超时,请求取消或系统崩溃导致接收响应实体失败时,服务器不会试图回滚事务。如果客户端决定重新发起这个请求,那么服务器将不可避免地不止一次执行这个相同的事务。在一些情况下,这会导致应用数据损坏或者不一致的应用程序状态。
尽管HTTP从来都没有被设计来支持事务性处理,但它也能被用作于一个传输协议对关键的任务应用提供被满足的确定状态。要保证HTTP传输层的安全,系统必须保证HTTP方法在应用层的幂等性。
1.4.2 幂等的方法
HTTP/1.1 明确地定义了幂等的方法,描述如下
[方法也可以有“幂等”属性在那些(除了错误或过期问题)N的副作用>0的相同请求和独立的请求是相同的]
换句话说,应用程序应该保证准备着来处理多个相同方法执行的实现。这是可以达到的,比如,通过提供一个独立的事务ID和其它避免执行相同逻辑操作的方法。
请注意这个问题对于HttpClient是不具体的。基于应用的浏览器特别受和非幂等的HTTP方法相关的相同问题的限制。
HttpClient假设没有实体包含方法,比如GET和HEAD是幂等的,而实体包含方法,比如POST和PUT则不是。
1.4.3 异常自动恢复
默认情况下,HttpClient会试图自动从I/O异常中恢复。默认的自动恢复机制是受很少一部分已知的异常是安全的这个限制。
HttpClient不会从任意逻辑或HTTP协议错误(那些是从HttpException类中派生出的)中恢复的。
HttpClient将会自动重新执行那么假设是幂等的方法。
HttpClient将会自动重新执行那些由于运输异常失败,而HTTP请求仍然被传送到目标服务器(也就是请求没有完全被送到服务器)失败的方法。
HttpClient将会自动重新执行那些已经完全被送到服务器,但是服务器使用HTTP状态码(服务器仅仅丢掉连接而不会发回任何东西)响应时失败的方法。在这种情况下,假设请求没有被服务器处理,而应用程序的状态也没有改变。如果这个假设可能对于你应用程序的目标Web服务器来说不正确,那么就强烈建议提供一个自定义的异常处理器
1.4.4 请求重试处理
为了开启自定义异常恢复机制,应该提供一个HttpRequestRetryHandler接口的实现。
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception,
int executionCount,HttpContext context) {
if (executionCount >= 5) {
// 如果超过最大重试次数,那么就不要继续了
return false;
}
if (exception instanceof NoHttpResponseException) {
// 如果服务器丢掉了连接,那么就重试
return true;
}
if (exception instanceof SSLHandshakeException) {
// 不要重试SSL握手异常
return false;
}
HttpRequest request = (HttpRequest) context.getAttribute(
ExecutionContext.HTTP_REQUEST);
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// 如果请求被认为是幂等的,那么就重试
return true;
}
return false;
}
};
httpclient.setHttpRequestRetryHandler(myRetryHandler);
1.5 中止请求
在一些情况下,由于目标服务器的高负载或客户端有很多活动的请求,那么HTTP请求执行会在预期的时间框内而失败。这时,就可能不得不过早地中止请求,解除封锁在I/O执行中的线程封锁。被HttpClient执行的HTTP请求可以在执行的任意阶段通过调用HttpUriRequest#abort()方法而中止。这个方法是线程安全的,而且可以从任意线程中调用。当一个HTTP请求被中止时,它的执行线程就封锁在I/O操作中了,而且保证通过抛出InterruptedIOException异常来解锁。
1.6. Aborting requests
In some situations HTTP request execution fails to complete within the expected time frame due to high load on the target server or too many concurrent requests issued on the client side. In such cases it may be necessary to terminate the request prematurely and unblock the execution thread blocked in a I/O operation. HTTP requests being executed by HttpClient can be aborted at any stage of execution by invoking HttpUriRequest#abort()
method. This method is thread-safe and can be called from any thread. When an HTTP request is aborted its execution thread - even if currently blocked in an I/O operation - is guaranteed to unblock by throwing a InterruptedIOException
有时候HTTP请求会在期望的时间内无法完成,因为目标服务的高负荷或者客户端有太多的并发请求。这时,可能需要去过早的中断请求和解锁被IO操作阻塞的线程。HttpClient能够用HttpUriRequest#abort()方法在任何执行阶段丢弃HTTP请求。这个方法是线程安全的,并且可以被任何线程调用。当一个请求被丢弃,他的执行线程应该保证解锁并抛出一个InterruptedIOException异常。
1.7. Redirect handling
HttpClient handles all types of redirects automatically, except those explicitly prohibited by the HTTP specification as requiring user intervention. See Other
(status code 303) redirects on POST
and PUT
requests are converted to GET
requests as required by the HTTP specification. One can use a custom redirect strategy to relaxe restrictions on automatic redirection of POST methods imposed by the HTTP specification.
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); CloseableHttpClient httpclient = HttpClients.custom() .setRedirectStrategy(redirectStrategy) .build();
HttpClient often has to rewrite the request message in the process of its execution. Per default HTTP/1.0 and HTTP/1.1 generally use relative request URIs. Likewise, original request may get redirected from location to another multiple times. The final interpreted absolute HTTP location can be built using the original request and the context. The utility method URIUtils#resolve
can be used to build the interpreted absolute URI used to generate the final request. This method includes the last fragment identifier from the redirect requests or the original request.
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(); }