【nodejs基础】解决跨域问题

跨域问题

  • 跨域:浏览器同源策略引起的接口调用问题
  • 同源策略: 主机 端口 协议
  • 接口调用: XMLHttpRequestFetch 都遵循同源策略
  • 浏览器:浏览器发现可疑行为,拒绝接收

浏览器限制跨域请求一般有两种方式:

  • 浏览器限制发起跨域请求
  • 跨域请求可以正常发起,但是返回的结果被浏览器拦截了

一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。

为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须先使用 OPTIONS 方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。

把这个选项勾上就可以看到预检请求了,关于预检请求,可以参看下面文章。

image.png

image.png

预检请求

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/ 的时候,是可以正常取到数据的。

image.png

当我们通过 3000 端口去访问 http://localhost:4000/ 的时候,就会产生跨域错误。

image.png

通过这里也能看出来是一个跨域错误(CORS error

image.png

解决跨域问题

响应简单请求

响应简单请求:

  • 动词为 get / post / head
  • 没有自定义请求头
  • Content-Type 是 application/x-wwwform-urlencodedmultipart/form-datatext/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

参考资料

开课吧全栈架构师课程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值