前后台分离时,如何实现前台系统调用后台系统?
上图有两种调用方式,下面进行一一解释:
1、 通过js调用(图上蓝色线)
a) 优点
i. 直接调用,没有java代码中转,调用的路径更短
ii. 节省服务器的带宽
b) 缺点
i. 调用接口时无法添加自己的业务逻辑(如:缓存)
ii. 接口不安全
1. 有些接口只能在内网公开
iii. Jsonp需要接口的提供方提供支持(存在局限性)
2、 通过后台java调用(图上黄色线)
a) 优点
i. 安全
ii. 可以添加自己的业务逻辑
iii. 对接口的提供方没有要求,没有局限性
b) 缺点
i. 间接调用接口,使得调用路径变长
1. 不是真正的缺点,添加自己业务逻辑
ii. 占用内网的带宽
1. 发布上线时需要考虑服务器机房的网络带宽
总结:在实际企业开发中,以上2种方式都会使用,往往都是这2种方式结合使用。
如何在java中发起http请求? – HttpClient
创建一个maven项目,在该项目中配置httpclient的标签
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
</dependency>
学习如下几个文件:
回想我们使用浏览器的步骤:
1.打开浏览器
2.输入要访问的网址,例如:百度
3.按下回车键,执行我们的请求
4.如果成功就显示页面,该页面是浏览器对html渲染后的效果;如果不成功也会返回给我们一个状态码。
5.使用完成后关闭浏览器
我们在使用httpclient的时候与上述浏览器的使用步骤是一致的。下面贴出详细代码:
DoGET.java
package cn.itcast.httpclient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class DoGET {
public static void main(String[] args) throws Exception {
// 创建Httpclient对象
// 打开浏览器
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http GET请求
// 输入网址
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
CloseableHttpResponse response = null;
try {
// 执行请求
// 按下回车键
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
// 浏览器度html进行渲染
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("输出内容:"+content);
}
} finally {
// 关闭浏览器
if (response != null) {
response.close();
}
httpclient.close();
}
}
}
DoGETParam.java
package cn.itcast.httpclient;
import java.net.URI;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class DoGETParam {
public static void main(String[] args) throws Exception {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 定义请求的参数
URI uri = new URIBuilder("http://www.baidu.com/s").setParameter("wd", "java").build();
System.out.println(uri);
// 创建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();
}
}
}
DoPOST.java
(首先测试百度是否可以走post请求)
发现报错,说明百度不支持post请求。
换一个连接地址:开源中国http://www.oschina.net/
请求成功,说明支持post请求。
编写代码,用程序对该网站做请求:
package cn.itcast.httpclient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
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/");
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();
}
}
}
执行该代码发现报403错误,但是刚测试可以进行post请求,这是什么原因呢?是开源做了一个限制,不允许程序访问网站,是为了防止爬虫。解决办法是伪装成浏览器。如何伪装呢?我们发现通过浏览器请求都会加如下信息:
我们在程序中加上上述语句即可。
package cn.itcast.httpclient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
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 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 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();
}
}
}
DoPOSTParam.java
package cn.itcast.httpclient;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
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");
// 设置2个post参数,一个是scope、一个是q
List<NameValuePair> parameters = new ArrayList<NameValuePair>(0);
parameters.add(new BasicNameValuePair("scope", "project"));
parameters.add(new BasicNameValuePair("q", "java"));
parameters.add(new BasicNameValuePair("fromerr", "RgI0EeI0"));
// 构造一个form表单式的实体
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
// 将请求实体设置到httpPost对象中
httpPost.setEntity(formEntity);
httpPost.setHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 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();
}
}
}
上面浏览器中参数的设置依据,都是先用浏览器进行访问,然后按照浏览器的访问格式编写代码用程序去进行访问。
以上代码都是先打开浏览器然后访问最后做关闭。每次都创建然后销毁,性能比较差。和数据库的连接有点相像,数据库用的是连接池。这里我们使用连接管理器,来创建浏览器。
package cn.itcast.httpclient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
public class HttpConnectManager {
public static void main(String[] args) throws Exception {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 设置最大连接数
cm.setMaxTotal(200);
// 设置每个主机地址的并发数
cm.setDefaultMaxPerRoute(20);
doGet(cm);
doGet(cm);
}
public static void doGet(HttpClientConnectionManager cm) throws Exception {
// 打开浏览器
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// 创建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();
}
}
}
上面的连接管理器打开了一个浏览器,可以在该浏览器中执行多个uri,相当于打开一个浏览器里面有多个tab,如下图:
如果有些tab页访问过后不再需要,也没有进行关闭的话,就会造成打开的tab越来越多。不可用的tab就会占用资源,就需要我们定期清理无效连接,需要通过一个线程将无效的连接关闭:
ClientEvictExpiredConnections.java
package cn.itcast.httpclient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
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();
}
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) {
wait(5000);
// 关闭失效的连接
connMgr.closeExpiredConnections();
}
}
} catch (InterruptedException ex) {
// 结束
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
}
当我们发起一个http请求的时候都要设置一定的参数,创建连接的最长时间,传输的最长时间等。此处的参数如果不设置会出问题的,比如未设置连接创建时间,就意味着一直连接下去,比如连到一个网站该网站未响应那么我们的程序就会一直去连接,那该程序就一直卡在这里无法继续运行。所以最好还是手动设置一下,可以提高体验性能:
RequestConfigDemo.java
package cn.itcast.httpclient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class RequestConfigDemo {
public static void main(String[] args) throws Exception {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建http GET请求
HttpGet httpGet = new HttpGet("http://www.baidu.com/");
// 构建请求配置信息
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间
.setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间
.setSocketTimeout(10 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
// 设置请求配置信息
httpGet.setConfig(config);
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();
}
}
}
上面分析了Httpclient中各个文件的作用,下面就把它集成到项目中去使用。我们在程序中肯定使用连接管理器来实现。使用连接管理器,用到的连接池一般都是由spring来管理,下面就需要spring与Httpclient做整合。