HttpClient4.5.x 翻译

前言(译者注)

该文档原文地址。适用于HttpClient4.5.x 版本

1.1. 发送请求

HttpClient最基本的的功能就是发送HTTP请求。发送一次HTTP方法通常由HttpClient内部处理一次或多次HTTP请求/HTTP响应的交互。使用者将使用一个请求对象去执行HTTP请求,HttpClient将传输该请求到目的服务器,然后得到一个响应结果,或者如果出现失败情况则抛出异常。

自然而然,HttpClient最主要的入口就是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 Method:GET,HEAD,POST,PUT,DELETE和OPTIONS。每个Method都有对应的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace,和 HttpOptions。

请求URI就是统一资源标识符,用来标识请求的资源。而HTTP请求URI包含了协议schema,域名名字,可选的端口,资源路径,可选参数和可选的fragment。

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

HttpClient提供了URIBuidler实用类来创建和修改请求URIs。

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\"");
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"
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"

HeaderIterator同时还提供了简便的方法来逐个解析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使用两种entity enclosing requests方法:POST 和 PUT。HTTP应答通常会有内容实体,当然除了这几种:HEAD 方法和204 No Content,304 Not Modified,205 Reset Content 响应。
HttpClient根据内容源来区分三种内容实体(译者注:存在理解局限,难免有误差)。

  • streamed:内容从流中获取,不可重复。
  • self-contained:内容来源内存或者从连接中独立获取,该内容实体属于可重复,也常在HTTP请求中使用。
  • wrapping:从另一个内容实体获取。

当连接管理器(connection management)从HTTP响应读取流内容时,区分以上三种内容实体是很重要的。对于请求实体来说只是由Application创建并且只是由HttpClient发送,streamed和self-contained差别不大,这种情况下,建议把不可重复的当作streamed,把可重复的当作self-contained。

1.1.4.1. 可重复内容实体

一个内容实体可以重复,表示它的内容可以读取多次。只有self-contained能做得到(比如ByteArrayEntity或者StringEntity)

1.1.4.2. 使用HTTP实体

由于一个实体可以表达二进制和字符内容,所有它必须支持字符编码(为了支持后者,即字符内容)。

实体被创建有几种情况,一种是携带enclosed content的请求,另一种是请求成功并且成功返回给客户端的响应报文。

读取实体内容方法有几种,从 HttpEntity#getContent()方法直接获取 java.io.InputStream,或者使用 HttpEntity#writeTo(OutputStream) 方法,该方法将会向对应的OutputStream写出所有内容。

为了读取响应的内容,我们可以通过 HttpEntity#getContentType() 和 HttpEntity#getContentLength() 获取一些公共元数据:Content-Type 和 Content-Length 等头部信息。由于Content-Type头部包含了文本媒体类型(比如text/plain或者text/html)的字符编码,也可以使用 HttpEntity#getContentEncoding 获取该值。如果该头部不存在,contentLength将返回-1,contentType将返回 NULL。如果Content-Type是有效的,则返回Header对象。

当输出一个outgoing消息时,这些元数据必须提供。

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. 确保释放资源

为了确保释放系统资源,调用者必须关闭content stream或者与其相关联的的repsonse对象。

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();
}

关闭content stream和关闭response对象的区别在于前者尽可能保持连接可用,而后者则直接直接关闭连接并释放连接。(译者注:建议使用EntityUtils#consume 来释放stream来达到连接复用的目的)

注意即使使用HttpEntity#writeTo(OutputStream)方法,当把消息体全部写到输出流后,需要确保释放系统资源。如果此方法通过HttpEntity#getContent()获取一个java.io.InputStream实例,也需要在finally代码块关闭该流。

当处理流实体时,可以使用EntityUtils#consume(HttpEntity)方法来确保实体内容被消费,并且底层的stream已被关闭。

某些情况下,我们只需要返回实体的一小部分,并且消费其余内容和连接复用的成本太高,这样的情况下,我们只需直接关闭response即可(译者注:下方例子只需读取头两个字节,读取完毕后,直接关闭response即可)。

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) 方法来读取HTTP实体。HttpClient 同时也提供EntityUtils类,该类暴露了几个简易的静态方法来读取实体的信息和内容。我们可以直接通过该类来读取string/byte array不同类型的实体内容,而不是直接通过java.io.InputStream。但是,一般不建议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类,它会将原始内容读入in-memory 缓存中。

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

1.1.7. 生产实体内容

HttpClient提供了几个一些高效的类方便为HTTP连接中输出内容,这些类的实例可以关联一些entity enclosing类型的HTTP请求,比如POST和PUT请求。HttpClient也提供了一些公共的数据容器类,比如字符串,字节数组,输入流和文件:StringEntiy,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表单提交,比如,为了网站登陆或者提交数据,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 encoding去编码参数和生成以下内容:

param1=value1&param2=value2
1.1.7.2. Content chunking

通常建议HttpClient基于传输的HTTP消息体来选择合适的编码方式,不过当要使用chunk coding时可设置HttpEntity#setChunked()为true。不过请注意HttpClient只是使用该值作为提示而已。该值将会被忽略在某些不支持chunking coding的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. 返回处理

