远程调用工具包HttpClient
HttpClient基础
基础概念
HTTP协议的客户端编程工具包。
- 基于标准、纯净的Java语言,实现了HTTP1.0和HTTP1.1,在HTTP1.0和HTTP1.1中使用用KeepAlive来保持持久连接
- 以可扩展的面向对象的结构实现了HTTP全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)
- 支持加密的HTTPS协议(HTTP通过SSL协议)可以配置Https
- 通过HTTP代理方式建立透明的连接。可以配置代理访问,利用CONNECT方法通过HTTP代理建立隧道的HTTPS连接
- Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos认证方案
- 连接管理支持使用多线程的的应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接
- Request的输出流可以避免流中内容体直接从socket缓冲到服务器,Response的输入流可以有效的从socket服务器直接读取相应内容
- 具备设置连接超时的能力
基础使用步骤:基于4.5版本
-
注意:不同的版本的api可能会有区别,特别是4.5之前的版本api有不少区别
-
(1)连接对象CloseableHttpClient。创建api为:CloseableHttpClient client = HttpClients.createDefault();
-
(2)请求配置对象RequestConfig。可以对本次请求做一些配置。例如本次请求建立TCP/IP连接的超时时间,从连接池获取连接的超时时间,从Socket连接获取数据的超时时间,重试,长连接保持的策略等等。
-
(3)创建
HttpResponse,调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回服务器本次请求的响应对象。利用响应对象的api方法可以获取到本次请求的响应状态码响应体等等。 -
(4)释放连接等资源对象。
HttpClient的请求配置
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
clientBuilder.useSystemProperties(); // 使用该方法可以读取操作系统属性
clientBuilder.disableAuthCaching(); // 调用该方法可以禁用缓存
clientBuilder.disableAutomaticRetries(); // 调用该方法可以禁用连接自动重试
clientBuilder.disableConnectionState(); // 调用该方法可以禁用连接状态
clientBuilder.disableRedirectHandling(); // 调用该方法可以禁用重定向
clientBuilder.disableCookieManagement(); // 调用该方法可以禁用cookie管理
clientBuilder.disableContentCompression(); // 调用该方法可以禁用内容压缩
clientBuilder.setKeepAliveStrategy(ConnectionKeepAliveStrategy keepAliveStrategy); // 设置长连接策略
clientBuilder.setDefaultHeaders(Collection<? extends Header > defaultHeaders); // 设置默认的请求头
clientBuilder.setRetryHandler(HttpRequestRetryHandler retryHandler); // 设置连接重试策略
clientBuilder.setConnectionManager(HttpClientConnectionManager connManager); // 设置连接管理器 即连接池
clientBuilder.setSSLContext(SSLContext sslContext); // 设置SSL策略设置SSL上下文
clientBuilder.addInterceptorFirst(HttpResponseInterceptor itcp); // 将此协议拦截器添加到协议处理列表的头
clientBuilder.addInterceptorLast(HttpResponseInterceptor itcp); // 将此协议拦截器添加到协议处理列表的尾部
clientBuilder.evictExpiredConnections(); // 开启独立线程清理过期连接
clientBuilder.evictIdleConnections(long maxIdleTime, TimeUnit maxIdleTimeUnit); //设置最长空闲时间及空闲时间的单位
clientBuilder.setBackoffManager(BackoffManager backoffManager); // 从请求的返回中动态调整ConnectionPool大小的控制器,每个Route有一个值,这样就可以限制对特定Host的访问频次了。AIMDBackoffManager中还有一个冷却时间的参数,用于调整一个请求之后多长时间之后才能再次访问
clientBuilder.setConnectionBackoffStrategy(ConnectionBackoffStrategy connectionBackoffStrategy); // 当管理特定Route的多个请求时,从请求结果确认是否是一个BackOff(请求等待)信号。多个请求发生的时候,有时候如果频次太高会造成服务器拒绝,从而需要当出现拒绝信号的时候需要调整后续请求的等待。
clientBuilder.setConnectionManagerShared(boolean shared);
clientBuilder.setConnectionReuseStrategy(ConnectionReuseStrategy reuseStrategy); // 决定一个连接在Request完成之后是否还保持打开
clientBuilder.setConnectionTimeToLive(long connTimeToLive, TimeUnit connTimeToLiveTimeUnit); //
clientBuilder.setContentDecoderRegistry(Map<String, InputStreamFactory> contentDecoderMap);
clientBuilder.setDefaultAuthSchemeRegistry(Lookup< AuthSchemeProvider > authSchemeRegistry); //AuthSchemeProvider是AuthScheme的工厂,后者定义的是一个针对服务器发出的质疑(也就是权限验证不过)而响应
clientBuilder.setDefaultConnectionConfig(ConnectionConfig config); // 默认的Connection设置
clientBuilder.setDefaultCookieSpecRegistry(Lookup< CookieSpecProvider > cookieSpecRegistry); // CookieSpecProvider 是 CookieSpec的工厂,后者定义了Set-Cookie以及Cookie的转换方式
clientBuilder.setDefaultCookieStore(CookieStore cookieStore); // Cookie的存取位置设置
clientBuilder.setDefaultCredentialsProvider(CredentialsProvider credentialsProvider); // 设置验证信息
clientBuilder.setDefaultRequestConfig(RequestConfig config); // 默认的Request设置
clientBuilder.setDefaultSocketConfig(SocketConfig config); // 默认的Socket设置, Timout、保持连接时长、Buffer大小等等
clientBuilder.setDnsResolver(DnsResolver dnsResolver); // 用于重写OS提供的DNS查找功能
clientBuilder.setHttpProcessor(HttpProcessor httpprocessor); // 一系列处理HTTP协议的拦截器
clientBuilder.setMaxConnPerRoute(int maxConnPerRoute); // 单个Route最大连接数
clientBuilder.setMaxConnTotal(int maxConnTotal); // 全局最大维持的连接数
clientBuilder.setProxyAuthenticationStrategy(AuthenticationStrategy proxyAuthStrategy);
clientBuilder.setPublicSuffixMatcher(PublicSuffixMatcher publicSuffixMatcher); // 测试域名是否满足公开的域名后缀,比如.com什么的。
clientBuilder.setRedirectStrategy(RedirectStrategy redirectStrategy); // 确认是否需要从当前返回中重定向到其他的地址
clientBuilder.setRequestExecutor(HttpRequestExecutor requestExec); // 设置执行器
clientBuilder.setRoutePlanner(HttpRoutePlanner routePlanner); // 从HttpHost, HttpRequest, HttpContext 三个值来获取HttpRoute的过程。HttpClient不仅支持简单的直连、复杂的路由策略以及代理。HttpRoutePlanner是基于http上下文情况下,客户端到服务器的路由计算策略,一般没有代理的话,就不用设置这个东西。这里有一个很关键的概念—Route:在HttpClient中,一个Route指运行环境机器->目标机器host的一条线路,也就是如果目标url的host是同一个,那么它们的route也是一样的
clientBuilder.setSchemePortResolver(SchemePortResolver schemePortResolver); // 不同协议的默认端口,DefaultSchemePortResolver中HTTP是80,HTTPS是443
clientBuilder.setServiceUnavailableRetryStrategy(ServiceUnavailableRetryStrategy serviceUnavailStrategy); // 当服务不可用的时候,确定是否重试,重试几次
clientBuilder.setSSLHostnameVerifier(HostnameVerifier hostnameVerifier); // 用于SSL连接中主机名的校验,因为SSL是要校验证书的
clientBuilder.setSSLSocketFactory(LayeredConnectionSocketFactory sslSocketFactory); // 设置创立SSLSocket连接工厂
clientBuilder.setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy); // 用于校验是否从服务端返回了权限验证失败的信息,必须是线程安全的
clientBuilder.setUserAgent(String userAgent); // 设置UserAgent
clientBuilder.setUserTokenHandler(UserTokenHandler userTokenHandler); // 校验HttpContext是否是一个User专有的,如果是则返回UserToken用于标记唯一的User,不是的话则返回null
RequestConfig的请求配置
RequestConfig.Builder configBuilder = RequestConfig.custom();
configBuilder.setConnectTimeout(int timeout); // 连接超时时间,单位毫秒
configBuilder.setConnectionRequestTimeout(int timeout); // 从池中获取连接超时时间
configBuilder.setSocketTimeout(int timeout); // 读超时时间(等待数据超时时间)
configBuilder.requestConfigBuilder.setStaleConnectionCheckEnabled(boolean connectionCheckEnabled); // 确保获取到的连接都是可用连接,已经过时,如果需要设置可以通过ConnectionConfig中设置closeExpiredConnections和closeIdleConnections来关闭
configBuilder.setAuthenticationEnabled(boolean authenticationEnabled); // 确定是否应自动处理身份验证
configBuilder.setCircularRedirectsAllowed(boolean circularRedirectsAllowed); // 确定循环重定向(重定向到相同位置)是否应该重定向
configBuilder.setMaxRedirects(int maxRedirects); // 重定向的最大数目。对重定向次数的限制是为了防止无限循环
configBuilder.setRelativeRedirectsAllowed(boolean redirect); // 确定是否应拒绝相对重定向。HTTP规范要求位置值是一个绝对URI
configBuilder.setCookieSpec(String cookieSpec); // 确定用于HTTP状态管理的cookie规范的名称
configBuilder.setLocalAddress(InetAddress); // 返回请求执行的本地地址。在多个网络接口的计算机上,可用于选择其中的网络接口连接产生
configBuilder.setProxy(HttpHost httpHost); // 代理配置,HttpHost设置代理服务器的address和端口
configBuilder.setProxyPreferredAuthSchemes(Collection<String> collections); // 在使用代理主机进行身份验证时,确定支持的身份验证方案的优先顺序。
configBuilder.setTargetPreferredAuthSchemes(Collection<String> collections); // 在使用目标主机进行身份验证时,确定受支持的身份验证模式的首选项顺序
RequestConfig requestConfig = requestConfigBuilder.build();
长连接配置
Http的请求头可以设置连接为长连接,Http1.1默认为长连接。在HTTP 1.0以前,每个http请求都要求打开一个TCP socket连接,并且使用一次之后就断开这个TCP连接,这会导致频繁地创建和销毁TCP。HTTP 1.1通过使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接,以此提高性能和提高http服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。
Http的keep_alive
请求完成的判断:
- Content-Length:Content-Length表示请求实体的内容长度。通过请求的数据长度判断数据是否请求完成。但是这种只能针对于静态资源请求判断。如果是动态资源请求,则需要利用Transfer-Encoding进行判断
- Transfer-Encoding:指传输编码,当服务端无法知道实体内容的长度时,就可以通过指定Transfer-Encoding:chunked来告知浏览器当前的编码是将数据分成一块一块传递的。还可以指定Transfer-Encoding:gzip,chunked表明实体内容不仅是gzip压缩的,还是分块传递的。最后,当浏览器接收到一个长度为0的chunked时,判断当前请求内容已全部接收。
Tcp的Keep-alive
tcp链接建立之后,如果应用程序或者上层协议一直不发送数据,或者隔很长时间才发送一次数据,当链接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,链接还需不需要保持。当超过一段时间之后,TCP自动发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,链接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持链接
HTTP的keep-alive为了维持和服务器的连接,使其连接存活的时间久一些,多次在同一个连接内请求响应数据,避免重复创建连接。Tcp的作用是为了在无数据传输的情况下检测连接的另一端是否存活或者掉线。
如果需要短时间内频繁的发送请求,客户端可以开启keep-alive,使用http的长连接。
超时配置
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5000) // 建立连接的超时时间,单位毫秒
.setConnectionRequestTimeout() // 从连接管理器(即连接池)获取连接的超时时间
.setSocketTimeout(); // 从服务器获取响应数据的超时时间
详解:
connectionRequestTimeout:httpClient使用用连接时尝试从连接池中获取,若是在等待了一定的时间后还没有获取到可用连接(比如连接池中没有空闲连接了)则会抛出获取连接超时异常。在请求很多的情况下,这个值一定不能设置太大,否则导致大量的请求阻塞等待获取连接,从而拖垮系统。单位为毫秒
connectTimeout:指客户端和服务器建立连接的超时时间-单位ms,最大约21秒,因为内部tcp在进行三次握手建立连接时,默认tcp超时时间是20秒。如果在该时间范围内还没有建立起连接,则就抛出connectionTimeOut异常
socketTimeout:等待响应超时(读取数据超时)时间。建立连接之后,获取response的返回等待时间 ,即在与目标url建立连接后,等待放回response的最大时间,在规定时间内没有返回响应的话就抛出SocketTimeout。这个需要根据实际响应实际进行调整。
重试策略根接口:HttpRequestRetryHandler
详解:请求重连。如果是指定的可恢复异常,发生了异常的时候,且重连的次数小于指定的次数的时候,则进行请求重连。如果发生了不可恢复异常,获取重试次数大于了指定次数,那么请求结束,抛出异常。默认实现是:DefaultHttpRequestRetryHandler
- 如果重试的次数大于了3次,不需要重试
- 如果执行异常为几个特殊的异常或者其异常的子类,则不进行重试
- 同一个任务在异步任务被终止了,不进行重试
- 幂等的方法可以进行重试,比如GET
- 如果请求没有发送成功,则可以进行重试
源码解析:
public interface HttpRequestRetryHandler{
// 根据这个方法的返回值确定是否重连
boolean retryRequest(IOException exception, int executionCount, HttpContext context);
}
public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
// 饿汉单例模式,但是由于所有的变量用了final修饰,因此不会有线程安全问题
public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
private final int retryCount; // 方法重试的次数
private final boolean requestSentRetryEnabled; // 如果一个请求发送成功过,是否还会被再次发送
private final Set<Class<? extends IOException>> nonRetriableClasses;
// 构造方法,参数分别是: 重试次数/ 如果一个请求发送成功过,是否还会被再次发送/不进行重试的异常集合
protected DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled,
final Collection<Class<? extends IOException>> clazzes) {
super();
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();
for (final Class<? extends IOException> clazz: clazzes) {
this.nonRetriableClasses.add(clazz);
}
}
// 2个参数构造方法 参数:重试次数/如果一个请求发送成功过,是否还会被再次发送
public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {
this(retryCount, requestSentRetryEnabled, Arrays.asList( InterruptedIOException.class,
UnknownHostException.class,ConnectException.class, SSLException.class));
}
// 上文单例模式使用的构造方法:重试次数设置为3 / 如果一个请求发送成功过,将不会被重新请求/ 不重试的异常集合在2参数构造指定了
public DefaultHttpRequestRetryHandler() {
this(3, false);
}
// 重试策略的方法,如果需要自定义重试策略,则需要覆写这个方法
@Override
public boolean retryRequest(final IOException exception, final int executionCount, final HttpContext context)
Args.notNull(exception, "Exception parameter");
Args.notNull(context, "HTTP context");
if (executionCount > this.retryCount) { // 如果已经执行的次数大于了重试次数,返回false,不进行重试
return false;
}
if (this.nonRetriableClasses.contains(exception.getClass())) { // 如果异常属于2参数构造定义的异常,返回false
return false;
}
// 如果是上面规定的集合异常的子类,返回false,则不重试
for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
if (rejectException.isInstance(exception)) {
return false;
}
}
// 判断当前请求是否已经被终止了,这个是避免当前请求被放入异步的异步的HttpRequestFutureTask中, 当这个异步任务被cancel的时 候,会通过AtomicBoolean的compareAndSet的方法,保证状态被更改
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final HttpRequest request = clientContext.getRequest();
if(requestIsAborted(request)){
return false;
}
// 判断请求是否是幂等请求,所有包含http body的请求都认为是非幂等的,比如post/put等,幂等的请求可以直接重试,比如get
if (handleAsIdempotent(request)) {
return true;
}
// 根据上下文判断请求是否发送成功了,或者根据状态为是否永远可以重复发送(默认的是否),没有发送成功,可以重试
if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
return true;
}
// 否则不需要重试
return false;
}
}
请求如果没有发送成功,则进行重试。判断请求是否成功:通过HttpCoreContext类的变量HTTP_REQ_SENT确定请求是否发送成功,如果为true,则说明请求发送成功,否则失败。
- 请求之前将http.request_sent 置为 false
- 通过流 flush 数据到服务端。如果出现异常即是发送失败
- 然后将 http.request_sent 置为 true
业务重试策略
不重试的异常:
- InterruptedIOException: 线程中断异常
- UnknownHostException: 找不到对应的host
- ConnectException: 找到了host但是建立连接失败
- SSLException: https认证异常
超时异常,继承于SocketTimeoutException,Socket..Exception继承于继承自 InterruptedIOException,属于线程中断异常不会进行重试
- java.net.SocketTimeoutException: Read timed out
- java.net.SocketTimeoutException: connect timed out
重试:
- 只有发生了IO异常时才会发生重试
- InterruptedIOException、UnknownHostException、ConnectException、SSLException ,发生这4中异常不重试
- GET方法可以重试3次,POST方法在socket对应的输出流没有被write并flush成功时可以重试3次
- 连接超时,读/写超时不进行重试
- socket 传输中被重置或关闭会进行重试
证书问题导致的HttpClient访问异常
即访问https站点的时候,因为证书信任问题导致访问异常。
解决方案
非连接池:推荐方案一
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[] {"SSLv2Hello", "SSLv3", "TLSv1","TLSv1.1", "TLSv1.2" },
null,
NoopHostnameVerifier.INSTANCE);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
HttpClient client = null;
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HostnameVerifier verifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
};
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
verifier);
client = HttpClientBuilder.create().setSSLSocketFactory(sslConnectionSocketFactory).build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 旧版本可能有这种解决方案,不推荐使用
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
X509HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, hostnameVerifier);
连接池
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
/*去除服务端的SSL证书校验,信任SSL访问*/
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSSLContext(sslContext);
NoopHostnameVerifier noopHostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslContext, noopHostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry =
RegistryBuilder<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build();
//使用Httpclient连接池的方式配置(推荐)
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new
PoolingHttpClientConnectionManager(socketFactoryRegistry);
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
e.printStackTrace();
}
PoolingHttpClientConnectionManager cm = (socketFactoryRegistry != null) ?
new PoolingHttpClientConnectionManager(socketFactoryRegistry):
new PoolingHttpClientConnectionManager();
java环境下整合HttpClient的连接池
连接池配置对象
连接池父接口是:HttpClientConnectionManager,有两个实现类:
- BasicHttpClientConnectionManager
- PoolingHttpClientConnectionManager
BasicHttpClientConnectionManager每次只管理一个connection。不过,虽然它是thread-safe的,但由于它只管理一个连接,所以只能被一个线程使用。它在管理连接的时候如果发现有相同route的请求,会复用之前已经创建的连接,如果新来的请求不能复用之前的连接,它会关闭现有的连接并重新打开它来响应新的请求
PoolingHttpClientConnectionManager与BasicHttpClientConnectionManager不同,它管理着一个连接池。它可以同时为多个线程服务。每次新来一个请求,如果在连接池中已经存在route相同并且可用的connection,连接池就会直接复用这个connection;当不存在route相同的connection,就新建一个connection为之服务;如果连接池已满,则请求会等待直到被服务或者超时。
作用:Tcp建立连接需要三次握手,如果针对没一次的HttpClient都重新建立一个连接,那么在大量请求的情况下,开销比较大,对此可以利用Http的连接池,提前建立好一批连接,使用的时候从连接池获取,这样可以省略掉每次都需要建立连接的开销,提高吞吐量。
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal();(int maxTotal); // 设置整个连接池的最大连接数
connectionManager.setDefaultMaxPerRoute(int maxPerRoute); // 设置每个route默认的最大连接数
// 下面这种方案也可以设置route的最大连接数,优先于defaultMaxPerRoute
HttpHost httpHost = new HttpHost();
httpHost.set(hostname, port);
connectionManager.setMaxPerRoute(new HttpRoute(httpHost), int maxRoute);
connectionManager.setDefaultSocketConfig(SocketConfig socketConfig);
connectionManager.setScoketConfig(HttpHost httpHost, SocketConfig socketConfig);//优先于setDefaultSocketConfig
connectionManager.setDefaultConnectionConfig(ConnectionConfig connectionConfig);
connectionManager.setConnectionConfig(HttpHost httpHost, ConnectionConfig connectionConfig);
connectionManager.setValidateAfterInactivity(boolean ms);
SocketConfig配置
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();
socketConfigBuilder.setTcpNoDelay(true); // 是否立即发送数据,设置为true会关闭Socket缓冲,默认为false
socketConfigBuilder.setSoTimeout(500); // 接收数据的等待超时时间,单位ms
socketConfigBuilder.setSoLinger(60); // 关闭Socket时,要么发送完所有数据,要么等待60s后,就关闭连接,此时socket.close()是阻塞的
socketConfigBuilder.setSoKeepAlive(true); // 开启监视TCP连接是否有效
socketConfigBuilder.setBacklogSize(100); // backlog, 设置容量限制功能,避免太多的客户端socket占用太多服务器资源
socketConfigBuilder.setRcvBufSize(8192); // 设置接收缓冲区
socketConfigBuilder.setSndBufSize(8192); // 发送缓冲区的大小
socketConfigBuilder.setSoReuseAddress(true); // 如果网络上仍然有数据向旧的ServerSocket传输数据,是否允许新的ServerSocket绑定到与旧的ServerSocket同样的端口上。端口重用,与操作系统相关。是否可以在一个进程关闭Socket后,即使它还没有释放端口,其它进程还可以立即重用端口
SocketConfig socketConfig = socketConfigBuilder.build();
connectionManager.setDefaultSocketConfig(socketConfig);
SpringBoot环境下HttpClient链接池整合RestTemplate
需要的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.22.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
SpringBoot的1.5.22版本依赖的httpclient版本是4.5.9
application.properties配置文件配置连接池参数
#最大的连接数
spring.http.maxTotalConnect=300
#并发数,如果是同一个地址,则可以将其设置为最大数,很重要的参数
spring.http.defaultMaxPerRoute=100
#创建链接的最长时间,指客户端和服务器建立连接的超时时间-单位ms, 最大约21秒,因为内部tcp在进行三次握手建立连接时,默认tcp超时时间是20秒
spring.http.connectTimeout=3000
#从连接池中获取到连接的最长时间,不能过大,否则大量大连接等待
spring.http.connectionRequestTimeout=200
#数据传输的最长时间 不宜过大-单位ms
spring.http.readTimeout=15000
#设置重试的次数
spring.http.retryTimes=3
#提交请求前测试连接是否可用
spring.http.staleConnectionCheckEnabled=true
#默认的长连接保持时间
spring.http.keepAliveTime=10
spring.http.charset=utf-8
#设置不同地址的长连接保持的时间
spring.http.keepAliveTargetHost[www.baidu.com]=5
整合配置java配置类实现
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ConfigurationProperties(prefix = "spring.http")
@PropertySource("classpath:application.properties")
public class HttpPoolConfig {
// java配置的优先级低于yml或者properties配置;如果yml或者properties配置不存在,会采用java配置
private Integer maxTotalConnect;
private Integer defaultMaxPerRoute;
private Integer connectTimeout;
private Integer connectionRequestTimeout;
private Integer readTimeout;
private String staleConnectionCheckEnabled;
private Integer retryTimes;
private String charset;
private Integer keepAliveTime = 60;
private Map<String, Integer> keepAliveTargetHost;
public Integer getMaxTotalConnect() {
return maxTotalConnect;
}
public void setMaxTotalConnect(Integer maxTotalConnect) {
this.maxTotalConnect = maxTotalConnect;
}
public Integer getDefaultMaxPerRoute() {
return defaultMaxPerRoute;
}
public void setDefaultMaxPerRoute(Integer defaultMaxPerRoute) {
this.defaultMaxPerRoute = defaultMaxPerRoute;
}
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Integer getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectionRequestTimeout(Integer connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public Integer getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
this.readTimeout = readTimeout;
}
public String getStaleConnectionCheckEnabled() {
return staleConnectionCheckEnabled;
}
public void setStaleConnectionCheckEnabled(String staleConnectionCheckEnabled) {
this.staleConnectionCheckEnabled = staleConnectionCheckEnabled;
}
public Integer getRetryTimes() {
return retryTimes;
}
public void setRetryTimes(Integer retryTimes) {
this.retryTimes = retryTimes;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public Integer getKeepAliveTime() {
return keepAliveTime;
}
public void setKeepAliveTime(Integer keepAliveTime) {
this.keepAliveTime = keepAliveTime;
}
public Map<String, Integer> getKeepAliveTargetHost() {
return keepAliveTargetHost;
}
public void setKeepAliveTargetHost(Map<String, Integer> keepAliveTargetHost) {
this.keepAliveTargetHost = keepAliveTargetHost;
}
}
@Configuration
@Component
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
public class HttpClientConfig {
@Autowired
private HttpPoolConfig httpPoolConfig;
@Bean(name = "restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* @Description 创建Http客户端的工厂
* @param
* @return org.springframework.http.client.ClientHttpRequestFactory
* @throws
*/
@Bean(name = "clientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory() {
if (httpPoolConfig.getMaxTotalConnect() <= 0) {
throw new IllegalArgumentException("maxTotalConnection config error: " + httpPoolConfig.getMaxTotalConnect());
}
if (httpPoolConfig.getDefaultMaxPerRoute() <= 0) {
throw new IllegalArgumentException("defaultMaxPerRoute config error: " + httpPoolConfig.getDefaultMaxPerRoute());
}
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
// 建立连接超时
httpRequestFactory.setConnectTimeout(httpPoolConfig.getConnectTimeout());
// 从连接池获取请求连接的超时时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
httpRequestFactory.setConnectionRequestTimeout(httpPoolConfig.getConnectionRequestTimeout());
// 数据读取超时时间,即SocketTimeout
httpRequestFactory.setReadTimeout(httpPoolConfig.getReadTimeout());
return httpRequestFactory;
}
@Bean(name = "httpClientTemplate")
@Qualifier(value = "clientHttpRequestFactory")
public RestTemplate httpClientTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
return createRestTemplate(clientHttpRequestFactory);
}
@Bean(name = "httpClient")
public HttpClient httpClient() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
/*去除服务端的SSL证书校验,信任SSL访问*/
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSSLContext(sslContext);
NoopHostnameVerifier noopHostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, noopHostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build();
//使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// 设置最大的连接数
poolingHttpClientConnectionManager.setMaxTotal(httpPoolConfig.getMaxTotalConnect());
// 设置路由并发数
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpPoolConfig.getDefaultMaxPerRoute());
// 配置连接池
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
// 设置重试次数
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpPoolConfig.getRetryTimes(), true));
// 设置默认的请求头
httpClientBuilder.setDefaultHeaders(defaultHttpHeaders());
// 设置长连接的保持策略
httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());
return httpClientBuilder.build();
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
e.printStackTrace();
}
return null;
}
/**
* @Description 长连接策略
* @param
* @return org.apache.http.conn.ConnectionKeepAliveStrategy
* @throws
*/
private ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse httpResponse, HttpContext httpContext) {
BasicHeaderElementIterator iterator = new BasicHeaderElementIterator(httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (iterator.hasNext()) {
HeaderElement headerElement = iterator.nextElement();
String headerName = headerElement.getName();
String headerValue = headerElement.getValue();
if (headerValue != null && "timeout".equalsIgnoreCase(headerName)) {
try {
return Long.parseLong(headerValue) * 1000;
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
HttpHost httpHost = (HttpHost) httpContext.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpPoolConfig.getKeepAliveTargetHost())
.orElseGet(HashMap::new)
.entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(httpHost.getHostName()))
.findAny();
// 如若配置了,使用配置的长连接保持时间,否则使用默认的长连接保持时间
return any.map(value -> value.getValue() * 1000L).orElse(httpPoolConfig.getKeepAliveTime() * 1000L);
}
};
}
/**
* @Description 设置默认的请求头
* @param
* @return java.util.List<org.apache.http.Header>
* @throws
*/
private List<Header> defaultHttpHeaders() {
List<Header> headers = new ArrayList<>();
// User-Agent: 访问者是通过什么工具来请求 Mozilla/5.0 (平台) 引擎版本 浏览器版本号
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36"));
// accept-encoding: 客户端可以解压的格式的数据
headers.add(new BasicHeader("accept-encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
return headers;
}
private RestTemplate createRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
// 设置编码为UTF-8,解决乱码问题
setDefaultCharset(restTemplate);
// 设置错误处理器
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
private void setDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(httpPoolConfig.getCharset());
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}
}
浙公网安备 33010602011771号