关于HttpClient的一些开发和学习笔记

1 篇文章 0 订阅

Java_HttpClient

前言

以前有接触过HttpClient,只是用用就没有多想。现在因为看到一个爬虫项目使用了HttpClient,才发现原来

Java的HttpClient和Python的request库一样,功能强大。并且因为有一个设置代理IP的需求,所以对HttpClient进行深入学习,同时进行记录。
关于使用场景,目前我觉得是解析API。除此之外,今后的学习再进行补充。

而且HttpClient的源头Http是目前使用者最多,运用广泛的协议,所以非常值得学习。
也非常感谢一些优质博客的博主对于HttpClient的见解,对学习很有帮助。

时间线

20200808-完成初稿

参考链接

API解读

几句常见说法

  1. HttpClient不是一个浏览器。这是一个客户端HTTP传输库。

    由于使用电脑的生活中,使用浏览器的频率非常高,所以一开始接触到HttpClient时,会将HttpClient当成是一个代码浏览器。可以这么说,但也不可以这么说。

    可以这么说,是因为浏览器发送请求,服务端收到请求后返回数据。这些浏览器和HttpClient都能做到。

    不可以这么说,是因为浏览器还有一个渲染引擎渲染数据,并解释用户的输入,例如点击哪里,输入什么;

    而HttpClient是没有用户界面的,只能靠编码方式解释数据。

    但是HttpClient不是一个浏览器,不能拿浏览器所能实现的功能与HttpClient相比较

  2. 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. 大体步骤

    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、释放连接。

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

  2. 总结

参数请求

关于请求,常见的有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进行学习,并进行比较。

http工具类:package com.tpl.util; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; /** * */ public class HttpClientUtil { public static void main(String arg[]) throws Exception { String url = "http://xxx/project/getxxx.action"; JSONObject params= new JSONObject(); List res=new ArrayList(); JSONObject params1 = new JSONObject(); // params1.put("code", "200"); // params1.put("phone", "13240186028"); res.add(params1); params.put("result", res); String ret = doPost(url, params).toString(); System.out.println(ret); } /** httpClient的get请求方式2 * @return * @throws Exception */ public static String doGet(String url, String charset) throws Exception { /* * 使用 GetMethod 来访问一个 URL 对应的网页,实现步骤: 1:生成一个 HttpClinet 对象并设置相应的参数。 * 2:生成一个 GetMethod 对象并设置响应的参数。 3:用 HttpClinet 生成的对象来执行 GetMethod 生成的Get * 方法。 4:处理响应状态码。 5:若响应正常,处理 HTTP 响应内容。 6:释放连接。 */ /* 1 生成 HttpClinet 对象并设置参数 */ HttpClient httpClient = new HttpClient(); // 设置 Http 连接超时为5秒 httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000); /* 2 生成 GetMethod 对象并设置参数 */ GetMethod getMethod = new GetMethod(url); // 设置 get 请求超时为 5 秒
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值