处理HTTP返回的最简便方法就是使用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的实现就是facade模式,来处理某些HTTP协议相关的,比如 跳转,认证,连接复用,keep alive时间等。用户可自行替换以上所提的点,而不用默认的实现。

 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<String, Object>对象,一些任意命名的集合。应用可以在请求执行之前填充上下文内容,或者在执行完成后检查其内容。
HttpContext是非线程安全的,建议每个线程自己维护自己的上下文内容。
在HTTP执行请求的过程中,HttpClient在执行上下文中添加以下属性:

  • HttpConnection实例代表目标服务的真实底层连接
  • HttpHost实例代表连接目标
  • HttpRoute实例代表完整的连接路线
  • HttpRequest实例代表真实HTTP请求。在执行上下文中不可变的HttpRequest对象总是代表消息的状态,默认HTTP/1.0 和 HTTP/1.1 使用相对路径URIs,但是如果请求使用non-tunneling模式使用的绝对路径的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实例,来确保请求之间通话环境和状态信息的自动传播,这样这些不同请求就是一个逻辑相关的session通话。
下面例子中,就是在连续请求共用同一个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上下文,来达到为单个或多个连续请求保存一个处理进行时状态。
如果拦截器不依赖执行上下文某个特定的状态,那么拦截器执行的先后顺序是无关紧要的。相反,如果拦截器是相互依赖,必须在某个特定的顺序执行。那么它们必须按照相同的顺序加入到协议处理器中。

协议拦截器必须是线程安全的。跟Servlets相似,协议拦截器不应该使用对象属性,除非对其操作都是同步的。

下面例子就是怎么在连续请求中使用一个本地上下文对象。

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问题,比如socket 超时,socket reset;HttpException,该异常是非法的HTTP请求。通常我们认为I/O异常是非致命的都是可以处理的,但HttpException则是致命的而且不会自动处理。HttpClient在实现中使用ClientProtocolException来达到再次抛出HttpException,ClientProtocolException是java.io.IOException的子类,所以使用HttpClient时可以使用一个catch语句(译者注:就是catch ClientProtocolException)来处理I/O异常和协议非法异常。

1.5.1. HTTP安全传输

HTTP协议并不适合所有类型的应用,HTTP在设计之初目标是基于请求——返回,支持静态或者动态内容检索的协议,从不支持事务性的操作。举个例子,如果服务端成功接收到客户端请求并且处理该请求后,生成返回内容和状态码给客户端,那么服务端的任务就已经结束了。即使客户端接收返回内容失败,比如读超时,请求取消或者系统宕机,服务端也不会回滚事务。如果客户端再次请求相同请求,那么服务端将会不可避免再次执行相同的处理。在某些情况下,这样的操作就会导致应用的数据错误,或者应用状态的不一致。

尽管HTTP从未设计支持事务处理,但如果满足某些条件,它仍然可以用作关键任务应用程序的传输协议。为了确保HTTP传输层安全,系统必须确保HTTP方法在应用层上的幂等性。

1.5.2. 幂等方法

HTTP/1.1 对幂等方法对定位如下:
[请求可以有幂等属性,所谓幂等就是正常多次请求(排除错误和异常)的效果跟单次请求的效果是一样的]
换句话说,应用应该处理好多次执行同个方法的情况,这是可以实现的,比如,通过唯一的 transaction id来避免其他请求执行相同逻辑操作。
请注意这个问题并不局限于HttpClient。基于浏览器的应用同样碰到HTTP方法非幂等性的问题。
默认,HttpClient假定不带实体的enclosing方法,比如GET和HEAD就是幂等的方法,而POST和PUT就是非幂等的。

1.5.3. 自动异常处理

默认,HttpClient会自动处理I/O异常,自动处理策略局限于以下少量已知安全的异常。

  • HttpClient不会从任何逻辑或者HTTP协议错误(衍生自HttpException子类)中恢复
  • 如果方法是幂等的,HttpClient会自动重试恢复
  • 如果是传输异常,并且链接是可到达的,那么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();

请注意,如果请求是按照RFC-2616来实现,那么用户可以使用StandardHttpRequestRetryHandler,该类会自动重试: GET, HEAD, PUT, DELETE, OPTIONS, 和 TRACE类型请求,而不使用默认的。

1.6. 中止请求

在某些情况下,HTTP请求由于服务端高负载或者客户端过多平行请求导致无法在指定时间内完成,这样就有必要提前结束请求,释放I/O操作的阻塞线程。HttpClient的HTTP请求可以在任何状态的执行期通过HttpUriRequest#abort() 来中止。该方法是线程安全的,当HTTP请求被中止时,即使其对应的线程是I/O阻塞的,也可以通过抛出InterruptedIOException来确保释放。

1.7. 重定向处理

HttpClient会自动处理所有类型的重定向,除非HTTP声明被用户显式禁止。See other(303状态码)按照HTTP声明,POST和PUT请求将会被转成GET请求,用户可以自行修改其默认HTTP声明的限制。

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

HttpClient在执行过程中经常需要rewrite其请求消息。HTTP/1.0和HTTP/1.1一般都是使用相对路径URIs。同样,原始请求可能从一个地址多次重定向。最终生效的绝对路径地址可以使用原始地址和上下文来获取。工具方法URIUtils#resolve可以用来生成最终的绝对路径URI,最终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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值