HttpClient介绍及SpringBoot整合

1.HttpClient介绍

HTTP 协议是现在 Internet 上使用得最多、最重要的协议,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。

2.功能介绍

以下列出的是 HttpClient 提供的主要的功能,更多详细的功能可以参见 HttpClient 的主页。
(1)实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2)支持自动转向
(3)支持 HTTPS 协议
(4)支持代理服务器等

3.导入jar包

<!--添加httpClient依赖 -->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
		</dependency>

4.常见问题

4.1.字符编码

某目标页的编码可能出现在两个地方,第一个地方是服务器返回的http头中,另外一个地方是得到的html/xml页面中。
在http头的Content-Type字段可能会包含字符编码信息。例如可能返回的头会包含这样子的信息:Content-Type: text/html; charset=UTF-8。这个头信息表明该页的编码是UTF-8,但是服务器返回的头信息未必与内容能匹配上。比如对于一些双字节语言国家,可能服务器返回的编码类型是UTF-8,但真正的内容却不是UTF-8编码的,因此需要在另外的地方去得到页面的编码信息;但是如果服务器返回的编码不是UTF-8,而是具体的一些编码,比如gb2312等,那服务器返回的可能是正确的编码信息。通过method对象的getResponseCharSet()方法就可以得到http头中的编码信息。
对于象xml或者html这样的文件,允许作者在页面中直接指定编码类型。比如在html中会有这样的标签;或者在xml中会有<?xml version="1.0" encoding="gb2312"?>这样的标签,在这些情况下,可能与http头中返回的编码信息冲突,需要用户自己判断到底那种编码类型应该是真正的编码。

4.2.自动转向

根据RFC2616中对自动转向的定义,主要有两种:301和302。301表示永久的移走(Moved Permanently),当返回的是301,则表示请求的资源已经被移到一个固定的新地方,任何向该地址发起请求都会被转到新的地址上。302表示暂时的转向,比如在服务器端的servlet程序调用了sendRedirect方法,则在客户端就会得到一个302的代码,这时服务器返回的头信息中location的值就是sendRedirect转向的目标地址。
HttpClient支持自动转向处理,但是象POST和PUT方式这种要求接受后继服务的请求方式,暂时不支持自动转向,因此如果碰到POST方式提交后返回的是301或者302的话需要自己处理。就像刚才在POSTMethod中举的例子:如果想进入登录BBS后的页面,必须重新发起登录的请求,请求的地址可以在头字段location中得到。不过需要注意的是,有时候location返回的可能是相对路径,因此需要对location返回的值做一些处理才可以发起向新地址的请求。
另外除了在头中包含的信息可能使页面发生重定向外,在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是:。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是,在上面那个标签中url的值也可以是一个相对地址,如果是这样的话,需要对它做一些处理后才可以转发。

5.SpringBoot整合HttpClient

5.1 配置文件httpclient.properties

#最大连接数
http.maxTotal = 1000
#并发数
http.defaultMaxPerRoute = 20
#创建连接的最长时间
http.connectTimeout=5000
#从连接池中获取到连接的最长时间
http.connectionRequestTimeout=500
#数据传输的最长时间
http.socketTimeout=5000
#提交请求前测试连接是否可用
http.staleConnectionCheckEnabled=true

5.2 配置类

@Configuration	//注解--配置类
@PropertySource(value="classpath:/properties/httpClient.properties")
public class HttpClientConfig {
	@Value("${http.maxTotal}")
	private Integer maxTotal;						//最大连接数
	@Value("${http.defaultMaxPerRoute}")
	private Integer defaultMaxPerRoute;				//最大并发链接数
	@Value("${http.connectTimeout}")
	private Integer connectTimeout;					//创建链接的最大时间
	@Value("${http.connectionRequestTimeout}") 
	private Integer connectionRequestTimeout;		//链接获取超时时间
	@Value("${http.socketTimeout}")
	private Integer socketTimeout;			  		//数据传输最长时间
	@Value("${http.staleConnectionCheckEnabled}")
	private boolean staleConnectionCheckEnabled; 	//提交时检查链接是否可用
	//定义httpClient链接池
	@Bean(name="httpClientConnectionManager")
	public PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
		PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
		manager.setMaxTotal(maxTotal);  //设定最大链接数
		manager.setDefaultMaxPerRoute(defaultMaxPerRoute);  //设定并发链接数
		return manager;
	}
	//定义HttpClient
	/**
	 * 实例化连接池,设置连接池管理器。
	 * 这里需要以参数形式注入上面实例化的连接池管理器
      @Qualifier 指定bean标签进行注入
	 */
	@Bean(name = "httpClientBuilder")
	public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){
		//HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		httpClientBuilder.setConnectionManager(httpClientConnectionManager);
		return httpClientBuilder;
	}

	/**
	 * 	注入连接池,用于获取httpClient
	 * 1.定义池对象
	 * 2.创建builder对象 关联池
	 * 3.利用builder实例化对象
	 */
	@Bean
	public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){
		return httpClientBuilder.build();
	}
	/**
	 * Builder是RequestConfig的一个内部类
	  * 通过RequestConfig的custom方法来获取到一个Builder对象
	  * 设置builder的连接信息
	 */
	@Bean(name = "builder")
	public RequestConfig.Builder getBuilder(){
		RequestConfig.Builder builder = RequestConfig.custom();
		return builder.setConnectTimeout(connectTimeout)
				.setConnectionRequestTimeout(connectionRequestTimeout)
				.setSocketTimeout(socketTimeout)
				.setStaleConnectionCheckEnabled(staleConnectionCheckEnabled);
	}
	/**
	 * 使用builder构建一个RequestConfig对象
	 * @param builder
	 * @return
	 */
	@Bean
	public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
		return builder.build();
	}
}

