引言
本文主要记录如何构建HttpClient(4.5.x) 和 入门使用.
参考官方文档
http://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/
1. HttpClient构建
1.1 关于HttpClinet
在构建之前, 先大致了解一下HttpClient类
HttpClient子类
-
DecompressingHttpClient: 装饰器类(已弃用),支持解压缩逻辑,可用RequestAcceptEncoding/ResponseContentEncoding取代. -
AutoRetryHttpClient: 装饰器类(已弃用),支持服务异常重试逻辑,可用ServiceUnavailableRetryStrategy取代. - CloseableHttpClient: 默认的HttpClient
- MinimalHttpClient: 简易版HttpClient
-
AbstractHttpClient: 已弃用,文档推荐HttpClientBuilder, 构建InternalHttpClient对象. - InternalHttpClient: 默认访问权限, 只能通过HttpClientBuilder构建.
经过上述说明, 4.5.x版本中, 我们只需关心如何通过HttpClientBuilder构建HttpClient即可.
此外,HttpClient还提供了HttpClients作为工厂类, 封装了HttpClientBuilder和MinimalHttpClient构建.
1.2 构建HttpClient(默认)
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(2000) // 创建Socket超时时间
.setConnectTimeout(2000) // 请求响应超时时间
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build()
2. HttpClient使用入门
2.0 构建URI
// 构建URI 方式1
URI uri = URI.create("http://localhost:8080/hello?user=roylion")
// 构建URI 方式2
URI uri = new URIBuilder("http://localhost:8080")
.setPath("/hello")
.addParameter("user", "roylion")
.build();
2.1 Get请求
基础GET请求
/**
* 基础get请求
*
* @param uri 请求路径
* @param headers 请求header参数 {@link BasicHeader#BasicHeader(String, String)}
* @param reqCfg 请求配置参数
* @return 返回响应, 用完必须close()
* @throws IOException
*/
public static CloseableHttpResponse baseGet(URI uri, Header[] headers, RequestConfig reqCfg) throws IOException {
final HttpGet httpGet = new HttpGet();
httpGet.setURI(uri);
httpGet.setHeaders(headers);
httpGet.setConfig(reqCfg);
return httpClient.execute(httpGet);
}
GET - FORM表单提交
/**
* form表单提交
*
* @param uri 请求路径
* @param formData 表单数据列表
* @return 响应结果
* @throws URISyntaxException
* @throws IOException
*/
public static String getFrom(URI uri, List<NameValuePair> formData) throws URISyntaxException, IOException {
URIBuilder uriBuilder = new URIBuilder(uri);
for (NameValuePair nameValuePair : formData) {
uriBuilder.addParameter(nameValuePair.getName(), nameValuePair.getValue());
}
URI finalUri = uriBuilder.build();
Header[] headers = new Header[]{new BasicHeader("Content-Type", "application/x-www-form-urlencoded")};
try (CloseableHttpResponse resp = baseGet(finalUri, headers, null);) {
return toString(resp.getEntity());
}
}
2.2 Post请求
基础POST请求
/**
* 基础post请求
*
* @param uri 请求路径
* @param headers 请求header参数 {@link BasicHeader#BasicHeader(String, String)}
* @param reqCfg 请求配置参数
* @param httpEntity 请求体对象
* String(文本, json...) {@link StringEntity}
* form(表单) {@link UrlEncodedFormEntity}
* multipartForm(文件, 图片, 文本...) {@link MultipartEntityBuilder#build()}
* @return 返回响应, 用完必须close()
* @throws IOException
*/
public static CloseableHttpResponse basePost(URI uri, Header[] headers, RequestConfig reqCfg, HttpEntity httpEntity) throws IOException {
HttpPost httpPost = new HttpPost();
httpPost.setURI(uri);
httpPost.setHeaders(headers);
httpPost.setConfig(reqCfg);
httpPost.setEntity(httpEntity);
return httpClient.execute(httpPost);
}
POST - JSON 请求
/**
* post请求, 传递JSON请求体
*
* @param uri 请求路径
* @param jsonBody json字符串
* @return 返回响应结果
* @throws IOException
*/
public static String postJson(URI uri, String jsonBody) throws IOException {
StringEntity body = new StringEntity(jsonBody, ContentType.APPLICATION_JSON);
try (CloseableHttpResponse resp = basePost(uri, null, null, body)) {
return toString(resp.getEntity());
}
}
POST - FORM 表单提交
/**
* post请求, 表单提交
*
* @param uri 请求路径
* @param formData 表单参数列表
* @return 返回响应结果
* @throws IOException
*/
public static String postForm(URI uri, List<NameValuePair> formData) throws IOException {
UrlEncodedFormEntity body = new UrlEncodedFormEntity(formData);
try (CloseableHttpResponse resp = basePost(uri, null, null, body)) {
return toString(resp.getEntity());
}
}
POST文件上传
/**
* 上传文件
*
* @param uri 请求路径
* @param name 上传文件参数名称
* @param inputStream 文件的输入流
* @param contentType 文件类型
* 普通文件 {@link ContentType#MULTIPART_FORM_DATA}
* 图片 {@link ContentType#IMAGE_JPEG} {@link ContentType#IMAGE_PNG} ...
* @param fileName 上传文件名称
* @return
* @throws IOException
*/
public static String postFile(URI uri, String name, InputStream inputStream, ContentType contentType, String fileName) throws IOException {
// HttpMultipartMode.RFC6532参数的设定是为避免文件名为中文时乱码
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
multipartEntityBuilder.addBinaryBody(name, inputStream, contentType, fileName);
HttpEntity fileEntity = multipartEntityBuilder.build();
try (CloseableHttpResponse resp = basePost(uri, null, null, fileEntity)) {
return toString(resp.getEntity());
}
}
POST多文件上传
/**
* 多文件上传
*
* @param uri 请求路径
* @param name 上传文件参数名称
* @param fileList 文件列表
* @return
*/
public static String postMultipartFile(URI uri, String name, List<InputStreamBody> fileList) throws IOException {
// HttpMultipartMode.RFC6532参数的设定是为避免文件名为中文时乱码
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
for (InputStreamBody inputStreamBody : fileList) {
multipartEntityBuilder.addPart(name, inputStreamBody);
}
HttpEntity fileEntity = multipartEntityBuilder.build();
try (CloseableHttpResponse resp = basePost(uri, null, null, fileEntity)) {
return toString(resp.getEntity());
}s
}
3. HttpClient详细配置
以下配置均参考于HttpClientBuilder#build()方法。
// userAgent
/**
* 获取agent
* [system]http.agent
* 配置userAgent, 优先级高于system. {@link HttpClientBuilder#setUserAgent(String)}
* 禁用默认userAgent. {@link HttpClientBuilder#disableDefaultUserAgent()}
*/
String userAgentCopy = VersionInfo.getUserAgent("Apache-HttpClient",
"org.apache.http.client", HttpClients.class);
/**
* 配置公共域名后缀
* {@link HttpClientBuilder#setPublicSuffixMatcher(PublicSuffixMatcher)}
*/
PublicSuffixMatcher publicSuffixMatcherCopy = PublicSuffixMatcherLoader.getDefault();
// =============================> 连接管理 <============================= //
/**
* 请求执行器, 发送请求的核心代码
* {@link HttpClientBuilder#setRequestExecutor(HttpRequestExecutor)}
*/
HttpRequestExecutor requestExecCopy = new HttpRequestExecutor();
/**
* SslSocket工厂类
* [system]https.protocols => String[] supportedProtocols
* [system]https.cipherSuites => String[] supportedCipherSuites
* {@link HttpClientBuilder#setSSLSocketFactory(LayeredConnectionSocketFactory)}
* {@link HttpClientBuilder#setSSLContext(SSLContext)}
* {@link HttpClientBuilder#setSSLHostnameVerifier(HostnameVerifier)} 用于SSL连接中主机名的校验,因为SSL是要校验证书的
*/
LayeredConnectionSocketFactory sslSocketFactoryCopy = new SSLConnectionSocketFactory(
SSLContexts.createDefault(),
new DefaultHostnameVerifier(publicSuffixMatcherCopy));
/**
* 连接管理器(分单连接和池化连接)
* {@link HttpClientBuilder#setConnectionManager(HttpClientConnectionManager)}
*
* [system]http.keepAlive = true
* => [system]http.maxConnections = {max} => DefaultMaxPerRoute=max; MaxTotal=2*max
* {@link HttpClientBuilder#setDnsResolver(DnsResolver)}
* {@link HttpClientBuilder#setConnectionTimeToLive(long, TimeUnit)}
* {@link HttpClientBuilder#setDefaultSocketConfig(SocketConfig)}
* {@link HttpClientBuilder#setDefaultConnectionConfig(ConnectionConfig)}
* {@link HttpClientBuilder#setMaxConnTotal(int)}
* {@link HttpClientBuilder#setMaxConnPerRoute(int)}
*/
HttpClientConnectionManager connManagerCopy = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactoryCopy)
.build(),
null,
null,
null,
-1,
TimeUnit.MILLISECONDS);
/**
* 连接复用策略 根据'Connection: keep-alive'确认是否需要保持连接
* http.keepAlive = false => NoConnectionReuseStrategy.INSTANCE(不复用连接)
* {@link HttpClientBuilder#setConnectionReuseStrategy(ConnectionReuseStrategy)}
*/
ConnectionReuseStrategy reuseStrategyCopy = DefaultClientConnectionReuseStrategy.INSTANCE;
/**
* keepAlive策略 根据'Keep-Alive: timeout=300'确认连接过期时间
* {@link HttpClientBuilder#setKeepAliveStrategy(ConnectionKeepAliveStrategy)}
*/
ConnectionKeepAliveStrategy keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
// =============================> 权限认证(代理)相关 <============================= //
/**
* 权限认证策略
* {@link HttpClientBuilder#setTargetAuthenticationStrategy(AuthenticationStrategy)}
*/
AuthenticationStrategy targetAuthStrategyCopy = TargetAuthenticationStrategy.INSTANCE;
/**
* 代理权限认证策略
* {@link HttpClientBuilder#setProxyAuthenticationStrategy(AuthenticationStrategy)}
*/
AuthenticationStrategy proxyAuthStrategyCopy = ProxyAuthenticationStrategy.INSTANCE;
/**
* 获取用户Token
* {@link HttpClientBuilder#setUserTokenHandler(UserTokenHandler)}
*/
UserTokenHandler userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE;
/**
* 权限认证注册仓库
* {@link HttpClientBuilder#setDefaultAuthSchemeRegistry(Lookup)}
*/
Lookup<AuthSchemeProvider> authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
.build();
/**
* 权限认证信息仓库
* {@link HttpClientBuilder#setDefaultCredentialsProvider(CredentialsProvider)}
*/
CredentialsProvider defaultCredentialsProvider = new BasicCredentialsProvider();
// =============================> Cookie相关 <============================= //
/**
* Cookie规格仓库
* {@link HttpClientBuilder#setDefaultCookieSpecRegistry(Lookup)}
*/
Lookup<CookieSpecProvider> cookieSpecRegistryCopy = CookieSpecRegistries.createDefault(publicSuffixMatcherCopy);
/**
* Cookie存储仓库
* {@link HttpClientBuilder#setDefaultCookieStore(CookieStore)}
*/
CookieStore defaultCookieStore = new BasicCookieStore();
// =============================> 执行器职责链 <============================= //
/** 构建执行器职责链
* MainClientExec(基础)
* => ProtocolExec(添加了HttpProcessor拦截器链)
* => RetryExec(请求异常重试机制)
* => ServiceUnavailableRetryExec(请求响应异常, 延迟重试机制)
* => RedirectExec(支持重定向执行器)
* => BackoffStrategyExec(回退降级机制)
*/
ClientExecChain execChain = new MainClientExec( // 核心执行器
requestExecCopy,
connManagerCopy,
reuseStrategyCopy,
keepAliveStrategyCopy,
new ImmutableHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
targetAuthStrategyCopy,
proxyAuthStrategyCopy,
userTokenHandlerCopy);
// =============================> 拦截器 <============================= //
/**
* 构建HttpProcessor拦截器链
*
* 添加优先级高的拦截器
* {@link HttpClientBuilder#addInterceptorFirst(HttpRequestInterceptor)}
* {@link HttpClientBuilder#addInterceptorFirst(HttpResponseInterceptor)}
*/
final HttpProcessorBuilder b = HttpProcessorBuilder.create();
b.addAll(
new RequestDefaultHeaders(Collections.EMPTY_LIST),
new RequestContent(),
new RequestTargetHost(),
new RequestClientConnControl(),
new RequestUserAgent(userAgentCopy),
new RequestExpectContinue());
/**
* 往请求中添加cookie的拦截器
* {@link HttpClientBuilder#disableCookieManagement()}
*/
b.add(new RequestAddCookies());
/**
* 往请求中配置'Accept-Encoding'的拦截器
* {@link HttpClientBuilder#disableContentCompression()}
* {@link HttpClientBuilder#setContentDecoderRegistry(Map)}
*/
b.add(new RequestAcceptEncoding());
/**
* 权限认证缓存的拦截器
* {@link HttpClientBuilder#disableAuthCaching()}
*/
b.add(new RequestAuthCache());
/**
* 处理响应中的'Set-Cookie'的拦截器
* {@link HttpClientBuilder#disableCookieManagement()}
*/
b.add(new ResponseProcessCookies());
/**
* 处理'Content-Encoding'响应首部的拦截器
* {@link HttpClientBuilder#disableContentCompression()}
* {@link HttpClientBuilder#setContentDecoderRegistry(Map)}
*/
b.add(new ResponseContentEncoding());
/**
* 添加优先级低的拦截器
* {@link HttpClientBuilder#addInterceptorLast(HttpRequestInterceptor)}
* {@link HttpClientBuilder#addInterceptorLast(HttpResponseInterceptor)}
*/
HttpProcessor httpprocessorCopy = b.build();
execChain = new ProtocolExec(execChain, httpprocessorCopy); // 带拦截器职责链的执行器
/**
* 请求重试机制
* {@link HttpClientBuilder#disableAutomaticRetries()}
* {@link HttpClientBuilder#setRetryHandler(HttpRequestRetryHandler)}
*/
HttpRequestRetryHandler retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
execChain = new RetryExec(execChain, retryHandlerCopy); // 支持请求异常重试的执行器
/**
* 可选, 依赖服务不可用, 提供延迟重试机制
*
* {@link HttpClientBuilder#setServiceUnavailableRetryStrategy(ServiceUnavailableRetryStrategy)}
*/
final ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = null;
if (serviceUnavailStrategyCopy != null) {
execChain = new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategyCopy); // 支持响应异常, 延迟重试的执行器
}
/**
* Http路由规划 (代理, 端口解析...)
* {@link HttpClientBuilder#setRoutePlanner(HttpRoutePlanner)}
* {@link HttpClientBuilder#setSchemePortResolver(SchemePortResolver)}
* {@link HttpClientBuilder#setProxy(HttpHost)}
*/
SchemePortResolver schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
HttpRoutePlanner routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
/**
* 重定向策略
* {@link HttpClientBuilder#disableRedirectHandling()}
* {@link HttpClientBuilder#setRedirectStrategy(RedirectStrategy)}
*/
RedirectStrategy redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
execChain = new RedirectExec(execChain, routePlannerCopy, redirectStrategyCopy); // 支持重定向的执行器
/**
* 回退(服务降级)机制, 重试是自私的, 当重试造成服务超负载时, 应使用回退机制, 使下游系统不可用, 过一段时间后再自动恢复.
* {@link HttpClientBuilder#setConnectionBackoffStrategy(ConnectionBackoffStrategy)}
* {@link HttpClientBuilder#setBackoffManager(BackoffManager)}
*/
if (false) {
execChain = new BackoffStrategyExec(execChain, null, null); // 支持回退降级的执行器
}
// =============================> 关闭资源 <============================= //
/**
* 配置关闭HttpClient时资源释放操作
* {@link HttpClientBuilder#addCloseable(Closeable)}
* {@link HttpClientBuilder#setConnectionManagerShared(boolean)}
* {@link HttpClientBuilder#evictExpiredConnections()}
* {@link HttpClientBuilder#evictIdleConnections(long, TimeUnit)}
*
*/
List<Closeable> closeablesCopy = new ArrayList<Closeable>(1);
// 可选, 设置定时关闭空闲/过期的连接
if (false) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connManagerCopy,
10, TimeUnit.SECONDS,
0, null);
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
connectionEvictor.shutdown();
try {
connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
} catch (final InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
}
});
// 启动定时关闭空闲/过期的连接
connectionEvictor.start();
}
// 关闭连接管理器
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
connManagerCopy.shutdown();
}
});
new InternalHttpClient(
execChain,
connManagerCopy,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
defaultCookieStore,
defaultCredentialsProvider,
defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
closeablesCopy);