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
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(); }
如果要携带 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)
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, }, }, }
原理: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('