问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

HttpClient使用不当,服务挂了!是时候系统学习一下了

发布网友 发布时间:2024-10-02 05:45

我来回答

1个回答

热心网友 时间:2024-11-15 10:35

背景

最近发生了两件事,觉得有必要系统的学习一下Apache的HttpClient了。

事件一:联调微信支付接口,用到HttpClient,花时间整理了一番。如果有一篇文章,读一读就可以掌握HttpClient 80%的内容,再有可以直接用的Demo,下次再遇到是不是就可以非常容易集成了?这篇便是这篇文章的目标之一。

事件二:上家公司同事发消息求助,说系统JVM溢出,找不到原因不了。查看了发来的日志文件,基本定位是HttpClient调用三方接口时内存溢出导致的。

无论出于哪种原因,HTTP调用的熟练使用都是必不可少的,今天就来一起系统学习一下,查漏补缺。

HttpClient

HTTP协议的重要性不言而喻,它是现在Internet中使用最多,最重要的协议了。虽然JDK中已经提供了HTTP协议的基本功能,但对于大部分应用来说,这套API还是不够丰富和灵活。

HttpClient是用来编程实现HTTP调用的一款框架,它是Apache Jakarta Common下的子项目,相比传统JDK自带的URLConnection,增加了易用性和灵活性。

HttpClient不仅使客户端发送Http请求变得更加容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性。

目前主流的SpringCloud框架,服务与服务之间的调用也全部是基于HttpClient来实现的。因此,系统的学习一下HttpClient,还是非常有必要的。

HttpClient功能及特性

HttpClient主要提供了以下功能及特性实现:

基于标准、纯净的java语言。实现了HTTP 1.0和HTTP 1.1;

以可扩展的面向对象的结构实现了HTTP全部的方法(GET、 POST、PUT、DELETE、HEAD、OPTIONS、TRACE)等。

支持HTTPS协议。

通过HTTP代理建立透明的连接。

利用CONNECT方法通过HTTP代理建立隧道的HTTPs连接。

Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos认证方案。

插件式的自定义认证方案。

便携可靠的套接字工厂使它更容易的使用第三方解决方案。

连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。

自动处理Set-Cookie中的Cookie。

插件式的自定义Cookie策略。

Request的输出流可以避免流中内容直接缓冲到Socket服务器。

Response的输入流可以有效的从Socket服务器直接读取相应内容。

在HTTP 1.0和HTTP1.1中利用KeepAlive保持持久连接。

直接获取服务器发送的response code和 headers。

设置连接超时的能力。

实验性的支持HTTP1.1 response caching。

源代码基于Apache License 可免费获取。

支持自动(跳转)转向;

关于以上特性,了解即可,用到时再进行深入学习和实践。

HttpClient使用步骤

使用HttpClient来发送请求、接收响应通常有以下步骤:

引入依赖:项目中通过Maven等形式引入HttpClient依赖类库。

创建HttpClient对象。

创建请求方法实例:GET请求创建HttpGet对象,POST请求创建HttpPost对象,并在对象构建时指定请求URL。

设置请求参数:调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;HttpPost也可调用setEntity(HttpEntity entity)方法来设置请求参数。

发送请求:调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

获取响应结果:调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。

释放连接:无论执行方法是否成功,都必须释放连接。

以上便是使用HttpClient的核心步骤:引入依赖、创建HttpClient对象、创建请求实例、设置请求参数、发送请求、获取请求结果、释放连接。

文章刚开始提到的事件二,便是由于释放连接不当导致连接累积导致内存溢出。

了解了HttpClient的使用步骤,就可以具体的代码实现了。

实例代码实战

在项目中引入HttpClient依赖:

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency>Get请求示例

先以Get请求为例,展示一下调用百度搜索Java关键字:

