Ajax跨域问题总结目录
1. 跨域问题描述
1.1 跨域的含义?
- 跨域是指从一个域名的网页去请求另一个域名的资源。比如从百度(https://baidu.com)页面去请求京东(https://www.jd.com)的资源。
1.2 哪些访问存在跨域问题
- 通过超链接或者form表单提交或者window.location.href的方式进行跨域是不存在问题的。但在一个域名的网页中的一段js代码发送ajax请求去访问另一个域名中的资源,由于同源策略的存在导致无法跨域访问,那么ajax就存在这种跨域问题。
2. 同源问题
- 同源策略是指一段脚本只能读取来自同一来源的窗口和文档的属性,同源就是协议、域名和端口都相同。
2.1 同源策略作用
- 如果你刚刚在网银输入账号密码,查看了自己还有账户余额,紧接着访问一些不规矩的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。所以,从安全的角度来讲,同源策略是有利于保护网站信息的。
2.2 同源判断
区分同源和不同源的三要素
- 协议
- 域名
- 端口号
协议一致,域名一致,端口号一致,三个要素都一致,才是同源,其它一律都是不同源
URL1 | URL2 | 是否同源 | 描述 |
---|---|---|---|
http://localhost:8080/a/index.html | http://localhost:8080/a/first | 同源 | 协议 域名 端口一致 |
http://localhost:8080/a/index.html | http://localhost:8080/b/first | 同源 | 协议 域名 端口一致 |
http://www.myweb.com:8080/a.js | https://www.myweb.com:8080/b.js | 不同源 | 协议不同 |
http://www.myweb.com:8080/a.js | http://www.myweb.com:8081/b.js | 不同源 | 端口不同 |
http://www.myweb.com/a.js | http://www.myweb2.com/b.js | 不同源 | 域名不同 |
http://www.myweb.com/a.js | http://crm.myweb.com/b.js | 不同源 | 子域名不同 |
3. 解决跨域问题
说明:
- 在模拟两个服务器时,一共创建了两个模块a和b,并且配置了两个Tomcat,一个运行模块a(主要是模拟前端代码请求),一个运行模块b(主要是模拟后端服务器做出的响应)
3.1 方案一:设置响应头
- 核心原理:跨域访问的资源允许你跨域访问。
- 实现代码:
//设置响应头,允许Ajax跨域请求
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
模拟实现核心代码:
// 使用ES6新特性:箭头函数
window.onload = () => {
document.getElementById("btn").onclick = () => {
let xmlHttpRequest = new XMLHttpRequest();
// 2. 注册回调函数
xmlHttpRequest.onreadystatechange = () => {
if (xmlHttpRequest.readyState == 4) {
if (xmlHttpRequest.status >= 200 && xmlHttpRequest.status < 300) {
document.getElementById("mydiv").innerHTML = xmlHttpRequest.responseText
}
}
}
// 3. 开启通道
xmlHttpRequest.open("GET", "http://localhost:8081/b/hello", true)
// 4. 发送请求
xmlHttpRequest.send()
}
}
同源
- 响应给前端数据
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置响应头,允许Ajax跨域请求
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
// response.getWriter().print("hello,ajax");
response.getWriter().print("{\"username\":\"zhangsan\"}");
}
}
3.2 方案二:使用jsonp
jsonp简介:
- jsonp:json with padding(带填充的json)
- jsonp不是一个真正的ajax请求。只不过可以完成ajax的局部刷新效果。可以说jsonp是一种类ajax请求的机制。
- jsonp不是ajax请求,但是可以完成局部刷新的效果,并且可以解决跨域问题。
- 注意:jsonp解决跨域的时候,只支持GET请求。不支持post请求。
代码实现:
- 发送请求
<script type="text/javascript">
function sayHello(data){
alert("hello"+data.name)
}
</script>
<!--使用jsonp实现跨域请求:src:向 b web应用的jsonp程序发送请求-->
<script type="text/javascript" src="http://localhost:8081/b/jsonp1?fun=sayHello">
/*从bWEB服务器会返回一个js代码*/
</script>
- 响应数据
String fun = request.getParameter("fun");
response.getWriter().print(fun+"({\"name\":\"json\"})");
3.3 方案三:jQuery封装的jsonp
使用jQuery封装的jsonp,在发送请求时,URL会被默认添加上callback=jQuery36003529724253140707_1660986216632&_=1660986216637,【创建一个callback函数】这就导致在后端响应数据时,需要把json格式的数据响应给callback函数,之后函数会再去调用ajax请求中的success属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery的jsonp封装解决Ajax的跨域问题</title>
</head>
<body>
<script type="text/javascript" src="/a/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function () {
$("#btn").click(function () {
//默认发送地址
//http://localhost:8081/b/jsonp3?callback=jQuery36003529724253140707_1660986216632&_=1660986216637
//jQuery会默认生成一个callback函数,然后执行success中的内容
$.ajax({
type: "GET",//jsonp仅仅支持get
url: "http://localhost:8081/b/jsonp3",
dataType: "jsonp",//指定数据类型是jsonp形式
//另外自己也可以指定函数名和函数,不适用默认的callback
/*jsonp: "fun",
jsonpCallback:"sayHello",*/
success: function (data) {
$("#mydiv").html("欢迎,"+data.username)
}
})
})
})
</script>
<button id="btn">jQuery库封装的jsonp</button>
<div id="mydiv"></div>
</body>
</html>
- 后端响应
@WebServlet("/jsonp3")
public class JSONP3Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String callback = request.getParameter("callback");
response.getWriter().print(callback+"({\"username\":\"zhangsan\"})");
}
}
3.4 方案四:使用代理机制
理解代理机制:
- 描述:
- 按钮想要向Target Servlet发送一个请求 ,但是问题是:他们两个不在一个服务器,属于不同的站点;
- 需要找一个中间代理人,ProxyServlet向TargetServlet发送请求;
- 之后,TargetServlet将数据响应给代理人ProxyServlet,然后最终由ProxyServlet将收到的数据响应给前端页面;
- 实际上,ProxyServlet所做的工作就相当于我们在浏览器上输入地址栏,然后跳转到相对应页面的工作性质是一样的,因为本身响应的数据就是---->所需资源页面的html代码
代码实现:
- 前端页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>实现代理解决跨域问题</title>
</head>
<body>
<!--需要向代理服务器发送请求,代理向目标发送请求,然后一次响应-->
<script type="text/javascript">
window.onload = () =>{
document.getElementById("btn").onclick = () =>{
//创建XMLHttpRequest对象
var xmlHttpRequest = new XMLHttpRequest();
//注册回调函数
xmlHttpRequest.onreadystatechange = () =>{
if (xmlHttpRequest.readyState == 4) {
if (xmlHttpRequest.status == 200) {
document.getElementById("mydiv").innerHTML = xmlHttpRequest.responseText
}else {
alert(xmlHttpRequest.status)
}
}
}
//打开通道
xmlHttpRequest.open("GET","/a/proxy",true)
//发送数据
xmlHttpRequest.send()
}
}
</script>
<button id="btn">使用代理方式解决跨域问题</button>
<div id="mydiv"></div>
</body>
</html>
- 代理程序:
@WebServlet("/proxy")
public class ProxyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//通过httpclient组件,发送HTTP Get请求,访问TargetServlet
// 目标地址
String url = "http://localhost:8081/b/target";
HttpGet httpGet = new HttpGet(url);
// 设置类型 "application/x-www-form-urlencoded" "application/json"
httpGet.setHeader("Content-Type", "application/x-www-form-urlencoded");
// System.out.println("调用URL: " + httpGet.getURI());
//httpClient实例化
CloseableHttpClient httpClient = HttpClients.createDefault();
// 执行请求并获取返回
HttpResponse resp = httpClient.execute(httpGet);
HttpEntity entity = resp.getEntity();
// System.out.println("返回状态码:" + resp.getStatusLine());
// 显示结果
BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
String line = null;
StringBuffer responseSB = new StringBuffer();
while ((line = reader.readLine()) != null) {
responseSB.append(line);
}
System.out.println("服务器响应的数据:" + responseSB);
reader.close();
httpClient.close();
response.getWriter().print(responseSB);
}
}
- 目标程序(这里响应的数据比较简单就是一个json格式的数据):
@WebServlet("/target")
public class TargetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//相当于跨域的那个服务器,响应一个json格式的字符串
response.getWriter().print("{\"username\":\"zhangsan\"}");
}
}