跨域问题
- 跨域:浏览器同源策略引起的接口调用问题
- 同源策略: 主机 端口 协议
- 接口调用:
XMLHttpRequest
和Fetch
都遵循同源策略 - 浏览器:浏览器发现可疑行为,拒绝接收
浏览器限制跨域请求一般有两种方式:
- 浏览器限制发起跨域请求
- 跨域请求可以正常发起,但是返回的结果被浏览器拦截了
一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。
为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的 HTTP
请求方法,浏览器必须先使用 OPTIONS
方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。
把这个选项勾上就可以看到预检请求了,关于预检请求,可以参看下面文章。
预检请求 https://www.jianshu.com/p/b55086cbd9af
来看看跨域问题是什么样的。
// http.js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
const { method, url } = req;
if (method == 'GET' && url == '/') {
fs.readFile('./index.html', (err, data) => {
res.setHeader('Content-Type', 'text/html');
res.end(data);
});
} else if (method == 'GET' && url == '/api/users') {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([{ name: 'warbler', age: 23 }]));
}
})
.listen(4000, () => {
console.log('api listen at ' + 4000);
});
// proxy.js
const express = require('express')
const app = express()
app.use(express.static(__dirname + '/'))
app.listen(3000)
// 可以同时启用两个服务器
const api = require('./http')
const proxy = require('./proxy')
// index.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
(async () => {
axios.defaults.baseURL = 'http://localhost:4000'
const res = await axios.get("/api/users")
console.log('data', res.data)
document.writeln(`Response : ${JSON.stringify(res.data)}`)
})()
</script>
当我们直接访问 http://localhost:4000/
的时候,是可以正常取到数据的。
当我们通过 3000
端口去访问 http://localhost:4000/
的时候,就会产生跨域错误。
通过这里也能看出来是一个跨域错误(CORS error
)
解决跨域问题
响应简单请求
响应简单请求:
动词为 get
/ post
/ head
没有自定义请求头
Content-Type 是 application/x-wwwform-urlencoded
, multipart/form-data
或 text/plain
之一 通过添加以下响应头解决:
res.setHeader("Access-Control-Allow-Origin", 'http://localhost:3000')
响应预检请求
该案例中通过添加自定义的 x-token
请求头使请求变为预检 (preflight)
请求。
// index.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
(async () => {
axios.defaults.baseURL = 'http://localhost:4000'
const res = await axios.get("/api/users", {
Headers: {
"X-Token": "aaabbb"
}
})
console.log('data', res.data)
document.writeln(`Response : ${JSON.stringify(res.data)}`)
})()
</script>
响应 preflight
请求,需要响应浏览器发出的 options
请求(预检请求),并根据情况设置响应头。
// http.js
else if (method == 'OPTIONS') {
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Headers": "X-Token,Content-Type",
"Access-Control-Allow-Methods": "PUT"
});
res.end();
}
响应 credential 请求
如果要携带 cookie
信息,则请求变为 credential
请求:
// 预检options中和/users接口中均需添加
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 设置cookie
res.setHeader('Set-Cookie', 'cookie1=va222;'
// ajax服务需要设置
axios.defaults.withCredentials = true
// 服务端查看cookie
console.log('cookie',req.headers.cookie)
// index.html
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
const { method, url } = req;
if (method == 'GET' && url == '/') {
fs.readFile('./index.html', (err, data) => {
res.setHeader('Content-Type', 'text/html');
res.end(data);
});
} else if (method == 'GET' && url == '/api/users') {
res.setHeader('Content-Type', 'application/json');
res.setHeader("Access-Control-Allow-Origin", 'http://localhost:3000')
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader("Set-Cookie", 'cookie1=123')
res.end(JSON.stringify([{ name: 'warbler', age: 23 }]));
} else if (method == 'OPTIONS') {
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Headers": "X-Token,Content-Type",
"Access-Control-Allow-Methods": "PUT"
});
res.end();
}
})
.listen(4000, () => {
console.log('api listen at ' + 4000);
});
// index.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
(async () => {
axios.defaults.baseURL = 'http://localhost:4000'
axios.defaults.withCredentials = true
const res = await axios.get("/api/users", {
headers: {
"X-Token": "aaabbb"
}
})
console.log('data', res.data)
document.writeln(`Response : ${JSON.stringify(res.data)}`)
})()
</script>
反向代理
服务端设置请求转发
const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express()
app.use(express.static(__dirname + '/'))
app.use('/api', createProxyMiddleware({
target: 'http://localhost:4000', changeOrigin: false
}));
app.listen(3000)
webpack devserver
vue.config.js
中配置的请求代理实际上是 webpack devserver
。
// vue.config.js
module.exports = {
devServer: {
disableHostCheck: true,
compress: true,
port: 5000,
proxy: {
'/api/': {
target: 'http://localhost:4000',
changeOrigin: true,
},
},
}
Socket实现一个即时通讯IM
原理:Net
模块提供一个异步 API
能够创建基于流 TCP
服务器,客户端与服务器建立连接后,服务器可以获得一个全双工 Socket
对象,服务器可以保存 Socket
对象列表,在接收某客户端消息时,推送给其他客户端。
// 用于TCP通讯
const net = require("net")
// 创建服务
const chatServer = net.createServer()
// 用户列表
const clientList = []
// 监听连接事件
chatServer.on('connection', client => {
// client => 流
client.write("Hello\n")
// 添加到用户列表
clientList.push(client)
client.on('data', data => {
// data => 二进制通讯 Buffer
console.log('🚀🚀~ receive:', data.toString());
// 广播
clientList.forEach((cli) => {
cli.write(data)
})
})
})
// 监听端口
chatServer.listen(9000)
// 通过Telnet连接服务器
// telnet localhost 9000