leftso 4327 0 2018-07-11 14:26:19

logo-cover-HttpClient 4.x 使用详解(基础篇)

1.httpclient获取请求响应的状态码

在发送Http请求之后 - 我们得到一个org.apache.http.HttpResponse的实例- 它允许我们访问响应的状态行,并隐含地显示状态码:
response.getStatusLine().getStatusCode()
使用这个,我们可以验证我们从服务器收到的代码确实是正确的
@Test
public void givenGetRequestExecuted_whenAnalyzingTheResponse_thenCorrectStatusCode() 
  throws ClientProtocolException, IOException {
    HttpClient client = HttpClientBuilder.create().build();    
    HttpResponse response = client.execute(new HttpGet(SAMPLE_URL));
    int statusCode = response.getStatusLine().getStatusCode();
    assertThat(statusCode, equalTo(HttpStatus.SC_OK));
}

请注意,我们通过org.apache.http.HttpStatus使用库中预定义的状态码

2.配置httpclient超时

2.1通过原始字符串参数配置超时

HttpClient的自带了大量的配置参数-所有这些都可以在一个通用的,地图状的方式来设置。

3个超时参数需要配置

DefaultHttpClient httpClient = new DefaultHttpClient();
 
int timeout = 5; // seconds
HttpParams httpParams = httpClient.getParams();
httpParams.setParameter(
  CoreConnectionPNames.CONNECTION_TIMEOUT, timeout * 1000);
httpParams.setParameter(
  CoreConnectionPNames.SO_TIMEOUT, timeout * 1000);
// httpParams.setParameter(
//   ClientPNames.CONN_MANAGER_TIMEOUT, new Long(timeout * 1000));

注意:由于此JIRA(4.3.2中的规定),最后一个参数 - 连接管理器超时 - 在使用4.3.0或4.3.1版本时被注释掉了。

2.2通过API配置超时

这些参数中最重要的参数 - 前两个 - 也可以通过更安全的API来设置:
DefaultHttpClient httpClient = new DefaultHttpClient();
 
int timeout = 5; // seconds
HttpParams httpParams = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(
  httpParams, timeout * 1000); // http.connection.timeout
HttpConnectionParams.setSoTimeout(
  httpParams, timeout * 1000); // http.socket.timeout
第三个参数在HttpConnectionParams中没有自定义设置器,它仍然需要通过setParameter方法手动设置

2.3使用新的4.3配置超时。生成器

4.3中介绍的流畅的构建器API提供了在较高级别设置超时的正确方法
int timeout = 5;
RequestConfig config = RequestConfig.custom()
  .setConnectTimeout(timeout * 1000)
  .setConnectionRequestTimeout(timeout * 1000)
  .setSocketTimeout(timeout * 1000).build();
CloseableHttpClient client = 
  HttpClientBuilder.create().setDefaultRequestConfig(config).build();

2.4超时属性说明

现在,我们来解释这些不同类型的超时的含义:

  • Connection Timeout (http.connection.timeout) -建立与远程主机的连接的时间
  • Socket Timeout (http.socket.timeout) -的时间等待数据-建立连接之后; 两个数据包之间不活动的最长时间
  • Connection Manager Timeout (setConnectionRequestTimeout)(http.connection-manager.timeout) -connect Manager获取Connection 超时时间。单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。

前两个参数 - 连接和套接字超时 - 是最重要的,但为获得连接设置超时在高负载情况下显然非常重要,这就是为什么第三个参数不应该被忽略的原因。

2.5使用HttpClient

配置完成后,客户端不能用于执行HTTP请求:
HttpGet getMethod = new HttpGet("http://host:8080/path");
HttpResponse response = httpClient.execute(getMethod);
System.out.println(
  "HTTP Status of response: " + response.getStatusLine().getStatusCode());

使用先前定义的客户端,与主机的连接将在5秒内超时,如果连接已建立但未收到数据,则超时也将增加5秒

请注意,连接超时将导致org.apache.http.conn.ConnectTimeoutException被抛出,而套接字超时将导致一个java.net.SocketTimeoutException

2.6硬超时

虽然在建立HTTP连接和不接收数据时设置超时非常有用,但有时我们需要为整个请求设置硬超时

例如,下载一个潜在的大文件适合这个类别 - 在这种情况下,连接可能会成功建立,数据可能会一直通过,但我们仍然需要确保操作不会超过某个特定时间阈。

