同源策略
同源策略最早由Netscape公司提出,是浏览器的的一种安全策略。 同源:协议、域名、端口号必须完全相同。 违背同源策略就是跨域
如我们的网页服务器是a.com的,需要向b.com发送请求,此时就是违背同源策略的,属于跨域请求,因为这两个是不同的服务。
跨域在我们项目中经常出现,因为单台服务器的服务是有上限的,性能是有瓶颈的,所以需要加入更多的服务器来使项目更加流畅、服务更加完善。
Ajax默认是需要遵守同源策略的。
下面我们使用同源策略来创建一个页面,首先,创建一个文件夹,里面新建两个文件。
其中index存放前端代码,server存放服务端代码。 首先我们先搭建服务端代码
const express = require('express')
const app = express();
app.get('/home',(request,response) => {
//响应一个页面,__dirname表示当前的绝对路径
response.sendFile(__dirname + '/index.html');
});
//前端获取数据
app.get("/data",(request,response) => {
response.send("用户数据");
})
app.listen(9000,()=>{
console.log("服务已启动,9000端口监听中....");
});
然后我们搭建前端代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页</title>
</head>
<body>
<h1>HELLO STUDENT</h1>
<button>点击获取用户数据</button>
<script>
const btn = document.querySelector("button");
btn.onclick = function(){
var xhr = new XMLHttpRequest();
// 由于是满足同源策略的,所以url可以简写
xhr.open("GET","/data");
xhr.send();
//绑定事件
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if (xhr.status >= 200 && xhr.status < 300){
console.log(xhr.response);
}
}
}
}
</script>
</body>
</html>
此时回到浏览器,属于http://127.0.0.1:9000/home,打开服务端搭建起来的页面,点击按钮获取数据,可以看到浏览器抓取到了包,控制台也可以输出结果。 注意:这里的页面需要使用网址打开,不能使用编译器自带的方式打开网页,不然会无法请求到数据!!
那么如何解决跨域问题呢?下面的章节中,将讲述几个方法来解决跨域问题。
JSONP
前几节课讲解axios时,使用的是网络上的axios包,那么我们获取到这个包,是不是我们从当前网页向目标网页的服务器发起一个请求呢?这个请求是不是也是跨域请求呢?
答案当然是是的,网络上获取axios包是https协议的,而我们的网页是file协议的,这必然导致跨域问题,但是为什么浏览器没报错呢?这就是因为script标签本身就具有跨域特性。
那么jsonp是什么呢?
- jsonp是一个非官方的跨域解决方案,纯粹凭借着程序员的聪明才智开发出来的,只支持get请求- 。
- jsonp就是利用上面那种具有跨域特性的包来发送跨域请求的,如:img、link、script….
那么我们怎么利用jsonp进行跨域请求呢,我们只需要在服务端开启一个端口供script标签进行访问,这个服务端返回的结果必须要是js代码,这样script标签才能正常识别。
可是这样还是不够好,我们可以在前端设置好我们需要对服务端返回的数据进行操作的函数,然后服务端返回的是js的函数调用,参数就是服务端需要返回的数据。
通过这种方式,就可以利用script标签对服务端的数据进行处理。
下面通过一个例子进行演示。 服务端代码:
//省略部分内容
...
app.get('/jsonp-server',(request,response) => {
const data = {name:"张三"};
let str = JSON.stringify(data);
//这里服务端返回的数据是函数调用,传入的是参数是需要返回的数据
response.end(`handle(${str})`);
});
...
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原理演示</title>
<style>
#result{
width: 200px;
height: 200px;
border: red 1px solid;
}
</style>
</head>
<body>
<div id="result"></div>
<script>
//我们在这里先对服务端返回的数据处理做定义
function handle(data){
const result = document.querySelector("#result");
result.innerHTML = data.name;
}
</script>
//这里获取到服务端的函数调用,调用上方的函数,参数就是服务端数据
<script src="http://127.0.0.1:8000/jsonp-server"></script>
</body>
</html>
我们此时再打开网页,可以发现,确实是输出了服务端给我们的数据
JSONP练习
那么我们可以尝试使用jsonp来判断用户输入的用户名是否重复,不过由于这里不讲诉数据库,所以先默认你输入任何内容都是会显示”用户名已存在”。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
用户名:<input type="text" id="username"/>
<p></p>
<script>
//获取input元素
const input = document.querySelector("#username");
//声明handle函数
function handle(data){
input.style.border = "solid 1px red";
//修改p标签内容
document.querySelector("p").innerHTML = data.msg;
}
input.onblur = function(){
//获取用户输入的值
var username = this.value;
//向服务端发送请求,判断用户是否存在
//1.创建script标签
const script = document.createElement("script");
//2.设置标签的src属性,如果要传递参数,可以在后面输入?a=10...
script.src = "http://127.0.0.1:8000/check-username";
//3.将script标签插入到文档中
document.body.appendChild(script);
}
</script>
</body>
</html>
//省略部分内容
...
app.get('/check-username',(request,response) => {
const data = {
exist:1,
msg:"用户名已存在!"
}
let str = JSON.stringify(data);
response.send(`handle(${str})`);
});
...
打开浏览器,可以看到捕获到的包中,返回的结果其实就是一次函数调用。
jQuery中发送JSONP请求
在jQuery中发送jsonp请求非常简单,只需要使用$.getJSON就行。
首先,我们先制定一个需求,点击按钮,发送jsonp请求,返回的内容打印在div中。
先来制作前端代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../../jQuery3.6/jquery-3.6.0.min.js"></script>
<style>
#result {
width: 300px;
height: 300px;
border: 1px solid red;
}
</style>
</head>
<body>
<button>点击使用jQuery发送JSONP请求</button>
<div id="result"></div>
<script>
//绑定单击事件
$("button").on('click',function(){
//使用此方法发送JSONP请求,第一个参数是url,第二个参数是对返回数据进行的操作
//注意:这里url必须要有?callabck=?这个参数,这是固定格式
$.getJSON("http://127.0.0.1:8000/jquery-jsonp?callback=?",function(data){
$("#result").html(`
名称:${data.name}<br/>
年龄:${data.age}<br/>
性别:${data.gender}
`)
})
})
</script>
</body>
</html>
再来制作服务端代码
app.get('/jquery-jsonp',(request,response) => {
const data = {
name:"张三",
age:30,
gender:"男"
}
//将数据转换为字符串
let str = JSON.stringify(data);
//接受callback参数
let cb = request.query.callback;
response.send(`${cb}(${str})`);
})
上面前端代码中说到url中必须要有callback参数,这是为什么呢? 其实我们前端传递url的时候没有给callback赋值,但是jQuery调用时是为callback赋值了的,赋的值就是后面定义的那个函数,那么我们在服务器代码中,只要接受到这个函数值,在返回数据给前端时,返回这个函数以及传参调用,就可以使用我们的函数了。
并且上面的div中也输出了我们服务器传回的值。
CROS
CROS是什么?
CROS,跨域资源共享。是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务端进行,支持get和post请求。跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
其实前面我们能正常使用Ajax访问服务,都是设置了CROS,下面再来回顾一下服务端使用的方法。
//1. 引入express
const express = require('express');
//2. 创建应用对象
const app = express();
//3. 创建路由规则
app.all("/cros-server",(request,response) => {
//添加CROS响应头
response.setHeader("Access-Control-Allow-Origin","*");
//*号表示所有网站的都可以响应,如果我们只想特定的网页,可以接上对应的url,如
//response.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500");
//设置允许头信息
response.setHeader("Access-Control-Allow-Headers","*");
//设置允许请求的方法
response.setHeader("Access-Control-Allow-Method","*");
response.send("HELLO");
})
//4. 监听端口,启用服务
app.listen(8000,()=>{
console.log("服务已经启动,8000端口监听中...");
})
CORS还有许多方式,有需要的可以查看网址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS