网站首页 > 技术文章 正文
前言
HttpClient可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
使用HttpClient发送请求和接收响应的步骤:
- 创建CloseableHttpClient对象;
- 创建请求方法实例,并指定请求URL。例:如果要发送Get请求,创建HttpGet对象;如果要发送POST请求,创建HttpPost对象;
- 如果需要发送参数,则调用setEntity(HttpEntity entity)方法来设置参数;
- 调用HttpGet/HttpPost对象的setHeader(String name,String value)方法设置header信息,或者调用setHeader(Header[] headers)设置一组header参数;
- 调用CloseableHttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个CloseableHttpResponse;
- 调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容;调用CloseableHttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;
- 释放连接。无论执行方法是否成功,都必须释放连接
1. 引入Maven依赖
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
1. HttpClient连接池分析
PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务。主要是分配连接,回收连接。同一个远程请求,会优先使用连接池提供的空闲的长连接。
默认构造方法:
/** * @since 4.4 */ public PoolingHttpClientConnectionManager( final HttpClientConnectionOperator httpClientConnectionOperator, final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final long timeToLive, final TimeUnit timeUnit) { super(); this.configData = new ConfigData(); //连接池的默认配置defaultMaxPerRoute默认为2,maxTotal默认为20 this.pool = new CPool(new InternalConnectionFactory( this.configData, connFactory), 2, 20, timeToLive, timeUnit); //官方推荐使用这个来检查永久链接的可用性,而不推荐每次请求的时候才去检查 this.pool.setValidateAfterInactivity(2000); this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator"); this.isShutDown = new AtomicBoolean(false); }
- maxTotal:连接池的最大连接数。
- defaultMaxPreRount:每个Rount(远程)请求最大的连接数。
- setValidateAfterInactivity:连接空闲多长时间(单位:毫秒)进行检查。
显示的调整连接池参数:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // Increase max total connection to 200 cm.setMaxTotal(200); // Increase default max connection per route to 20 cm.setDefaultMaxPerRoute(20); // Increase max connections for localhost:80 to 50 HttpHost localhost = new HttpHost("locahost", 80); cm.setMaxPerRoute(new HttpRoute(localhost), 50); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build();
2. SpringBoot集成HttpClient
2.1 超时时间设置
httpClient内部有三个超时时间设置:获取连接的超时时间、建立连接的超时时间、读取数据超时时间。
//设置网络配置器 @Bean public RequestConfig requestConfig(){ return RequestConfig.custom().setConnectionRequestTimeout(2000) //从链接池获取连接的超时时间 .setConnectTimeout(2000) //与服务器连接超时时间,创建socket连接的超时时间 .setSocketTimeout(2000) //socket读取数据的超时时间,从服务器获取数据的超时时间 .build(); }
1. 从连接池中获取可用连接超时
HttpClient中的要用连接时尝试从连接池中获取,若是在等待了一定的时间后还没有获取到可用连接(比如连接池中没有空闲连接了)则会抛出获取连接超时异常。
2. 连接目标超时connectionTimeout
指的是连接目标url的连接超时时间,即客服端发送请求到与目标url建立起连接的最大时间。如果在该时间范围内还没有建立起连接,则就抛出connectionTimeOut异常。
如测试的时候,将url改为一个不存在的url:“http://test.com” , 超时时间3000ms过后,系统报出异常: org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms
3. 等待响应超时(读取数据超时)socketTimeout
连接上一个url后,获取response的返回等待时间 ,即在与目标url建立连接后,等待放回response的最大时间,在规定时间内没有返回响应的话就抛出SocketTimeout。
测试的时候的连接url为我本地开启的一个url,http://localhost:8080/firstTest.htm?method=test,在我这个测试url里,当访问到这个链接时,线程sleep一段时间,来模拟返回response超时。
2.2 KeepAliveStrategy策略
keep-alive详解 —— 通过使用Keep-alive机制,可以减少tcp连接建立的次数,也以为这可以减少TIME_WAIT状态连接,以此提高性能和提高HTTP服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。但是长时间的tcp连接容易导致系统资源无效占用,配置不当的Keep-alive有事比重复利用连接带来的损失还更大。所以正确地设置Keep-alive timeout时间非常重要。
Keep-alive:timeout=5,max=100的含义。
意思是说:过期时间5秒,max是最多100次请求,强制断掉连接,也就是在timeout时间内每来一个新的请求,max会自动减1,直到为0,强制断掉连接。
需要注意的是:使用keep-alive要根据业务情况来定,若是少数固定客户端,长时间高频次的访问服务器,启用keep-client非常合适!
在HttpClient中默认的keepClient策略:
org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy
默认的话,是读取response中的keep-alive中的timeout参数,若是没有读到,那么设置为-1,这个代表无穷,但是这样设置便存在问题。因为现实中的HTTP服务器配置了在特定不活动周期之后丢掉连接来保存系统资源,往往是不通知客户端的。
默认的keep-alive策略
@Contract(threading = ThreadingBehavior.IMMUTABLE) public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy(); @Override public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) { Args.notNull(response, "HTTP response"); final HeaderElementIterator it = new BasicHeaderElementIterator( response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { final HeaderElement he = it.nextElement(); final String param = he.getName(); final String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout")) { try { return Long.parseLong(value) * 1000; } catch(final NumberFormatException ignore) { } } } return -1; } }
解决方案:可以自定义keep-alive策略,如果没有读到,则设置保存连接为60s。
@Bean public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); //设置连接池 httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); //设置超时时间 httpClientBuilder.setDefaultRequestConfig(requestConfig()); //定义连接管理器将由多个客户端实例共享。如果连接管理器是共享的,则其生命周期应由调用者管理,如果客户端关闭则不会关闭。 httpClientBuilder.setConnectionManagerShared(true); //设置KeepAlive ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { public long getKeepAliveDuration(HttpResponse response, HttpContext context) { // Honor 'keep-alive' header HeaderElementIterator it = new BasicHeaderElementIterator( response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase("timeout")) { try { return Long.parseLong(value) * 1000; } catch(NumberFormatException ignore) { } } } HttpHost target = (HttpHost) context.getAttribute( HttpClientContext.HTTP_TARGET_HOST); if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) { // Keep alive for 5 seconds only return 5 * 1000; } else { // otherwise keep alive for 30 seconds return 30 * 1000; } } }; httpClientBuilder.setKeepAliveStrategy(myStrategy); return httpClientBuilder; }
2.3 Connection eviction policy(连接逐出策略)
当一个连接被释放到连接池时,它可以保持活动状态而不能监控socket的状态和任何I/O事件。如果连接在服务器端被关闭,那么客户端连接也不能侦测连接状态中的变化和关闭本端的套接字去做出适当响应。
HttpClient尝试通过测试连接是否有效来解决该问题,但是它在服务器端关闭,失效的连接检查不是100%可靠。唯一的解决方案:创建监控线程来回收因为长时间不活动而被认为过期的连接。
2.4 HttpClient的重试机制
HttpClient使用连接池PoolingHttpClientConnectionManager
设置重试策略:org.apache.http.impl.client.DefaultHttpRequestRetryHandler
重试机制的源码:org.apache.http.impl.execchain.RetryExec#execute
在默认情况下,httpClient会使用默认的重试策略DefaultHttpRequestRetryHandler(不管你设置不设置)。
默认策略的构造方法:
public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { this(retryCount, requestSentRetryEnabled, Arrays.asList( InterruptedIOException.class, UnknownHostException.class, ConnectException.class, SSLException.class)); }
不重试的异常
关于HttpClient重试策略的研究
- InterruptedIOException,线程中断异常
- UnknownHostException,找不到对应host
- ConnectException,找到了host但是建立连接失败。
- SSLException,https认证异常
另外,我们还经常会提到两种超时,连接超时与读超时:
- java.net.SocketTimeoutException: Read timed out
- java.net.SocketTimeoutException: connect timed out
- 这两种超时都是SocketTimeoutException,继承自InterruptedIOException,属于上面的第1种线程中断异常,不会进行重试。
猜你喜欢
- 2024-10-13 谈谈springboot 获取前端json数据几种方法
- 2024-10-13 在Spring Boot中如何获取到Request对象?
- 2024-10-13 SpringBoot:如何优雅地进行响应数据封装、异常处理
- 2024-10-13 SpringBoot实现接口防抖的几种方案,杜绝重复提交
- 2024-10-13 @PostMapping @GetMapping注解 postmapping注解接收参数
- 2024-10-13 如何在SpringBoot中动态过滤JSON响应正文
- 2024-10-13 WebSocket 集群解决方案 websocket500
- 2024-10-13 SpringBoot跨系统调用接口方案 springboot跨越设置
- 2024-10-13 SpringBoot如何优雅的进行参数校验(一)
- 2024-10-13 IntelliJ IDEA必装插件以及SpringBoot使用小技巧合集
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)