HttpClient实现跨域请求
HttpClient实现跨域请求,我们也可以理解为不同系统间的接口调用,那么什么为跨域呢?可以总结为不同域名,不同端口或者相同域名不同端口的系统。分布式项目是现在市场的主流,跨域请求在项目中各子系统中的联系也是必不可少。熟悉的跨域请求技术一个jsonp,一个HttpClient,jsonp底层是通过<script>调用回调函数并传递参数实现跨域,但他只能实现get请求,有时间整理出来再与大家分享。废话不多说,下面一起探讨HttpClient。
HttpClient其主要作用就是通过Http协议,向某个URL地址发起请求,并且获取响应结果,我们通过一个简单的案例就可以快速入门
1.1发起Get请求
public class DoGET { public static void main(String[] args) throws Exception { // 创建Httpclient对象,相当于打开了浏览器 CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建HttpGet请求,相当于在浏览器输入地址 HttpGet httpGet = new HttpGet("http://www.baidu.com/"); CloseableHttpResponse response = null; try { // 执行请求,相当于敲完地址后按下回车。获取响应 response = httpclient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应,获取数据 String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { // 关闭资源 response.close(); } // 关闭浏览器 httpclient.close(); } } }
1.2带参数的get请求
public class DoGETParam { public static void main(String[] args) throws Exception { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建URI对象,并且设置请求参数 URI uri = new URIBuilder("http://www.baidu.com/s").setParameter("wd", "java").build(); // 创建http GET请求 HttpGet httpGet = new HttpGet(uri); CloseableHttpResponse response = null; try { // 执行请求 response = httpclient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应数据 String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } httpclient.close(); } } }
1.3发起post请求
public class DoPOST { public static void main(String[] args) throws Exception { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建http POST请求 HttpPost httpPost = new HttpPost("http://www.oschina.net/"); // 把自己伪装成浏览器。否则开源中国会拦截访问 httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"); CloseableHttpResponse response = null; try { // 执行请求 response = httpclient.execute(httpPost); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应数据 String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } // 关闭浏览器 httpclient.close(); } } }
1.4带参数post请求
public class DoPOSTParam { public static void main(String[] args) throws Exception { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建http POST请求,访问开源中国 HttpPost httpPost = new HttpPost("http://www.oschina.net/search"); // 根据开源中国的请求需要,设置post请求参数 List<NameValuePair> parameters = new ArrayList<NameValuePair>(0); parameters.add(new BasicNameValuePair("scope", "project")); parameters.add(new BasicNameValuePair("q", "java")); parameters.add(new BasicNameValuePair("fromerr", "8bDnUWwC")); // 构造一个form表单式的实体 UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters); // 将请求实体设置到httpPost对象中 httpPost.setEntity(formEntity); CloseableHttpResponse response = null; try { // 执行请求 response = httpclient.execute(httpPost); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应体 String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } // 关闭浏览器 httpclient.close(); } } }
2.5使用连接池管理器
public class HttpConnectManager { public static void main(String[] args) throws Exception { // 构建连接池管理器对象 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // 设置最大连接数 cm.setMaxTotal(200); // 设置每个主机地址的并发数 cm.setDefaultMaxPerRoute(20); // 构建请求配置信息 RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间 .setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间 .setSocketTimeout(10 * 1000) // 数据传输的最长时间 .setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用 .build(); // 构建HttpClient对象,把连接池、请求配置对象作为参数 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm) .setDefaultRequestConfig(config).build(); doGet(httpClient); doGet(httpClient); } public static void doGet(CloseableHttpClient httpClient) throws Exception { // 创建http GET请求 HttpGet httpGet = new HttpGet("http://www.baidu.com/"); CloseableHttpResponse response = null; try { // 执行请求 response = httpClient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println("内容长度:" + content.length()); } } finally { if (response != null) { response.close(); } // 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁 // httpClient.close(); } } }
注意:
1) 在案例中的配置相当重要,特别是各种有关超时时间的配置。如果不配置1,可能就会导致坏连接一直阻塞,影响其它请 求。或者导致无法获取结果,产生意想不到的错误。
2) HttpClient对象不要关闭,否则会导致整个连接池失效。
实际开发中,HttpClient虽然有连接池来管理连接。但是,如果访问的服务端已经断开了连接,作为客户端的HttpClient是不知道的。此时,池中的这个连接就是一个无效的连接。如果这样的连接很多,就会造成有效连接数的减少,服务器的压力变大,直至崩溃。因此,定期清理无效的链接显得尤为重要。
定期清理无效链接
public class ClientEvictExpiredConnections { public static void main(String[] args) throws Exception { PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // 设置最大连接数 cm.setMaxTotal(200); // 设置每个主机地址的并发数 cm.setDefaultMaxPerRoute(20); // 开启线程,执行清理链接的任务 new IdleConnectionEvictor(cm).start(); } // 创建内部类,继承Thread,成为线程类 public static class IdleConnectionEvictor extends Thread { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionEvictor(HttpClientConnectionManager connMgr) { this.connMgr = connMgr; } // 定义线程任务,定时关闭失效的连接 @Override public void run() { try { while (!shutdown) { synchronized (this) { // 每隔5秒钟一次 wait(5000); // 关闭失效的连接 connMgr.closeExpiredConnections(); } } } catch (InterruptedException ex) { // 结束 } } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } } }
SSM中整合HttpClient
要在项目中整合HttpClient,其实就是把HttpClient注册到Spring容器中。
而一般注册一个Bean,也有两种方式:XML配置或者注解。
我们选哪种?
方式1,XML配置
l 新建applicationContext-httpClient.xml文件:
l 编写配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> <!-- 配置连接池管理器 --> <bean id="httpClientConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager"> <!-- 配置最大连接数 --> <property name="maxTotal" value="${http.maxTotal}" /> <!-- 配置每个主机地址的最大连接数 --> <property name="defaultMaxPerRoute" value="${http.defaultMaxPerRoute}" /> </bean> <!-- 配置RequestConfigBuilder --> <bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder"> <!-- 创建连接的最长时间 --> <property name="connectTimeout" value="${http.connectTimeout}" /> <!-- 从连接池中获取到连接的最长时间 --> <property name="connectionRequestTimeout" value="${http.connectionRequestTimeout}" /> <!-- 数据传输的最长时间 --> <property name="socketTimeout" value="${http.socketTimeout}" /> <!-- 提交请求前测试连接是否可用 --> <property name="staleConnectionCheckEnabled" value="${http.staleConnectionCheckEnabled}" /> </bean> <!-- 配置RequestConfig --> <bean id="requestConfig" factory-bean="requestConfigBuilder" factory-method="build"/> <!-- 配置HttpClientBuilder --> <bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder"> <!-- 注入连接池管理器 --> <property name="connectionManager" ref="httpClientConnectionManager" /> <!-- 注入默认的请求配置对象 --> <property name="defaultRequestConfig" ref="requestConfig" /> </bean> <!-- 配置HttpClient --> <bean factory-bean="httpClientBuilder" factory-method="build"></bean> </beans>
注意,在上面的配置中,引用了${},外部属性文件。所以,在Spring的核心配置中添加配置文件:appliationContext.xml中
l httpClient.properties :资源文件放在项目的根目录下 resource
l 定义一个HttpClient的通用Service:提供get请求和POST请求的方法
@Service public class XmlApiService { // 注入在XML中已经配置好的HttpClient对象 @Autowired private CloseableHttpClient httpClient; /** * 无参的get请求 */ public String doGet(String uri) throws IOException { // 创建HttpGet请求,相当于在浏览器输入地址 HttpGet httpGet = new HttpGet(uri); CloseableHttpResponse response = null; try { // 执行请求,相当于敲完地址后按下回车。获取响应 response = httpClient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应,获取数据 return EntityUtils.toString(response.getEntity(), "UTF-8"); } } finally { if (response != null) { response.close(); } } return null; } /** * 有参get请求 */ public String doGet(String uri, Map<String, String> params) throws Exception { // 创建地址构建器 URIBuilder builder = new URIBuilder(uri); // 拼接参数 for (Map.Entry<String, String> me : params.entrySet()) { builder.addParameter(me.getKey(), me.getValue()); } return doGet(builder.build().toString()); } /** * 有参POST请求 */ public String doPost(String uri, Map<String, String> params) throws ParseException, IOException { // 创建http POST请求 HttpPost httpPost = new HttpPost(uri); httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"); if (params != null) { // 设置参数 List<NameValuePair> parameters = new ArrayList<NameValuePair>(0); for (Map.Entry<String, String> me : params.entrySet()) { parameters.add(new BasicNameValuePair(me.getKey(), me.getValue())); } // 构造一个form表单式的实体 UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters); // 将请求实体设置到httpPost对象中 httpPost.setEntity(formEntity); } CloseableHttpResponse response = null; try { // 执行请求 response = httpClient.execute(httpPost); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } } return null; } /** * 无参POST请求 */ public String doPost(String uri) throws ParseException, IOException { return doPost(uri, null); } } l 先把清理无效连接的线程复制到项目中: 注意修改构造函数,在构造函数中启动当前线程任务,这样当Bean一旦注册,线程就启动了 /** * 定时清理无效链接的线程 */ public class IdleConnectionEvictor extends Thread { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionEvictor(HttpClientConnectionManager connMgr) { this.connMgr = connMgr; // 启动线程 this.start(); } // 定义线程任务,定时关闭失效的连接 @Override public void run() { try { while (!shutdown) { synchronized (this) { // 每隔5秒钟一次 wait(5000); // 关闭失效的连接 connMgr.closeExpiredConnections(); } } } catch (InterruptedException ex) { // 结束 } System.out.println("清理任务 即将结束。。"); } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } }
l 在applicationContext-httpclient.xml配置文件中注册该类:
<bean class="com.taotao.common.bean.IdleConnectionEvictor" destroy-method="shutdown"> <constructor-arg index="0" ref="httpClientConnectionManager"></constructor-arg> </bean> l 完整的applicationContext-httpclient.xml: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> <!-- 设置请求参数的配置 --> <bean id="configBuilder" class="org.apache.http.client.config.RequestConfig.Builder"> <!-- 创建连接的最长时间 --> <property name="connectTimeout" value="${httpclient.connectTimeout}"></property> <!-- 从连接池中获取到连接的最长时间 --> <property name="connectionRequestTimeout" value="${httpclient.connectionRequestTimeout}"></property> <!-- 数据传输的最长时间 --> <property name="socketTimeout" value="${httpclient.socketTimeout}"></property> <!-- 提交请求前测试连接是否可用 --> <property name="staleConnectionCheckEnabled" value="${httpclient.staleConnectionCheckEnabled}"></property> </bean> <!-- 配置RequestConfig --> <bean id="requestConfig" factory-bean="configBuilder" factory-method="build"></bean> <!-- httpclient连接管理器 --> <bean id="httpClientConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager"> <!-- 最大连接数 --> <property name="maxTotal" value="${httpclient.maxTotal}"></property> <!-- 设置每个主机地址的并发数 --> <property name="defaultMaxPerRoute" value="${httpclient.defaultMaxPerRoute}"></property> </bean> <!-- httpclient的构造器 --> <bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder"> <property name="connectionManager" ref="httpClientConnectionManager"></property> <!-- 注入默认的请求配置对象 --> <property name="defaultRequestConfig" ref="requestConfig" /> </bean> <!-- 配置httpclient对象 scope="prototype" : 默认是单例,我们改成prototype,每次重新创建一个实例--> <bean class="org.apache.http.impl.client.CloseableHttpClient" factory-bean="httpClientBuilder" factory-method="build" > </bean> <!-- 自定义定期关闭无效HttpClient连接的类 --> <bean class="com.taotao.common.bean.IdleConnectionEvictor" destroy-method="shutdown"> <constructor-arg index="0" ref="httpClientConnectionManager"></constructor-arg> </bean> </beans>
l 测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring/applicationContext*.xml") public class ApiServiceTest { @Autowired private XmlApiService apiService; @Test public void testDoGetString() throws IOException { String content = this.apiService.doGet("http://www.baidu.com"); System.out.println(content); } @Test public void testDoGetStringMapOfStringString() throws Exception { Map<String,String> params = new HashMap<>(); params.put("categoryId", "13"); params.put("rows", "6"); params.put("callback", "func"); String content = this.apiService.doGet("http://manage.taotao.com/rest/api/content",params); System.out.println(content); } @Test public void testDoPostStringMapOfStringString() throws ParseException, IOException { Map<String,String> params = new HashMap<>(); params.put("scope", "project"); params.put("q", "java"); params.put("fromerr", "8bDnUWwC"); String content = this.apiService.doPost("http://www.oschina.net/search", params); System.out.println(content); } @Test public void testDoPostString() { fail("Not yet implemented"); } }
一切OK,整合成功
方式2:注解方式
直接在APIService中,通过@value注解来注入 配置参数。
然后通过@PostConstructor 注解来声明一个初始化方法,在这个方法中完成HttpClient对象的创建。
/** * 通过HttpClient进行远程调用的Service */ @Service public class ApiService { @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; private CloseableHttpClient httpClient; // PostConstruct注解标明该方法为初始化方法,会在对象构造函数执行完毕后执行 @PostConstruct public void init() { // 构建连接池管理器对象 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // 设置最大连接数 cm.setMaxTotal(maxTotal); // 设置每个主机地址的并发数 cm.setDefaultMaxPerRoute(defaultMaxPerRoute); // 构建请求配置信息 RequestConfig config = RequestConfig.custom().setConnectTimeout(connectTimeout) // 创建连接的最长时间 .setConnectionRequestTimeout(connectionRequestTimeout) // 从连接池中获取到连接的最长时间 .setSocketTimeout(socketTimeout) // 数据传输的最长时间 .setStaleConnectionCheckEnabled(staleConnectionCheckEnabled) // 提交请求前测试连接是否可用 .build(); // 构建HttpClient对象,把连接池、请求配置对象作为参数 httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(config).build(); } /** * 无参的get请求 */ public String doGet(String uri) throws IOException { // 创建HttpGet请求,相当于在浏览器输入地址 HttpGet httpGet = new HttpGet(uri); CloseableHttpResponse response = null; try { // 执行请求,相当于敲完地址后按下回车。获取响应 response = httpClient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { // 解析响应,获取数据 return EntityUtils.toString(response.getEntity(), "UTF-8"); } } finally { if (response != null) { response.close(); } } return null; } /** * 有参get请求 */ public String doGet(String uri, Map<String, String> params) throws Exception { // 创建地址构建器 URIBuilder builder = new URIBuilder(uri); // 拼接参数 for (Map.Entry<String, String> me : params.entrySet()) { builder.addParameter(me.getKey(), me.getValue()); } return doGet(builder.build().toString()); } /** * 有参POST请求 */ public String doPost(String uri, Map<String, String> params) throws ParseException, IOException { // 创建http POST请求 HttpPost httpPost = new HttpPost(uri); httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"); if (params != null) { // 设置参数 List<NameValuePair> parameters = new ArrayList<NameValuePair>(0); for (Map.Entry<String, String> me : params.entrySet()) { parameters.add(new BasicNameValuePair(me.getKey(), me.getValue())); } // 构造一个form表单式的实体 UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters); // 将请求实体设置到httpPost对象中 httpPost.setEntity(formEntity); } CloseableHttpResponse response = null; try { // 执行请求 response = httpClient.execute(httpPost); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } } return null; } /** * 无参POST请求 */ public String doPost(String uri) throws ParseException, IOException { return doPost(uri, null); } } 添加定时清理无效连接的任务 我们这里按照方式2,也就是注解注入的方式来添加定时清理无效链接的任务。 l 首先,在ApiService类中,定义内部类,实现 Runnable接口,成为任务类,编写定时清理的线程任务 // 创建内部类,实现Runnable接口,成为线程任务类 private class IdleConnectionEvictor implements Runnable { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionEvictor(HttpClientConnectionManager connMgr) { this.connMgr = connMgr; } // 定义线程任务,定时关闭失效的连接 @Override public void run() { try { while (!shutdown) { synchronized (this) { // 每隔5秒钟一次 wait(5000); // 关闭失效的连接 connMgr.closeExpiredConnections(); } } } catch (InterruptedException ex) { // 结束 } } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); System.out.println("定时清理任务即将结束。。"); } } }
l 在初始化方法,init中,HttpClient对象创建完毕后,创建任务,并开启线程执行
l 定义一个destroy方法,当ApiService被销毁前,结束线程任务
————————————————
版权声明:本文为CSDN博主「Mrherny」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Mrherny/article/details/80392012