Java_HttpClient
文章目录
前言
以前有接触过HttpClient,只是用用就没有多想。现在因为看到一个爬虫项目使用了HttpClient,才发现原来
Java的HttpClient和Python的request库一样,功能强大。并且因为有一个设置代理IP的需求,所以对HttpClient进行深入学习,同时进行记录。
关于使用场景,目前我觉得是解析API。除此之外,今后的学习再进行补充。而且HttpClient的源头Http是目前使用者最多,运用广泛的协议,所以非常值得学习。
也非常感谢一些优质博客的博主对于HttpClient的见解,对学习很有帮助。
时间线
20200808-完成初稿
参考链接
-
中文网站
-
中文翻译文档
-
使用心得
轻松把玩HttpClient之封装HttpClient工具类(一)(现有网上分享中的最强大的工具类)
非常好的一个使用系列,其中就包括定制HttpClient等等
API解读
几句常见说法
-
HttpClient不是一个浏览器。这是一个客户端HTTP传输库。
由于使用电脑的生活中,使用浏览器的频率非常高,所以一开始接触到HttpClient时,会将HttpClient当成是一个代码浏览器。可以这么说,但也不可以这么说。
可以这么说,是因为浏览器发送请求,服务端收到请求后返回数据。这些浏览器和HttpClient都能做到。
不可以这么说,是因为浏览器还有一个渲染引擎渲染数据,并解释用户的输入,例如点击哪里,输入什么;
而HttpClient是没有用户界面的,只能靠编码方式解释数据。
但是HttpClient不是一个浏览器,不能拿浏览器所能实现的功能与HttpClient相比较
-
HttpClient4.5.10发布,众网友反应平淡
这句话是来源于HttpClient中文官网,这里引用一下:
虽然HttpClient功能强大,但是外界存在众多对手,例如:JDK11 中自带的 HTTP Client,还有Okhttp等,HttpClient越发显得英雄迟暮。
另外还有自身原因,HttpClient 发一次大的版本,原来的API就用不了,严重打击了用户的使用热情。(原因:每一次更新,API变化非常大)
不断有用户反馈:Apache Httpcomponents HttpClient AIP 设计极其难用,导致越来越多的人选择JDK原生的HTTP工具类。
更有甚者,直接断言:HttpClient已经过气了。
为什么会这样呢,其实我觉得有一个原因,就是每一次更新,API变化非常大。
例如创建HttpClient,
//3.x HttpClient httpClient=newDefaultHttpClient(); //4.x CloseableHttpClient httpClient = HttpClients.createDefault();
除此之外,还有很多地方发生了修改,不再赘述
一些总结
- 连接池最大连接数,默认为20
- 同个route的最大连接数,默认为2
- 去连接池中取连接的超时时间,不配置则无限期等待
- 与目标服务器建立连接的超时时间,不配置则无限期等待
- 去目标服务器取数据的超时时间,不配置则无限期等待
- 要fully consumed entity,才能正确释放底层资源
- 同个host但ip有多个的情况,请谨慎使用单例的HttpClient和连接池
- HTTP1.1默认支持的是长连接,如果想使用短连接,要在request上加Connection:close的header,不然长连接是不可能自动被关掉的!
详细解读
这一部分,只记录一些已经使用到的功能,后面如果有使用到,再进行补充。
RequestConfig的常见名词解释
这个模块在定制HttpClient时非常重要,如果定制地不够完善,还不如使用默认配置,
但这就违背了使用定制的初衷。
3个超时名词
connectTimeout:连接目标超时
connect即连接,表示客户端连接目标url;当客户端发送请求到目标url建立起连接的最大时间;
如果在这个时间范围内,未能连接成功,则立即连接失败,抛出 connectTimeout异常;
socketTimeout :读取数据超时
如果客户端与目标url连接成功后,即客户端成功发送请求,
当在规定的时间内,客户端没有收到“反馈”时,将会抛出socketTimeout;
read timeout
ConnectionRequestTimeout:连接池获取连接超时异常
当客户端前往连接池获取连接时,在指定的时间内没有发现可用连接时,即连接池为空或者其它情况导致没有可用连接的原因,将返回Timeout waiting for connection from pool
所以告诉我们,当连接完成工作后,需要马上释放连接,预防占用。
异常
除了上面的超时异常,还有HttpClient还有以下几种异常
同时也整理引用官方文档API的解释
IOException
在发生I / O故障(如套接字超时或套接字重置)时发生java.io.IOException异常,通常I / O错误被认为是非致命的,可恢复的,
ClientProtocolException
发出HTTP异常(如违反HTTP协议)的HttpException。
而HTTP协议错误被认为是致命的,不能自动从中恢复。
HttpClient实现将HttpExceptions重新抛出为ClientProtocolException,它是java.io.IOException的子类。这使得HttpClient的用户可以从单个catch子句处理I / O错误和协议违规。
实际开发
下载链接
http://hc.apache.org/downloads.cgi
maven依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
完整的框架模板
其实这里有挺多内容的,但是如果一一描述的话,就非常多。
例如Get请求的有参,无参以及对象参数,同样Post请求也是如此。
但是这些有参无参都是共通的,所以将分为一类,即参数框架。
而这个框架是指,正常逻辑,且满足一些常见的异常处理。
大体框架 && Get/Post模板
这里的大体框架是基于Get请求的,即get block;
如果是Put请求,相同的道理
public String doGetOrPost(){
CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().build();
String url = "";//请求链接
//get or post block
HttpGet httpGet = new HttpGet(url);
//HttpPost httpPost = new HttpPost(url);
CloseableHttpResponse closeableHttpResponse = null;
try {
closeableHttpResponse = closeableHttpClient.execute(httpGet);
if (closeableHttpResponse.getStatusLine().getStatusCode()==200){
System.out.println("响应成功,进行相关处理");
} else{
System.out.println("响应失败,进行相关处理");
}
return null;
} catch (ClientProtocolException e) {
e.printStackTrace();
System.out.println("ClientProtocol出现异常");
return null;
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO出现异常");
return null;
}finally {
try {
if(closeableHttpClient != null) {
closeableHttpClient.close();
System.out.println("关闭closeableHttpClient");
}
if(closeableHttpResponse != null) {
closeableHttpResponse.close();
System.out.println("关闭closeableHttpResponse");
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO出现异常");
}
}
}
从上面的代码可以知道以下内容
大体步骤
1、创建HttpClient对象。
常见的方式有两种:http://www.httpclient.cn/archives/43.html
2、处理请求
创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;
对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
3、发送请求,获得响应
调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
4、处理响应
调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
5、释放连接。
无论执行方法是否成功,都必须释放连接
总结
参数请求
关于请求,常见的有GET与POST,而关于参数也有两种:无参和有参
其实有参是在无参的基础上进行添加参数处理操作
实现有参的同时也是在无参的基础上进行,所以这里只记录有参的方式
以及对象参数
详细可以见这里:HttpClient详细使用示例
有参
当连接存在参数时,有以下方式
关于方法
//http://localhost:8080/test @RequestMapping("/test") @ResponseBody public String hasArg(String name,String value){ return name+"-"+value; }
链接拼接
//固定方式 http://localhost:8080/test?name=zhj&value=good //输出:zhj-good //可变方式 StringBuffer params = new StringBuffer(); try { // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去) params.append("name=" + URLEncoder.encode("&", "utf-8")); params.append("&"); params.append("value=24"); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } String url = "http://localhost:8080/test"+'?'+params; //输出:&-good
使用URI获得链接
其实这个就相当于后面的表单提交了,也可以说是对目标链接的拆分
目标链接的每一部分都必须描述清楚
//其实就是描述链接 //之前的都是直接使用String表示,但其实真正的处理逻辑是这样的 //String先转换成以下内容,再进行发送请求 URI uri = null; try { //构造链接 // 将参数放入键值对类NameValuePair中,再放入集合中 List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("name", "&")); params.add(new BasicNameValuePair("value", "18")); // 设置uri信息,并将参数集合放入uri; // 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value) uri = new URIBuilder().setScheme("http").setHost("localhost") .setPort(8080).setPath("/test") .setParameters(params).build(); } catch (URISyntaxException e1) { e1.printStackTrace(); } //输出:&-18
前面的有参的参数类型都是基本数据类型,当有些请求涉及到对象时,即自定义的对象参数时,需要进行一些处理,如下
对象参数
对象参数只有POST方式存在,所以这里使用的函数是
//请求函数
@ResponseBody
@RequestMapping(value = "mypost",method = RequestMethod.POST)
public String mypost(@RequestBody User user){
return user.toString();
}
//链接:http://localhost:8080/mypost
//相关处理
HttpPost httpPost = new HttpPost("http://localhost:8080/mypost");
User user = new User();
user.setId(1);
user.setName("zhj");
user.setAge("18");
String userStr = JSON.toJSONString(user);
StringEntity entity = new StringEntity(userStr, "UTF-8");
//HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(entity);
//非常关键
httpPost.setHeader("Content-Type", "application/json;charset=utf8");
//输出:User{id=1, name='zhj', age='18'}
当对象参数和普通参数共存时,先进行有参的处理方式,获得链接,然后再进行对象参数的处理
默认/定制客户端
这里的配置是指关于HttpClient的配置,具体可以见http://www.httpclient.cn/archives/43.html
简单的来说,就是默认和定制
-
默认
在许多使用记录中有两个身影,如下:
HttpClients.createDefault(); //以及上面实例中的 HttpClientBuilder.create().build(); //其实两者是同一个概念的,因为HttpClient源码中是这样写的 public static CloseableHttpClient createDefault() { return HttpClientBuilder.create().build(); }
-
定制
相对于默认而言,定制更加具有个性化,可以满足个人需求
//例如源码中 public static HttpClientBuilder custom() { return HttpClientBuilder.create(); } //配置好需要的RequetConfig //更多参数可以见源码文件 public static RequestConfig myRequestConfig() { RequestConfig myrequestConfig = RequestConfig.custom() //连接上服务器(握手成功)的时间,超出该时间抛出connect timeout .setConnectTimeout(5000) //服务器返回数据(response)的时间,超过该时间抛出read timeout .setSocketTimeout(5000) //从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出: //ConnectionPoolTimeoutException: Timeout waiting for connection from pool .setConnectionRequestTimeout(5000) .build(); return myrequestConfig; } //然后使用这个配置进行创建 //如果创建定制HttpClient,且未使用上面配置中的任何一个将会造成阻塞状态 //原因:默认为-1 意味着无限大,就是一直阻塞等待! public String myCustomHttpClients(){ CloseableHttpClient closeableHttpClient = HttpClients.custom() .setDefaultRequestConfig(myRequestConfig()) .build(); return null; }
除了上面的这种方式,还有一种方式,即
public String myDefaultHttpClient(){ //创建一个默认的客户端 CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); String url = ""; //进行配置 HttpGet httpGet = new HttpGet(url); //HttpPost httpPost = new HttpPost(url); httpGet.setConfig(myRequestConfig()); //httpPost.setConfig(myRequestConfig()); //.... return null; }
关于以上的两种定制,目前还未知区别是什么,感觉两种区别不大
常规配置
由于目前业务并没有多么复杂,所以只记录超时的常规配置
关于超时的3个对象,可以见“详细解读”的3个超时名词
//配置RequestConfig
public static RequestConfig myRequestConfig() {
RequestConfig myrequestConfig = RequestConfig.custom()
//连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
.setConnectTimeout(5000)
//服务器返回数据(response)的时间,超过该时间抛出read timeout
.setSocketTimeout(5000)
//从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出:
//ConnectionPoolTimeoutException: Timeout waiting for connection from pool
.setConnectionRequestTimeout(5000)
.build();
return myrequestConfig;
}
代理IP
代理IP的作用这里就不多做介绍了,以后会完成关于代理IP的记录。
官网其实提供了3种方式,但其中1种并不经常使用,所以只摘抄1种,并增加一种简单的使用方式
HttpClient关于Http的代理有3种方式,如下:
方式1
HttpHost httpHost = new HttpHost(host,port,"http");
DefaultProxyRoutePlanner defaultProxyRoutePlanner = new DefaultProxyRoutePlanner(httpHost);
CloseableHttpClient closeableHttpClient = HttpClients.custom()
.setRoutePlanner(defaultProxyRoutePlanner)
.build();
方式2
RequestConfig requestConfig = RequestConfig.custom()
.setProxy(httpHost)
.build();
HttpGet httpGet = new HttpGet();
//HttpPost httpPost = new HttpPost();
httpGet.setConfig(requestConfig);
//httpPost.setConfig(requestConfig);
//...
方式3
这种方式,官网如是说:
“或者,可以提供自定义的RoutePlanner实现,以完全控制HTTP路由计算过程”
HttpRoutePlanner routePlanner = new HttpRoutePlanner() {
public HttpRoute determineRoute(
HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
}
}
表单提交
一般网站会设置表单提交,以便登录网站或者提交数据
这个常用于爬虫,但是正常的登录都有多种因素的。
例如隐藏字段等等,HttpClient则需要对表单的各种字段进行挖掘
而关于表单提交方面,Jsoup就非常友好,可以在表单内容的基础上进行添加值即可。
由于时间关系,所以不展开描述了,下次有机会,例如实现登录的时候,再进行测试
另外,其实这些表单提交也可以使用链接拼接直接实现。
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
上传文件
先空着,以后遇到再进行描述
可以前往 HttpClient详细使用示例
安全证书
先空着,以后遇到再进行描述
可以前往 HttpClient详细使用示例
总结
HttpClient的学习非常有助于对Http协议的理解;另外,也可以从HttpClient中,看到一整套的处理逻辑。
在之后,我会对RestTemplate、Feign进行学习,并进行比较。