Ajax请求限制
Ajax只能向自己的服务器发送请求,这是因为浏览器的同源策略限制。
同源
什么是同源
如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。
图文来源:https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
同源的目的
同源政策是为了保护用户信息的安全,防止恶意的网站窃取数据。在不同源的情况下,其中一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错。
【注意:非同源地址发送Ajax请求,请求发送出去了,但是浏览器拒绝接受响应,所以失败了。】
跨域问题解决方案
问题的提出
分别用Express搭建两个简单的服务
const express = require('express');
const path = require('path')
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
服务器1:
app.get('/', (req, res) => {
res.send('Hello Server1')
})
app.listen(3000)
console.log('服务器1启动成功');
服务器2:
app.get('/', (req, res) => {
res.send('Hello Server2')
})
app.listen(3001)
console.log('服务器2启动成功');
结果如图:
现在想要在客户端1(对应服务器1)中使用Ajax来访问服务器2,会出现如下错误
该问题即为上述所说浏览器的同源(这里是端口号不同)策略限制,可通过以下方法来解决这个问题。
JSONP
什么是JSONP?
JSONP(json with padding),padding有填充的意思,翻译过来就是“将JSON数据作为填充内容”。
其核心主要是:在服务器端将JSON数据作为函数的参数,将JSON数据填充到函数当中。
JSONP的实现
可以使用JSONP来解决浏览器的同源策略限制。JSONP是绕过浏览器同源政策的限制,向非同源服务器端发送请求。
【tips:JSONP不属于Ajax请求,但它可以模拟Ajax请求】
实现JSONP步骤
- 将不同源的服务器请求地址写在script标签的src属性中(该属性不受同源策略的限制)
<script src="http://localhost:3001/test"></script>
- 服务器端响应数据必须是一个函数的调用(真正要发送给客户端的数据需要作为函数调用的参数)
app.get('/test', (req, res) => {
const result = 'fn({msg:"Hello Server2"})'
res.send(result)
});
- 在客户端全局作用域下定义函数(script标签前)【目的是为了在函数调用时找到函数定义的部分】
function fn(data) {}
- 在fn函数内部对服务器端返回的数据进行处理
function fn(data) {
console.log(data);
}
简单实现
客户端
<script>
function fn(data) {
console.log(data);
}
</script>
<script src="http://localhost:3001/test"></script>
服务器
app.get('/test', (req, res) => {
const result = 'fn({msg:"Hello Server2"})'
res.send(result)
});
结果
客户端1成功向服务器端2发送请求,并对响应回来的数据进行输出处理。
优化实现
上述仅为JSONP的简单理解实现,在真正使用过程中是非常不便的,现针对上述简单实现中可能出现的问题,分步骤进行优化。
- 函数名称的传递
- 动态请求
- 封装JSONP函数
- 服务端优化
客户端
<!-- 动态请求 -->
<button id="btn">点我发送请求</button>
// 获取按钮元素
var btn = document.getElementById('btn');
// 为按钮添加点击事件
btn.onclick = function () {
jsonp({
url: 'http://localhost:3001/test',
data: {},
success: function (data) {
console.log(data)
}
})
}
function jsonp(options) {
// 创建script标签
var script = document.createElement('script');
// 拼接字符串的变量
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}
var fnName = 'myJsonp' + Math.random().toString().replace('.', '')
// 全局作用域下定义函数
window[fnName] = options.success
// 设置script标签的src属性
script.src = options.url + '?callback=' + fnName + params;
// 将script追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
// 将body中的script标签删除调掉
document.body.removeChild(script);
}
}
服务端
app.get('/test', (req, res) => {
// 接收客户端传递过来的函数名称
const fnName = req.query.callback;
// 将函数名称对应的函数调用代码返回给客户端
const data = JSON.stringify({
name: "kk",
age: 20
})
const result = fnName + '(' + data + ')'
res.send(result)
})
上述代码可以直接使用【res.jsonp():传送JSONP响应】来实现,两者效果是一样的
app.get('/test', (req, res) => {
res.jsonp({
name: "kk",
age: 20
})
})
【tips:由上述JSONP实现过程可以知道,JSOP仅支持GET方法。】
CORS
什么是CROS?
CROS(Cross-origin resource sharing),即跨域资源共享,它允许浏览器向跨域服务器发送Ajax请求,克服了Ajax只能同源使用的限制。
CROS的实现
<button id="btn">点击发送请求</button>
客户端
var btn = document.getElementById('btn')
btn.onclick = function() {
ajax({
type: 'get',
url: 'http://localhost:3001/cross',
success: function(data) {
console.log(data)
}
})
}
服务端
app.get('/cross', (req, res) => {
// 1. 允许哪些客户端访问我
// * 代表允许所有的客户端访问我
res.header('Access-Control-Allow-Origin', '*')
// 2. 允许客户端使用哪些请求方法访问我
res.header('Access-Control-Allow-Methods', 'get,post')
res.send('ok')
})
这样做会存在问题:真实的项目中,服务器端的路由是有很多个的,如果向上面这样做的话,那么就需要在每一个路由中都设置响应头,这样代码的重复率太高了。
现可以使用express提供的中间件来拦截所有的请求,再对所有请求设置这两个响应头。
// 拦截所有请求
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'get,post')
next();
})
app.get('/cross', (req, res) => {
res.send('ok')
})
cookie
跨域的话cookie无法随着请求发送到服务器端,无法实现跨域登录。
可以使用withCredentials和Access-Control-Allow-Credentials来解决。
在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
Access-Control-Allow-Credentials:true允许客户端发送请求时携带cookie
客户端
例如:在客户端1使用ajax的过程中加上
// 当发送跨域请求时,携带cookie信息
xhr.withCredentials = true;
服务端
在服务器2拦截所有请求里添加
// 允许客户端发送跨域请求携带cookie信息
res.header('Access-Control-Allow-Credentials',true)
Node代理
什么是Node代理?
同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制的。
客户端1想要通过Ajax对服务器2进行请求,Node代理的简单理解就是客户端1先向服务器1发送请求,服务器1再向服务器2发送请求(服务器不存在同源政策限制);然后服务器2响应给服务器1,服务器1再响应给客户端1。
Node代理的实现
客户端1
ajax({
type: 'get',
url: 'http://localhost:3000',
success: function(data) {
console.log(data)
}
})
服务端1
const express = require('express');
const path = require('path');
// 向其他服务器端请求数据的模块
const request = require('request');
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
request('http://localhost:3001', (err, response, body) => {
res.send(body)
})
})
app.listen(3000)
console.log('服务器1启动成功');
服务端2
const express = require('express');
const path = require('path')
const app = express();
app.use(express.static(path.join(__dirname, 'public')))
app.get('/', (req, res) => {
res.send('ok')
})
app.listen(3001)
console.log('服务器2启动成功');
此篇为视频学习过程中的笔记。若有不足和错误,欢迎批评指正!