HttpClient没有任何允许我们为请求设置总超时的配置; 但它确实为请求提供了异常功能,所以我们可以利用该机制来实现一个简单的超时机制:
 

HttpGet getMethod = new HttpGet(
  "http://localhost:8080/spring-security-rest-template/api/bars/1");
 
int hardTimeout = 5; // seconds
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        if (getMethod != null) {
            getMethod.abort();
        }
    }
};
new Timer(true).schedule(task, hardTimeout * 1000);
 
HttpResponse response = httpClient.execute(getMethod);
System.out.println(
  "HTTP Status of response: " + response.getStatusLine().getStatusCode());

我们使用java.util.Timerjava.util.TimerTask来设置一个简单的延迟任务,在5秒硬超时后中止HTTP GET请求

2.7超时和DNS循环 - 需要注意的事项

一些较大的域将使用DNS循环配置是非常常见的 - 基本上将相同的域映射到多个IP地址。由于HttpClient尝试连接到超时域的方式,这就给这个域的超时带来了新的挑战:

  • HttpClient获取到该域的IP路由列表
  • 它会尝试第一个 - 超时(使用我们配置的超时)
  • 它尝试第二个 - 也超时
  • 等等 …

所以,正如你所看到的那样 - 当我们预期它的整体操作不会超时。取而代之的是,当所有可能的路由超时以及更多的时候 - 它将完全透明地发生在客户端(除非你在DEBUG级别配置了你的日志)。以下是一个可以运行并复制此问题的简单示例:

int timeout = 3;
RequestConfig config = RequestConfig.custom().
  setConnectTimeout(timeout * 1000).
  setConnectionRequestTimeout(timeout * 1000).
  setSocketTimeout(timeout * 1000).build();
CloseableHttpClient client = HttpClientBuilder.create()
  .setDefaultRequestConfig(config).build();
 
HttpGet request = new HttpGet("http://www.google.com:81");
response = client.execute(request);

您将注意到具有DEBUG日志级别的重试逻辑:

DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com/173.194.34.212:81
DEBUG o.a.h.i.c.HttpClientConnectionOperator - 
 Connect to www.google.com/173.194.34.212:81 timed out. Connection will be retried using another IP address
 
DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com/173.194.34.208:81
DEBUG o.a.h.i.c.HttpClientConnectionOperator - 
 Connect to www.google.com/173.194.34.208:81 timed out. Connection will be retried using another IP address
 
DEBUG o.a.h.i.c.HttpClientConnectionOperator - Connecting to www.google.com/173.194.34.209:81
DEBUG o.a.h.i.c.HttpClientConnectionOperator - 
 Connect to www.google.com/173.194.34.209:81 timed out. Connection will be retried using another IP address
//...


3.httpclient4取消/中止请求

3.1中止GET请求

要中止正在进行的请求,客户可以简单地使用:
request.abort();
这将确保客户端不必使用整个请求来释放连接:
@Test
public void whenRequestIsCanceled_thenCorrect() 
  throws ClientProtocolException, IOException {
    HttpClient instance = HttpClients.custom().build();
    HttpGet request = new HttpGet(SAMPLE_URL);
    HttpResponse response = instance.execute(request);
 
    try {
        System.out.println(response.getStatusLine());
        request.abort();
    } finally {
        response.close();
    }
}

4.HttpClient 4不遵循重定向

4.1 在HttpClient 4.3之前

在旧版本的Http客户端(4.3版之前)中,我们可以配置客户端重定向的功能,如下所示:
@Test
public void givenRedirectsAreDisabled_whenConsumingUrlWhichRedirects_thenNotRedirected() 
  throws ClientProtocolException, IOException {
    DefaultHttpClient instance = new DefaultHttpClient();
 
    HttpParams params = new BasicHttpParams();
    params.setParameter(ClientPNames.HANDLE_REDIRECTS, false);
    // HttpClientParams.setRedirecting(params, false); // alternative
 
    HttpGet httpGet = new HttpGet("http://t.co/I5YYd9tddw");
    httpGet.setParams(params);
    CloseableHttpResponse response = instance.execute(httpGet);
 
    assertThat(response.getStatusLine().getStatusCode(), equalTo(301));
}
注意可以使用替代API来配置重定向行为,而不用设置实际的原始  http.protocol.handle-redirects参数
HttpClientParams.setRedirecting(params, false);
另外请注意,在禁用了后续重定向的情况下,我们现在可以检查Http Response状态代码是否确实是301永久移动 - 因为它应该是。

4.2在HttpClient 4.3之后

