跨域
为什么跨域
为什么会出现跨域问题呢?那就不得不讲浏览器的同源策略了,它规定了协议号-域名-端口号这三者必须都相同才符合同源策略

如有有一个不相同,就会出现跨域问题,不符合同源策略导致的后果有
- LocalStorge、SessionStorge、Cookie 等浏览器内存无法跨域访问
- DOM 节点无法跨域操作
- Ajax 请求无法跨域请求
注意点:一个 IP 是可以注册多个不同域名的,也就是多个域名可能指向同一个 IP,即使是这样,他们也不符合同源策略

跨域的时机
请求发出去到后端,后端返回数据,在浏览器接收后端数据时被浏览器的跨域报错拦下来

跨域情况

解决跨域方案
JSONP
因为浏览器同源策略的存在,导致存在跨域问题,那有没有不受跨域问题所束缚的东西呢?其实是有的,以下这三个标签加载资源路径是不受束缚的
- script 标签:
<script src="加载资源路径"></script>
- link 标签:
<link herf="加载资源路径"></link>
- img 标签:
<img src="加载资源路径"></img>
而 JSONP 就是利用了 script 的 src 加载不受束缚,从而可以拥有从不同的域拿到数据的能力。但是 JSONP 需要前端后端配合,才能实现最终的跨域获取数据。
JSONP 通俗点说就是:利用 script 的 src 去发送请求,将一个方法名 callback 传给后端,后端拿到这个方法名,将所需数据,通过字符串拼接成新的字符串 callback(所需数据),并发送到前端,前端接收到这个字符串之后,就会自动执行方法 callback(所需数据)。老规矩,先上图,再上代码。

- 后端代码
require('console')
let express = require('express')
let app = express()
app.get('/say', function (req, res) {
let { wd, callback } = req.query
console.log(wd)
res.end(`${callback}('我不爱你')`)
})
app.listen(3000)
- 前端代码
const jsonp = (url, params, cbName) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
window[cbName] = (data) => {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback: cbName }
const arr = Object.keys(params).map((key) => `${key}=${params[key]}`)
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
})
}
jsonp('http://localhost:3000/say', { wd: '我爱你' }, 'callback').then(
(data) => {
console.log(data)
}
)
- 缺点
JSONP 的缺点就是,需要前后端配合,并且只支持 get 请求方法,XSS
Cors
Cors,全称是 Cross-Origin Resource Sharing,意思是跨域资源共享,Cors 一般是由后端来开启的,一旦开启,前端就可以跨域访问后端。
为什么后端开启 Cors,前端就能跨域请求后端呢?我的理解是:前端跨域访问到后端,后端开启 Cors,发送 Access-Control-Allow-Origin: 域名 字段到前端(其实不止一个),前端浏览器判断 Access-Control-Allow-Origin 的域名如果跟前端域名一样,浏览器就不会实行跨域拦截,从而解决跨域问题。

- 后端代码
server1
// server1
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(3000)
server2
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000']
app.use(function (req, res, next) {
let origin = req.headers.origin
if (whitList.includes(origin)) {
// 设置那个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带那个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许那个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检存活时间
res.setHeader('Access-Control-Max-Age', 6000)
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // PUT请求不做任何处理
}
}
next()
})
app.get('/getData', function (req, res) {
console.log(req.headers)
res.end('我不爱你')
})
app.put('/getData', function (req, res) {
console.log(req.headers)
res.setHeader('name', 'jw')
res.end('我不爱你')
})
app.use(express.static(__dirname))
app.listen(4000)
- 前端代码
let xhr = new XMLHttpRequest()
xhr.open('GET', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'zfpx')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.response)
}
}
}
xhr.send()
postMessage
场景:http://localhost:3000/a.html页面中使用了 iframe 标签内嵌了一个http://localhost:4000/b.html的页面
虽然这两个页面存在于一个页面中,但是需要 iframe 标签来嵌套才行,这两个页面之间是无法进行通信的,因为他们端口号不同,根据同源策略,他们之间存在跨域问题
那应该怎么办呢?使用 postMessage 可以使这两个页面进行通信

a.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(3000)
b.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(4000)
a.html
<iframe
src="http://localhost:4000/b.html"
frameborder="0"
id="frame"
onload="load()"
></iframe>
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000')
}
window.onmessage = function (e) {
console.log(e.data)
}
</script>
b.html
<script>
window.onmessage = function (e) {
console.log(e.data)
e.source.postMessage('我不爱你', e.origin)
}
</script>
window.name
a 和 b 是同域的
c 是独立的
a 获取 c 的数据
a 先引用 c c 把值放到 window.name,把 a 引用的地址改到 b
a.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(3000)
b.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(4000)
a.html
<iframe
src="http://localhost:4000/c.html"
frameborder="0"
onload="load()"
id="iframe"
></iframe>
<script>
let first = true
function load() {
if (first) {
let iframe = document.getElementById('iframe')
iframe.src = 'http:/localhost:3000/b.html'
first = false
} else {
console.log(iframe.contentWindow.name)
}
}
</script>
b.html
<!-- 空 -->
c.html
<script>
window.name = '我不爱你'
</script>
hash
a 和 b 是同域的
c 是独立的
a 获取 c 的数据
a 给 c 传一个 hash 值,c 收到 hash 值后,c 把 hash 传递给 b,b 将结果放到 a 的 hash 值中
a.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(3000)
b.js
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(4000)
a.html
<iframe src="http://localhost:4000/c.html#ilovayou"></iframe>
<script>
window.onhashchange = function () {
console.log(location.hash)
}
</script>
b.html
<script>
window.parent.parent.location.hash = location.hash
</script>
c.html
<script>
console.log(location.hash)
let iframe = document.createElement('iframe')
iframe.src = 'http://localhost:3000/b.html#idontloveyou'
document.body.appendChild(iframe)
</script>
Node 接口代理
还是回到同源策略,同源策略它只是浏览器的一个策略而已,它是限制不到后端的,也就是前端-后端会被同源策略限制,但是后端-后端则不会被限制,所以可以通过 Node 接口代理,先访问已设置 Cors 的后端 1,再让后端 1 去访问后端 2 获取数据到后端 1,后端 1 再把数据传到前端

