文章目录
- 前言
- 一、简单使用
- 1.1 get 请求
- 1.2 post 简单表单请求
- 1.3 表单上传文件
- 1.4 上传 json 数据
- 二、高级用法
- 2.1 超时和重试
- 2.2 Cookie
- 2.3 拦截器
- 2.4 fluent API
- 三、3.1旧版本使用
- 3.1 Get 请求
- 3.2 Post 请求
- 四、异步版本使用
- 4.1 基本请求
- 4.2 请求流水线执行
- 参考
前言
HttpClient 成立于2001年,是 Apache Jakarta Commons 项目下的子项目,2004 年离开 Commons,提升成为一个单独的 Jakarta 项目。2005 年,Jakarta 创建了 HttpComponents 项目,目标是开发 HttpClient 3.x 的继任者。2007 年,Commons 项目,也就是 HttpClient 项目的发源地,离开了 Jakarta, 成为了1个新的顶级项目。不久之后,HttpComponents 也离开了 Jakarta, 成为一个独立的顶级项目,负责维护 HttpClient 的工作。
-
HttpClient 提供了高效、最新、功能丰富的支持 HTTP 协议的客户端编程工具包,支持最新版本的 HTTP 协议。
-
HttpComponents 项目,包含 HttpClient 和 HttpCore, AsyncClient 三大模块,提供了更好的性能和更大的灵活性。
-
HttpClient 是依赖于 HttpCore 的,最新的 HttpClient 版本为 5.2
-
HttpClient 是以 3.1 版本为分隔,大版本之间用法有很多不同
-
最新文档地址:https://hc.apache.org/httpcomponents-client-5.2.x/index.html
-
旧版文档地址:https://hc.apache.org/httpclient-legacy/userguide.html
-
github 地址:https://github.com/apache/httpcomponents-client
-
pom 依赖
<!-- 最新版本5 --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- 版本4 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <!-- 旧版本3,07年后没更新 --> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency>
一、简单使用
1.1 get 请求
String url = "http://httpbin.org/get";
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
final HttpGet httpget = new HttpGet(url);
// Create a custom response handler
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
final String responseBody = httpclient.execute(httpget, responseHandler);
System.out.println(responseBody);
}
1.2 post 简单表单请求
String url = "http://httpbin.org/post";
String username = "root";
String loginPw = "";
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
final HttpPost httppost = new HttpPost(url);
final List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("username", username));
params.add(new BasicNameValuePair("password", loginPw));
httppost.setEntity(new UrlEncodedFormEntity(params));
// Create a custom response handler
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
final String responseBody = httpclient.execute(httppost, responseHandler);
System.out.println(responseBody);
}
1.3 表单上传文件
final HttpPost httppost = new HttpPost(url);
final MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addTextBody("username", username);
builder.addTextBody("password", password);
builder.addBinaryBody("file", new File("src/test/resources/test.txt"), ContentType.APPLICATION_OCTET_STREAM, "test.txt");
final HttpEntity multipart = builder.build();
httppost.setEntity(multipart);
1.4 上传 json 数据
final HttpPost httppost = new HttpPost(url);
httppost.setHeader("Accept", "application/json");
httppost.setHeader("Content-type", "application/json");
final String json = "{\"id\":1,\"name\":\"John\"}";
final StringEntity stringEntity = new StringEntity(json);
httppost.setEntity(stringEntity);
二、高级用法
2.1 超时和重试
超时控制可以通过 RequestConfig 这个类控制
String url = "http://httpbin.org/get";
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofSeconds(100L))//连接请求超时, 0为无限。默认值:3分钟。
.setResponseTimeout(Timeout.ofSeconds(600L)) // 响应超时时间,0为无限。带有消息复用的HTTP传输可能不支持响应超时
.build();
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
final HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
httpclient.execute(httpGet, responseHandler);
}
重试,默认重试策略为最大次数1次,重试间隔为1秒。
String url = "http://httpbin.org/get";
try (final CloseableHttpClient httpclient = HttpClients.custom()
.setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.ofSeconds(20L)))
.build()) {
final HttpGet httpGet = new HttpGet(url);
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
httpclient.execute(httpGet, responseHandler);
}
2.2 Cookie
HttpClients.createDefault 已经内置默认 Cookie 管理器可以用来携带 Cookie 访问
String url = "http://httpbin.org/cookies/set/foo/bar";
String url2 = "http://httpbin.org/cookies";
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
final HttpGet httpGet = new HttpGet(url);
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
final HttpGet httpGet2 = new HttpGet(url2);
String responseBody2 = httpclient.execute(httpGet2, responseHandler);
System.out.println(responseBody2);
final String responseBody = httpclient.execute(httpGet, responseHandler);
System.out.println(responseBody);
responseBody2 = httpclient.execute(httpGet2, responseHandler);
System.out.println(responseBody2);
}
还可以访问通过本地上下文绑定 cookie,从而获取cookie 信息
String url = "http://httpbin.org/cookies/set/foo/bar";
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
// 创建一个本地的 Cookie 存储
final CookieStore cookieStore = new BasicCookieStore();
final HttpClientContext localContext = HttpClientContext.create();
// 绑定 cookieStore 到 localContext
localContext.setCookieStore(cookieStore);
final HttpGet httpget = new HttpGet(url);
final HttpClientResponseHandler<String> responseHandler = response -> {
final int status = response.getCode();
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
final HttpEntity entity = response.getEntity();
try {
final List<Cookie> cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
System.out.println("Local cookie: " + cookie);
}
return entity != null ? EntityUtils.toString(entity) : null;
} catch (final ParseException ex) {
throw new ClientProtocolException(ex);
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
String response = httpclient.execute(httpget, localContext, responseHandler);
System.out.println(response);
}
2.3 拦截器
httpclient 支持通过拦截器对请求进行一定的处理,有如下几个方法添加拦截器
- addRequestInterceptorFirst
- addRequestInterceptorLast
- addResponseInterceptorFirst
- addResponseInterceptorLast
- addExecInterceptorFirst
- addExecInterceptorLast
- addExecInterceptorBefore
- addExecInterceptorAfter
添加的拦截器可分为3种类型: request, response和 exec,对应请求,响应和执行。其中Exec执行的名字在枚举ChainElement 中,在 HttpClientBuilder 类源码中,可以发现除了 CACHING 其它都可以通过配置使用,并且枚举中的顺序也是Exec执行的顺序,其中 MAIN_TRANSPORT 执行包含 request 和 response 拦截器执行
ChainElement 定义了一组可用于构建HTTP请求处理管道的元素,每个元素都可以实现特定的功能,如添加自定义HTTP头、添加身份验证信息等。
public enum ChainElement {
REDIRECT, COMPRESS, BACK_OFF, RETRY, CACHING, PROTOCOL, CONNECT, MAIN_TRANSPORT
}
下面是一个对官方拦截器例子修改的代码
AtomicLong count = new AtomicLong();
try (final CloseableHttpClient httpclient = HttpClients.custom()
.addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "custom", (request, scope, chain) -> {
request.setHeader("request-id", Long.toString(count.incrementAndGet()));
return chain.proceed(request, scope);
})
.addExecInterceptorAfter("custom", "quit3rd", ((request, scope, chain) -> {
final Header idHeader = request.getFirstHeader("request-id");
if (idHeader != null && "3".equalsIgnoreCase(idHeader.getValue())) {
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND, "Oppsie");
response.setEntity(new StringEntity("bad luck", ContentType.TEXT_PLAIN));
return response;
} else {
return chain.proceed(request, scope);
}
}))
.addExecInterceptorBefore(ChainElement.CONNECT.name(), "AAA", (request, scope, chain) -> {
System.out.println("AAA");
return chain.proceed(request, scope);
})
.addExecInterceptorBefore("AAA", "BBB", (request, scope, chain) -> {
System.out.println("BBB");
return chain.proceed(request, scope);
})
.addExecInterceptorAfter("AAA", "CCC", (request, scope, chain) -> {
System.out.println("CCC");
return chain.proceed(request, scope);
})
.addRequestInterceptorFirst((request, entity, context) -> {
System.out.println("第一个request first现在获取:" + context.getAttribute("foo"));
})
.addRequestInterceptorFirst((request, entity, context) -> {
System.out.println("第二个request first, 现在设置name");
context.setAttribute("foo", "bar");
})
.addRequestInterceptorLast((request, entity, context) -> {
System.out.println("第一个request last现在获取:" + context.getAttribute("foo"));
})
.build()) {
for (int i = 0; i < 5; i++) {
final HttpGet httpget = new HttpGet("http://httpbin.org/get");
System.out.println("Executing request " + httpget.getMethod() + " " + httpget.getUri());
httpclient.execute(httpget, response -> {
System.out.println("----------------------------------------");
System.out.println(httpget + "->" + new StatusLine(response));
EntityUtils.consume(response.getEntity());
return null;
});
}
}
下面动图显示的是调试过程中 execChain 执行链的顺序
下面是 调试过程中的request和response 拦截器,从名字就可以看出除了Main类是自定义的拦截器,其余都是自带的,其中cookie处理也是通过拦截器实现的。
2.4 fluent API
HttpClienet 4.5 版本以上支持fluent API, 优点是代码更简洁,同时为线程安全的。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.13</version>
</dependency>
String urlGet = "http://httpbin.org/get";
String urlPost = "http://httpbin.org/post";
String response = Request.Get(urlGet)
.addHeader("Authorization", "Bear:dw")
.execute()
.handleResponse(httpResponse -> {
int code = httpResponse.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_SUCCESS) {
return org.apache.http.util.EntityUtils.toString(httpResponse.getEntity());
}
return null;
});
System.out.println(response);
String result = Request.Post(urlPost)
.bodyForm(Form.form().add("foo", "bar").build())
.execute()
.returnContent()
.asString();
System.out.println(result);
三、3.1旧版本使用
3.1 Get 请求
String url = "http://httpbin.com";
HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
try {
// Execute the method.
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + method.getStatusLine());
}
// Read the response body.
byte[] responseBody = method.getResponseBody();
// Deal with the response.
// Use caution: ensure correct character encoding and is not binary data
System.out.println(new String(responseBody));
} catch (HttpException e) {
System.err.println("Fatal protocol violation: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("Fatal transport error: " + e.getMessage());
e.printStackTrace();
} finally {
// Release the connection.
method.releaseConnection();
}
3.2 Post 请求
String url = "http://httpbin.org/post";
HttpClient client = new HttpClient();
PostMethod method = new PostMethod(url);
NameValuePair[] data = {
new NameValuePair("user", "joe"),
new NameValuePair("password", "bloggs")
};
method.setRequestBody(data);
try {
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + method.getStatusLine());
}
byte[] responseBody = method.getResponseBody();
System.out.println(new String(responseBody));
} catch (HttpException e) {
System.err.println("Fatal protocol violation: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("Fatal transport error: " + e.getMessage());
e.printStackTrace();
} finally {
method.releaseConnection();
}
四、异步版本使用
4.1 基本请求
final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setSoTimeout(Timeout.ofSeconds(5))
.build();
final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setIOReactorConfig(ioReactorConfig)
.build();
client.start();
final HttpHost target = new HttpHost("httpbin.org");
final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"};
for (final String requestUri: requestUris) {
final SimpleHttpRequest request = SimpleRequestBuilder.get()
.setHttpHost(target)
.setPath(requestUri)
.build();
System.out.println("请求url:" + requestUri);
final Future<SimpleHttpResponse> future = client.execute(
SimpleRequestProducer.create(request),
SimpleResponseConsumer.create(),
new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(final SimpleHttpResponse response) {
System.out.println(requestUri + " 返回状态码:" + response.getCode() + ",返回内容:" + response.getBodyText());
}
@Override
public void failed(final Exception ex) {
System.out.println(request + "->" + ex);
}
@Override
public void cancelled() {
System.out.println(request + " cancelled");
}
});
future.get();
}
System.out.println("Shutting down");
client.close(CloseMode.GRACEFUL);
4.2 请求流水线执行
final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(
H2Config.DEFAULT,
Http1Config.DEFAULT,
IOReactorConfig.DEFAULT,
PoolingAsyncClientConnectionManagerBuilder.create()
.setDefaultTlsConfig(TlsConfig.custom()
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.build())
.build());
client.start();
final HttpHost target = new HttpHost("httpbin.org");
final Future<AsyncClientEndpoint> leaseFuture = client.lease(target, null);
final AsyncClientEndpoint endpoint = leaseFuture.get(30, TimeUnit.SECONDS);
try {
final String[] requestUris = new String[] {"/", "/ip", "/user-agent", "/headers"};
final CountDownLatch latch = new CountDownLatch(requestUris.length);
for (final String requestUri: requestUris) {
final SimpleHttpRequest request = SimpleRequestBuilder.get()
.setHttpHost(target)
.setPath(requestUri)
.build();
System.out.println("Executing request " + request);
endpoint.execute(
SimpleRequestProducer.create(request),
SimpleResponseConsumer.create(),
new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(final SimpleHttpResponse response) {
latch.countDown();
System.out.println(request + "->" + new StatusLine(response));
System.out.println(response.getBody());
}
@Override
public void failed(final Exception ex) {
latch.countDown();
System.out.println(request + "->" + ex);
}
@Override
public void cancelled() {
latch.countDown();
System.out.println(request + " cancelled");
}
});
}
latch.await();
} finally {
endpoint.releaseAndReuse();
}
System.out.println("Shutting down");
client.close(CloseMode.GRACEFUL);
参考
- https://hc.apache.org/httpclient-legacy/index.html