5.3 关闭超时连接

@Component	//交给spring容器管理
public class HttpClientClose extends Thread{
	@Autowired
	private PoolingHttpClientConnectionManager manage;
	//开关 volatitle表示多线程可变数据,一个线程修改,其他线程立即修改
	private volatile boolean shutdown;	
	public HttpClientClose() {
		//System.out.println("执行构造方法,实例化对象");
		//线程开启启动
		this.start();
	}
	//以异步的形式关闭httpClient对象
	@Override
	public void run() {
		try {
			//如果服务没有关闭,执行线程
			while(!shutdown) {
				synchronized (this) {
					wait(5000);			//等待5秒
					//System.out.println("线程开始执行,关闭超时链接");
					//关闭超时的链接
					PoolStats stats = manage.getTotalStats();
					int av = stats.getAvailable();	//获取可用的线程数量
					int pend = stats.getPending();	//获取阻塞的线程数量
					int lea = stats.getLeased();    //获取当前正在使用的链接数量
					int max = stats.getMax();
					manage.closeExpiredConnections();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException();
		}
		super.run();
	}
	//关闭清理无效连接的线程
	@PreDestroy	//容器关闭时执行该方法.
	public void shutdown() {
		shutdown = true;
		synchronized (this) {
			//System.out.println("关闭全部链接!!");
			notifyAll(); //全部从等待中唤醒.执行关闭操作;
		}
	}
}

6.实现httpClient工具API

6.1编辑HttpClientService

@Service	//将对象交给Spring容器管理
public class HttpClientService {
	@Autowired
	private CloseableHttpClient httpClient;
	@Autowired
	private RequestConfig requestConfig;
	/**
	 *  需求:给定url地址,则可以自动的发起请求,并且将返回值结果返回给用户.	
	 * 参数分析:
	 * 	1.用户的url参数地址
	 * 	2.判断用户是否需要携带参数   使用Map集合封装数据.
	 * 	3.指定字符集编码  需要用户自己指定
	 * 返回值: String类型
	 */
	public String doGet(String url,Map<String,String> params,String charset) {
		//1.判断用户是否传递字符集编码
		if(StringUtils.isEmpty(charset)) {
			//设定默认值
			charset = "UTF-8";
		}
		
		/**
		 * 2.判断用户是否携带参数
		 * 	 没有带参数: http://manage.jt.com/item
		 * 	 带参数: http://manage.jt.com/item?id=1&name=xxxx&;	
		 * 	补充知识: entry={key=value}   Map~~~>entry~~~~~key:value
		 */
		if(params !=null) {//如果参数不为null,则需要进行参数拼接.
			url += "?";
			//map集合遍历. 从中动态的获取key=value.
			for (Map.Entry<String,String> entry : params.entrySet()) {
				String key = entry.getKey().trim();	//去除多余的空格
				String value = entry.getValue().trim();
				url += key+"="+value+"&";
			}
			//去除多余的&符
			url =url.substring(0, url.length()-1);
		}
		//3.定义请求对象
		HttpGet httpGet = new HttpGet(url);
		//添加超时配置
		httpGet.setConfig(requestConfig);
		//4.发起请求
		try {
			HttpResponse httpResponse = httpClient.execute(httpGet);
			//5.判断用户的请求是否正常
			if(httpResponse.getStatusLine().getStatusCode()==200) {
				//6.动态获取返回值
				return EntityUtils.toString(httpResponse.getEntity());
			}
			int status = httpResponse.getStatusLine().getStatusCode();
			throw new RuntimeException("获取后台错误的状态码:"+status+"|请求路径为:url"+url);
		} catch (IOException e) {
			e.printStackTrace();
			//将检查异常,转化为运行时异常
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 重载多个doGet方法 满足不同的用户需要
	 * @param url
	 * @param params
	 * @param charset
	 * @return
	 */
	public String doGet(String url) {
		return doGet(url, null, null);
	}
	public String doGet(String url,Map<String,String> params) {
		return doGet(url, params, null);
	}
	//实现httpClient POST提交
	public String doPost(String url,Map<String,String> params,String charset){
		String result = null;
		//1.定义请求类型
		HttpPost post = new HttpPost(url);
		post.setConfig(requestConfig);  	//定义超时时间
		//2.判断字符集是否为null
		if(StringUtils.isEmpty(charset)){
			charset = "UTF-8";
		}
		//3.判断用户是否传递参数
		if(params !=null){
			//3.2准备List集合信息
			List<NameValuePair> parameters = new ArrayList<>();
			//3.3将数据封装到List集合中
			for (Map.Entry<String,String> entry : params.entrySet()) {
				parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
			//3.1模拟表单提交
			try {
				UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters,charset); //采用u8编码
				//3.4将实体对象封装到请求对象中
				post.setEntity(formEntity);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		//4.发送请求
		try {
			CloseableHttpResponse response = httpClient.execute(post);
			//4.1判断返回值状态
			if(response.getStatusLine().getStatusCode() == 200) {
				//4.2表示请求成功
				result = EntityUtils.toString(response.getEntity(),charset);
			}else{
				System.out.println("获取状态码信息:"+response.getStatusLine().getStatusCode());
				throw new RuntimeException();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}

	public String doPost(String url){
		return doPost(url, null, null);
	}
	public String doPost(String url,Map<String,String> params){
		return doPost(url, params, null);
	}
	public String doPost(String url,String charset){
		return doPost(url, null, charset);
	}
}

6.2编辑测试类

@Autowired
	private HttpClientService httpClient;
	
	@Test
	public void testGet() {
		
		String url = "https://www.baidu.com";
		String result = httpClient.doGet(url);
		System.out.println(result);
	}
	```
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值