@TestpublicvoidtestGet()throwsIOException{//1、构建HttpClient对象CloseableHttpClienthttpClient=HttpClients.createDefault();//2、创建HttpGet,声明get请求HttpGethttpGet=newHttpGet("http://www.baidu.com/s?wd=java");//3、发送请求CloseableHttpResponseresponse=httpClient.execute(httpGet);//4.判断状态码if(response.getStatusLine().getStatusCode()==200){HttpEntityentity=response.getEntity();//使用工具类EntityUtils,从响应中取出实体表示的内容并转换成字符串Stringstring=EntityUtils.toString(entity,"utf-8");System.out.println(string);}//5、关闭资源response.close();httpClient.close();}

执行上述代码,HttpClient调用成功,控制台会打印出百度返回结果的HTML信息。这个过程也遵循了上面说到的HttpClient的使用步骤。

上述代码看似能够正常使用,但在执行的过程中如果出现异常,则会出现连接无法正常释放,导致内存溢出问题。

对上述代码进行改进:

@TestpublicvoidtestGet(){CloseableHttpClienthttpClient=null;CloseableHttpResponseresponse=null;try{//1、构建HttpClient对象httpClient=HttpClients.createDefault();//2、创建HttpGet,声明get请求HttpGethttpGet=newHttpGet("http://www.baidu.com/s?wd=java");//3、发送请求response=httpClient.execute(httpGet);//4.判断状态码if(response.getStatusLine().getStatusCode()==200){HttpEntityentity=response.getEntity();//使用工具类EntityUtils,从响应中取出实体表示的内容并转换成字符串Stringstring=EntityUtils.toString(entity,"utf-8");System.out.println(string);}}catch(Exceptione){//打印堆栈信息,进行异常情况处理;}finally{//5、关闭资源if(response!=null){try{response.close();}catch(IOExceptione){e.printStackTrace();}}if(httpClient!=null){try{httpClient.close();}catch(IOExceptione){e.printStackTrace();}}}}

虽然代码复杂了一些,但此时无论是否出现异常,都可以将连接进行正常的关闭,避免内存溢出。

在上述代码中,其中HttpGet的参数是直接拼接到HTTP连接后面的,当然也可以通过URI来构建,代码实现如下:

HttpGethttpGet=newHttpGet("http://www.baidu.com/s?wd=java");//上述实现等价于下面的实现;URIuri=newURIBuilder("http://www.baidu.com/s").setParameter("wd","java").build();HttpGethttpGet=newHttpGet(uri);

当然,针对资源释放部分,还可以利用Java 8提供的try-with-resources语法糖来进行简化代码。

Post请求示例

下面的实例中的Post请求相对Get请求,多了添加Header参数和Http的Entity参数:

@TestpublicvoidtestPost(){CloseableHttpClienthttpClient=null;CloseableHttpResponseresponse=null;try{//1.打开浏览器httpClient=HttpClients.createDefault();//2.声明get请求HttpPosthttpPost=newHttpPost("https://www.oschina.net/");//3.网站为了防止恶意攻击,在post请求中都*了浏览器才能访问httpPost.addHeader("User-Agent","Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/68.0.3440.106Safari/537.36");//4.判断状态码List<NameValuePair>parameters=newArrayList<>(0);parameters.add(newBasicNameValuePair("scope","project"));parameters.add(newBasicNameValuePair("q","java"));UrlEncodedFormEntityformEntity=newUrlEncodedFormEntity(parameters,"UTF-8");httpPost.setEntity(formEntity);//5.发送请求response=httpClient.execute(httpPost);if(response.getStatusLine().getStatusCode()==200){HttpEntityentity=response.getEntity();Stringstring=EntityUtils.toString(entity,"utf-8");System.out.println(string);}}catch(Exceptione){//打印堆栈信息,进行异常情况处理;}finally{//5、关闭资源if(response!=null){try{response.close();}catch(IOExceptione){e.printStackTrace();}}if(httpClient!=null){try{httpClient.close();}catch(IOExceptione){e.printStackTrace();}}}}

Post请求部分与Get请求的关键区别在于构建的请求对象不同,传输的参数不再局限于URL的拼接,还可以基于Entity来进行传输。我们在实践的过程中,大多数也是将数据放在Entity中基于JSON等格式进行传输。

HttpClient超时配置

正常来说上面的代码已经基本满足了业务需求,但还是有需要完善的地方,特别是针对HTTP请求超时情况的处理。

HttpClient对此提供了setConfig(RequestConfig config)方法来为请求配置超时时间等,部分核心代码如下:

//设置配置请求参数(没有可忽略)RequestConfigrequestConfig=RequestConfig.custom().setConnectTimeout(35000)//连接主机服务超时时间.setConnectionRequestTimeout(35000)//请求超时时间.setSocketTimeout(60000)//数据读取超时时间.build();//为httpGet实例设置配置httpGet.setConfig(requestConfig);

关于上述配置的重要性,也是不容忽视的。否则可能会导致请求阻塞,影响性能等问题。

HttpClient工具类封装

看完上述使用,是不是发现HttpClient的使用非常简单、便捷?其实,还可以根据具体是使用场景,进一步进行封装,封装成工具类,业务使用时直接调用即可。

关于HttpClientUtil的封装有很多方式,这里提供一种封装,仅供参考:

importorg.apache.http.HttpStatus;importorg.apache.http.NameValuePair;importorg.apache.http.client.config.RequestConfig;importorg.apache.http.client.entity.UrlEncodedFormEntity;importorg.apache.http.client.methods.CloseableHttpResponse;importorg.apache.http.client.methods.HttpGet;importorg.apache.http.client.methods.HttpPost;importorg.apache.http.client.utils.URIBuilder;importorg.apache.http.entity.StringEntity;importorg.apache.http.impl.client.CloseableHttpClient;importorg.apache.http.impl.client.HttpClients;importorg.apache.http.message.BasicNameValuePair;importorg.apache.http.util.EntityUtils;importjava.io.IOException;importjava.net.URI;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;/***http请求客户端**@authorzzs*/publicclassHttpClientUtil{privatestaticRequestConfigrequestConfig=null;privateHttpClientUtil(){}static{//设置http的状态参数requestConfig=RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).setConnectionRequestTimeout(5000).build();//TODO补充其他配置}publicstaticStringdoGet(Stringurl,Map<String,String>param){//创建Httpclient对象CloseableHttpClienthttpClient=HttpClients.createDefault();StringresultString="";CloseableHttpResponseresponse=null;try{//创建uriURIBuilderbuilder=newURIBuilder(url);if(param!=null){for(Stringkey:param.keySet()){builder.addParameter(key,param.get(key));}}URIuri=builder.build();//创建httpGET请求HttpGethttpGet=newHttpGet(uri);httpGet.setConfig(requestConfig);//执行请求response=httpClient.execute(httpGet);//判断返回状态是否为200if(response.getStatusLine().getStatusCode()==200){resultString=EntityUtils.toString(response.getEntity(),"UTF-8");}}catch(Exceptione){//TODO完善异常处理e.printStackTrace();}finally{try{if(response!=null){response.close();}if(httpClient!=null){httpClient.close();}}catch(IOExceptione){e.printStackTrace();}}returnresultString;}publicstaticStringdoGet(Stringurl){returndoGet(url,null);}publicstaticStringdoPost(Stringurl,Map<String,Object>param){//创建Httpclient对象CloseableHttpClienthttpClient=HttpClients.createDefault();CloseableHttpResponseresponse=null;StringresultString="";try{//创建HttpPost请求HttpPosthttpPost=newHttpPost(url);httpPost.setConfig(requestConfig);//创建参数列表if(param!=null){List<NameValuePair>paramList=newArrayList<>();for(Stringkey:param.keySet()){paramList.add(newBasicNameValuePair(key,(String)param.get(key)));}//模拟表单UrlEncodedFormEntityentity=newUrlEncodedFormEntity(paramList);httpPost.setEntity(entity);}//执行http请求response=httpClient.execute(httpPost);resultString=EntityUtils.toString(response.getEntity(),"utf-8");}catch(Exceptione){//TODO完善异常处理e.printStackTrace();}finally{try{if(response!=null){response.close();}if(httpClient!=null){httpClient.close();}}catch(IOExceptione){e.printStackTrace();}}returnresultString;}publicstaticStringdoPost(Stringurl){returndoPost(url,null);}publicstaticStringdoPostJson(Stringurl,Stringjson,Stringtoken_header)throwsException{//创建Httpclient对象CloseableHttpClienthttpClient=HttpClients.createDefault();CloseableHttpResponseresponse=null;StringresultString="";try{//创建HttpPost请求HttpPosthttpPost=newHttpPost(url);httpPost.setConfig(requestConfig);//创建请求内容httpPost.setHeader("HTTPMethod","POST");httpPost.setHeader("Connection","Keep-Alive");httpPost.setHeader("Content-Type","application/json;charset=utf-8");httpPost.setHeader("x-authentication-token",token_header);StringEntityentity=newStringEntity(json);entity.setContentType("application/json;charset=utf-8");httpPost.setEntity(entity);//执行http请求response=httpClient.execute(httpPost);if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK){resultString=EntityUtils.toString(resp
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
天将雄师观后感600字 为什么喝酒会降血糖 牛栏山 52度浓香型白酒-购买最佳价格 全兴大曲 52度浓香型白酒-购买最佳价格 笔记本电脑怎么接音箱电脑如何连接外置音箱 笔记本电脑有音箱插孔吗笔记本电脑如何连接喇叭 冰箱压缩机过热启动5秒就停了 周长相等的正方形和圆,其面积比是多少 周长相等的正方形和圆面积比是多少 周长相等的正方形与圆形,边长与半径的比是( ),面积之比是( )。 要有... ...在什么情况下,最有魅力。她们最吸引什么样的星座、男ǔ 索尼爱立信PSPPhone的娱乐功能 索尼爱立信W908c商务功能 索尼爱立信 G700商务版基本功能 如何搞定闷骚男ǔ 索尼爱立信P200主要参数 金融学专业在职研究生申请硕士学位 铝的特征是什么意思? 索尼爱立信W595c商务功能 索尼爱立信W100i商务功能 索尼爱立信W150 Yendo商务功能 我今年七月二本毕业,学的是国际贸易,但我英语不怎么行,读写还行,... 我是外地的本科大学生,今年七月毕业后来到广州一个小的私企工作,请问如 ... 我是一名女高职生,将于今年七月份从学校毕业,学的是数控专业。想问一... 我十八岁,今年七月将从新华中学毕业 汉译英 北京必去的10个公园 ...假定你是李华,将于今年七月从新星外语学校毕业。你从报纸上得知B&amp;B... 北京必去打卡地点有哪些,北京有哪些必去的景点? 区别脊椎动物和无脊椎动物的主要依据是体内有无( )A.脊柱B.神经C.肌肉... 为什么小米手机的铃声不响了呢? 面试官说:你来设计一个短链接生成系统吧 ...C:\windows\system32\senices.exe意外终止,状态码为-1073740972,_百 ... 单身公主相亲记中哪集是常胜把八妹改变成甜美公主的形象 联想笔记本怎么查行货还是假的? 每天都有很多同事问另一个同事的去向,因为有事找他,我很烦,怎样不... ...这是不是客套话?没有人会喜欢别人一直问自己许多问题是吧?_百度知 ... mac的bios在哪里设置u盘启动? 手提电脑装了九阴真经OL之后,玩游戏直接闪回桌面!(游戏并没有退出,只... macbook如何设置U盘启动? 小弟想玩九阴真经,请问现在想玩是不是只有买那个激活码,然后等到2月25... ...得到九阴真经荣誉+2,为什么不得荣誉了,以前是有的,不知道谁也发现了... 2012年2月15日九阴真经全国内测的几个问题 在家如何能做出好吃到爆的水煮鱼? 猪乳房炎概述 周杰伦唱响安阳总共演唱了几首歌,都是什么歌 安阳小周杰伦的《我不是他》的歌词谁知道? 加油的英文是fighting.还是flghting 学生办银行卡需要什么资料 考银行要准备什么 抚州有哪些中介公司