- 后端 2 代码
// index.js http://127.0.0.1:8000
const http = require('http')
const urllib = require('url')
const port = 8000
http.createServer(function (req, res) {
console.log(888)
const {
query: { name, age },
} = urllib.parse(req.url, true)
res.end(`${name}今年${age}岁啦!!!`)
}).listen(port, function () {
console.log('server is listening on port ' + port)
})
创建一个 index2.js,并 nodmeon index2.js
- 后端 1 代码
// index2.js http://127.0.0.1:8888
const http = require('http')
const urllib = require('url')
const querystring = require('querystring')
const port = 8888
http.createServer(function (req, res) {
// 开启Cors
res.writeHead(200, {
//设置允许跨域的域名,也可设置*允许所有域名
'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
//跨域允许的请求方法,也可设置*允许所有方法
'Access-Control-Allow-Methods': 'DELETE,PUT,POST,GET,OPTIONS',
//允许的header类型
'Access-Control-Allow-Headers': 'Content-Type',
})
const { query } = urllib.parse(req.url, true)
const { methods = 'GET', headers } = req
const proxyReq = http
.request(
{
host: '127.0.0.1',
port: '8000',
path: `/?${querystring.stringify(query)}`,
methods,
headers,
},
(proxyRes) => {
proxyRes.on('data', (chunk) => {
console.log(chunk.toString())
res.end(chunk.toString())
})
}
)
.end()
}).listen(port, function () {
console.log('server is listening on port ' + port)
})
- 前端代码
// index.html http://127.0.0.1:5500
//步骤一:创建异步对象
var ajax = new XMLHttpRequest()
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23')
//步骤三:发送请求
ajax.send()
//步骤四:注册事件 onreadystatechange 状态改变就会调用
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
console.log(ajax.responseText) //输入相应的内容
}
}
WebSocket
我理解的 WebSocket 是一种协议(跟 http 同级,都是协议),并且他可以进行跨域通信

- 后端代码
// index.js http://127.0.0.1:8000
const Websocket = require('ws')
const port = 8000
const ws = new Websocket.Server({ port })
ws.on('connection', (obj) => {
obj.on('message', (data) => {
data = JSON.parse(data.toString())
const { name, age } = data
obj.send(`${name}今年${age}岁啦!!!`)
})
})
- 前端代码
// index.html http://127.0.0.1:5500/index.html
function myWebsocket(url, params) {
return new Promise((resolve, reject) => {
const socket = new WebSocket(url)
socket.onopen = () => {
socket.send(JSON.stringify(params))
}
socket.onmessage = (e) => {
resolve(e.data)
}
})
}
myWebsocket('ws://127.0.0.1:8000', { name: '林三心', age: 23 }).then((data) => {
console.log(data) // 林三心今年23岁啦!!!
})
Nginx
其实 Nginx 跟 Node 接口代理是一个道理,只不过 Nginx 就不需要我们自己去搭建一个中间服务

先下载 nginx,然后将 nginx 目录下的 nginx.conf 修改如下:
server{
listen 8888;
server_name 127.0.0.1;
location /{
proxy_pass 127.0.0.1:8000;
}
}
最后通过命令行 nginx -s reload 启动 nginx
- 后端代码
// index.js http://127.0.0.1:8000
const http = require('http')
const urllib = require('url')
const port = 8000
http.createServer(function (req, res) {
const {
query: { name, age },
} = urllib.parse(req.url, true)
res.end(`${name}今年${age}岁啦!!!`)
}).listen(port, function () {
console.log('server is listening on port ' + port)
})
- 前端代码
// index.html http://127.0.0.1:5500
//步骤一:创建异步对象
var ajax = new XMLHttpRequest()
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23')
//步骤三:发送请求
ajax.send()
//步骤四:注册事件 onreadystatechange 状态改变就会调用
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
//步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
console.log(ajax.responseText) //输入相应的内容
}
}
document.domain && iframe
场景:a.sanxin.com/index.html 与 b.sanxin.com/index.html 之间的通信
其实上面这两个正常情况下是无法通信的,因为他们的域名不相同,属于跨域通信
那怎么办呢?其实他们有一个共同点,那就是他们的二级域名都是 sanxin.com,这使得他们可以通过 document.domain && iframe 的方式来通信

<!-- http://127.0.0.1:5500/index.html -->
<body>
<iframe src="http://127.0.0.1:5555/index.html" id="frame"></iframe>
</body>
<script>
document.domain = '127.0.0.1'
document.getElementById('frame').onload = function () {
console.log(this.contentWindow.data) // 林三心今年23岁啦!!!
}
</script>
<!-- http://127.0.0.1:5555/index.html -->
<script>
// window.name="林三心今年23岁啦!!!"
document.domain = '127.0.0.1'
var data = '林三心今年23岁啦!!!'
</script>