1. 什么是跨域?
浏览器有一个同源策略:如果两个 url 的协议、域名、端口三者完全相同,那就称之为同源。
同源之间获取资源是不受限制的,如果不满足同源(即协议、域名、端口有一个条件不同),那么获取资源就会受到限制,此时我们称之为跨域。
总结来说:如果两个url之间需要进行通信,但是不满足同源策略,此时就发生了跨域。
下面是一个例子:(所有url场景取自新浪首页,即url_1)
/*
url_1: https://www.sina.com.cn/
url_2: https://www.sina.com.cn/api/hotword.json
url_3: https://sspapi.zenyou.71360.com/js?i=537&o=2&ran=7160123520
url_4: https://b.zenyou.71360.com/bid/zhendao
在 url_1 向 url_2 发送一个请求,此时不发生跨域
在 url_1 向 url_3 发送一个请求,此时产生跨域问题,因为 url_1 url_3 之间域名不同
在 url_1 向 url_4 发送一个请求,此时产生跨域问题,因为 url_1 url_4 之间域名不同
*/
2. HTTP 请求的理解
要了解跨域, 首先要知道HTTP 协议的两个概念: 简单请求和预检请求
2.1 简单请求
某些请求不会触发cors预检请求,这样的请求称之为简单请求,值得注意的是,该术语并不属于fetch规范;
对于简单请求,浏览器直接发出CORS请求,具体来说,就是在头信息之中,增加一个Origin字段;在返回头信息中,增加一个Access-Control-Allow-Origin: * 字段(后端不作处理时)
简单请求并不会触发跨域, 只有非简单请求才会触发跨域.
满意所有下述条件,则该请求可视为简单请求:
1. 使用下列方法
1.1 GET
1.2 HEAD
1.3 POST
2. HTTP头部只包含如下字段(这些字段统称为对 CORS 安全的首部字段集合)
2.1 Accept
2.2 Accept-Language
2.3 Content-Language
2.4 Content-Type (需要注意额外的限制)
2.5 DPR
2.6 Downlink
2.7 Save-Data
2.8 Viewport-Width
2.9 Width
3. Content-Type 的值仅限于下列三者之一:
3.1 text/plain
3.2 multipart/form-data
3.3 application/x-www-form-urlencoded
4. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器
5. 请求中没有使用 ReadableStream 对象
2.2 非简单请求
非简单请求, 就是字面上的意思; 需要注意的是, 开发过程中基本上所有的请求都是非简单请求. 在浏览器发送非简单请求时, 并不是直接发送请求到服务器, 而是会先发送一个预检请求到服务器.
当一个请求满足下列任意一个条件时,它会变成非简单请求(即该请求会先发送一个options请求到服务器)
1. 使用下列方法
1.1 PUT
1.2 DELETE
1.3 CONNECT
1.4 OPTIONS
1.5 TRACE
1.6 PATCH
2. 人为设置了“对CORS 安全的首部字段集合”之外的其他首部字段(这些字段统称为对 CORS 安全的首部字段集合)
2.1 Accept
2.2 Accept-Language
2.3 Content-Language
2.4 Content-Type (需要注意额外的限制)
2.5 DPR
2.6 Downlink
2.7 Save-Data
2.8 Viewport-Width
2.9 Width
3. Content-Type 的值不属于下列三者之一:
3.1 text/plain
3.2 multipart/form-data
3.3 application/x-www-form-urlencoded
4. 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器
5. 请求中使用了ReadableStream对象
2.3 预检请求
预检请求, 即option 请求; 他表示当浏览器需要向服务器发送非简单请求时, 提前发送的一个请求.
预检请求的作用是确认服务器与浏览器之间是否能够进行通信, 以及为后续的请求做准备
PS: 对于预检请求, 这里只知道在跨域问题中可以用于确认一个请求是否发生了跨域, 至于在其他方面的应用, 暂时还没有接触过.
3. 跨域演示
express(app.js):
const express = require('express')
const log = console.log.bind(console)
const app = express()
app.get('/helloworld', (request, response) => {
response.send('hello')
})
const main = () => {
let server = app.listen(2300, () => {
let host = server.address().address
let port = server.address().port
log(`应用实例,访问地址为 http://${host}:${port}`)
})
}
if (require.main === module) {
main()
}
html(crossOriginDemo.html):
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>跨域demo</title>
<style type="text/css">
.ajaxButton {
width: 100px;
height: 50px;
background: blue;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<div class="ajaxButton">发送请求</div>
<script>
const log = console.log.bind(console)
const ajax = (method, url, data, headers, callback) => {
let r = new XMLHttpRequest()
r.open(method, url, true)
// 设置 headers
Object.entries(headers).forEach(([k, v]) => {
r.setRequestHeader(k, v)
})
r.onreadystatechange = () => {
if (r.readyState === 4) {
callback(r.response)
}
}
if (method === 'POST') {
data = JSON.stringify(data)
}
r.send(data)
}
let ele = document.querySelector('.ajaxButton')
ele.addEventListener('click', function () {
let url = 'http://localhost:2300/helloworld'
let method = 'GET'
let data = {}
let headers = {
'Content-Type': 'application/json',
}
ajax(method, url, data, headers, (r) => {
log('cors r is', r)
})
})
</script>
</body>
</html>
跨域原因:
由于页面使用的端口号是63342, 而服务器 设置的端口号是2300, 这破坏了同源策略, 因此发生了跨域, 不能够正常请求到数据.
点击页面按钮之后, 可以看到跨域提示:
4. 跨域的解决方案
- cors 模块: https://www.cnblogs.com/oulae/p/12784186.html
- json: https://www.cnblogs.com/oulae/p/12784187.html
- webpack devServer 代理: https://www.cnblogs.com/oulae/p/12784188.html
- 自定义node 转发: https://www.cnblogs.com/oulae/p/12784189.html
- nginx
- postMessage
- iframe
5. 跨域demo
6. 测试/生产环境中的跨域问题
- webpack dev Server(develop server)
- nginx
- cors
上面的这三个内容是在web 项目中常见的跨域解决方案, 但是, 如果webpack 使用webpack dev Server 解决跨域, 那么这个解决方式只能在本地运行时解决跨域问题, 如果后端在部署项目的时候没有部署webpack dev server(即没有使用在服务器建一个中转服务器, 并配置dev server), 那么还是会有跨域问题; webpack dev Server 是前端跨域解决方案, 但是不适用于生产环境和测试环境, 只适用于本机
cors 模块的配置和nginx 的配置一般是后端完成的.
因此测试/生产环境中的跨域问题, 大多数情况下都是后端去解决.