什么是跨域
用简单的情况来说就是 浏览器不执行其他与自己服务端口和域名不同的脚本的安全限制
我们知道,在互联网中存在两个基本的通信模型,即CS(Client Server)和p2p (peer to peer) ,无论那种方式,要进行通信要考虑到应用层前的域名地址问题,以及到终端后的应用层的端口问题。
众所周知,在网络编程中要唯一确定一个应用要使用到socket套接字,我们就是需要设置域名和端口来让这个应用有个名分。
那么涉及到前后端应用交互时一定要考虑到的问题,当域名或进程端口抑或是ip不同时我们往往都要处理这个跨域问题。
解决方案之典中典之必备知识之jsonp之预备知识
想要使用jsonp方法解决跨域问题有一个前提条件就是在html页面中的script标签的src属性
<script src="https://cdn.bootcss.com/jquery/3.6.0/jquery.min.js"></script>
是不会被同源机制所跨域,那么客户端发送script请求,参数中带着处理返回数据的回调函数,来将前端请求植入src属性中, 再将回调函数拼接到请求末尾 服务端接收到请求并帮助执行掉这个回调函数
说人话就是既然我普通方法不行,那我就把我的请求放到script的src属性的链接里,要带的东西也用get方式将params拼接在src的后面
通过一个例子来了解jsonp
用jQuery向服务端请求时:
<script>
$('#btn').click(function () {
// console.log(123);
$.ajax({
url: 'http://127.0.0.1:3000',
success: function (res) {
console.log(res);
}
})
})
</script>
显然会遇到跨域的问题
后端以下面形态出击:
const http = require('http')
const querystring = require('querystring')
let server = http.createServer((req, res) => {
const url = req.url
const query = querystring.parse(url.split('?')[1]) //
const { name, age, callback } = query //
const data = {
name: name,
age: age
}
res.end(`${callback}('${JSON.stringify(data)}')`) //
// JSON.parse(...)翻转回去
})
server.listen(3000, () => {
console.log('server start');
})
服务器的任务比较简单
- 将前端成功传来的参数构造数组
- 解构数据
- 将数据组合好形式传回给客户端
那么,主要的实现过程在客户端:
<script>
class Jsonp {
constructor(req) {
this.url = req.url
this.callbackName = req.callbackName
}
create() {
const script = document.createElement('script')
const url = `${this.url}?callback=${this.callbackName}`
script.src = url
document.getElementsByTagName('head')[0].appendChild(script)
}
}
new Jsonp({
url: 'http://127.0.0.1:3000',
callbackName: 'getMsg'
}).create()
function getMsg(data) {
data = JSON.parse(data)
console.log(`My name is ${data.name}, and ${data.age} age`);
}
</script>
客户端需要: 将请求构造好后插入到页面的script标签的src属性中,即可成功与服务端搭上线,再将服务端传的数据处理 最后再将jsonp封装一下:
<script>
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataStr = ''
// 往url后面拼接参数
for (let key in params) {
dataStr += `${key}=${params[key]}&`
}
dataStr += `callback=${callbackName}`
return `${url}?${dataStr}`
}
return new Promise((resolve, reject) => {
callbackName = callbackName || 'cb' + Math.random().toString().replace('.', '')
let script = document.createElement('script')
script.src = generateUrl()
document.body.appendChild(script)
window[callbackName] = data => {
resolve(data)
// document.body.removeChild(script)
}
})
}
jsonp({
url: 'http://127.0.0.1:3000',
params: {
name: 'zowie',
age: 18
},
callbackName: 'getData'
})
.then(data => JSON.parse(data))
.then(data => {
console.log(data);
})
</script>
解决方案2——CORS(跨域资源共享)
浏览器通过请求做预检,判断是简单请求还是复杂请求
在浏览器的网络报文中我们可以通过查看到一些配置的参数:
- Access-Control-Request-Method //请求方法
- Access-Control-Request-Headers //请求头部
- Origin //请求发出的域
服务端应答:
- Access-Control-Allow-Origin //能发出这个请求的域名
- Access-Control-Allow-Methods //允许发出请求的方法
- Access-Control-Allow-Headers //允许发出请求的头部字段
- Access-Control-Allow-Age //预检请求能被缓存的最长时间,在这个时间内,同一个请求不会再次发出预检请求
res.writeHead(200, {
"Content-Type": "text/plain", //服务端设置请求头的类型
"Access-Control-Allow-Origin": "*" //表示允许所有的域来请求我
})
但是实际引用中几乎是不会允许这样的操作,为了安全各厂的前端后端都会在同一域下,只有通过自家的前端获取自家后端的数据,这是可控的,也是安全的,
-
简单请求 如果是简单请求,该请求回携带Origin,如果Origin不在服务端的Access-Control-Allow-Origin中,浏览器就会拦截响应
-
复杂请求 和简单请求不同的地方主要体现在 预检和响应
res.writeHead(200, {
"Content-Type": "text/plain", //服务端设置请求头的类型
"Access-Control-Allow-Origin": "*",//表示允许所有的域来请求我
"Access-Control-Allow-Methods": "PUT,POST,GET",
"Access-Control-Allow-Headers": "X-Custom-Header", //允许请求头
"Access-Control-Max-Age": 2000, //预检缓存最长
"Access-Control-Allow-Credentials": true //允许携带cookie
})
不仅仅是设置简单的请求,也可以设置更多的可选选项来满足自身需求
Nginx Proxy
Nginx启动服务器代理请求,因为服务器是没有同源策略