HttpClient 4.3引入了更清洁,更高层次的API来构建和配置客户端:
@Test
public void givenRedirectsAreDisabled_whenConsumingUrlWhichRedirects_thenNotRedirected() 
  throws ClientProtocolException, IOException {
    HttpClient instance = HttpClientBuilder.create().disableRedirectHandling().build();
    HttpResponse response = instance.execute(new HttpGet("http://t.co/I5YYd9tddw"));
 
    assertThat(response.getStatusLine().getStatusCode(), equalTo(301));
}
请注意,新的API使用此重定向行为配置整个客户端 - 而不仅仅是个别请求。

5.httpclient4自定义请求头部信息

5.1按要求设置标题 - 在4.3之前

您可以使用简单的setHeader调用在请求上设置任何自定义标题:
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(SAMPLE_URL);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");

5.2请求标题 - 4.3及以上

HttpClient 4.3引入了一种构建请求的新方法 - RequestBuilder。要设置标题,我们将在构建器上使用相同的setHeader方法
HttpClient client = HttpClients.custom().build();
HttpUriRequest request = RequestBuilder.get()
  .setUri(SAMPLE_URL)
  .setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
  .build();
client.execute(request);

5.3在客户端设置默认标题 - 4.3及以上

除了在每个请求上设置标题,您还可以将其配置为客户端本身的默认标题
Header header = new BasicHeader(
  HttpHeaders.CONTENT_TYPE, "application/json");
List<Header> headers = Lists.newArrayList(header);
HttpClient client = HttpClients.custom()
  .setDefaultHeaders(headers).build();
HttpUriRequest request = RequestBuilder.get()
  .setUri(SAMPLE_URL).build();
client.execute(request);
当所有请求的头部需要相同时,这非常有用 - 例如自定义应用程序头部。
 

6.httpclient4.3版本以上常见操作

创建http客户端
CloseableHttpClient client = HttpClientBuilder.create().build();
发送基本的GET请求
instance.execute(new HttpGet("http://www.google.com"));
获取HTTP响应的状态码
CloseableHttpResponse response = instance.execute(new HttpGet("http://www.google.com"));
assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
获取响应的媒体类型
CloseableHttpResponse response = instance.execute(new HttpGet("http://www.google.com"));
assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
获取响应的媒体类型
CloseableHttpResponse response = instance.execute(new HttpGet("http://www.google.com"));
String contentMimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
assertThat(contentMimeType, equalTo(ContentType.TEXT_HTML.getMimeType()));
得到响应的主体
CloseableHttpResponse response = instance.execute(new HttpGet("http://www.google.com"));
String bodyAsString = EntityUtils.toString(response.getEntity());
assertThat(bodyAsString, notNullValue());
配置请求的超时时间
@Test(expected = SocketTimeoutException.class)
public void givenLowTimeout_whenExecutingRequestWithTimeout_thenException() 
    throws ClientProtocolException, IOException {
    RequestConfig requestConfig = RequestConfig.custom()
      .setConnectionRequestTimeout(1000).setConnectTimeout(1000).setSocketTimeout(1000).build();
    HttpGet request = new HttpGet(SAMPLE_URL);
    request.setConfig(requestConfig);
    instance.execute(request);
}
在整个客户端上配置超时
RequestConfig requestConfig = RequestConfig.custom().
    setConnectionRequestTimeout(1000).setConnectTimeout(1000).setSocketTimeout(1000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig);
发送POST请求
instance.execute(new HttpPost(SAMPLE_URL));
将参数添加到请求
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("key1", "value1"));
params.add(new BasicNameValuePair("key2", "value2"));
request.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
配置如何处理HTTP请求的重定向
CloseableHttpClient instance = HttpClientBuilder.create().disableRedirectHandling().build();
CloseableHttpResponse response = instance.execute(new HttpGet("http://t.co/I5YYd9tddw"));
assertThat(response.getStatusLine().getStatusCode(), equalTo(301));
为请求配置标题
HttpGet request = new HttpGet(SAMPLE_URL);
request.addHeader(HttpHeaders.ACCEPT, "application/xml");
response = instance.execute(request);
从响应中获取头文件
CloseableHttpResponse response = instance.execute(new HttpGet(SAMPLE_URL));
Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
assertThat(headers, not(emptyArray()));
关闭/释放资源
response = instance.execute(new HttpGet(SAMPLE_URL));
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        instream.close();
    }
} finally {
    response.close();
}