nodes实现cors

556 阅读4分钟

跨域

定义:

  • 协议,域名,端口任意一个不同, 都不属于同源,相互之间 的数据请求 称之为跨域请求

同源限制

  • 无法发ajax 请求 无法共享cookie

option请求:

  • 浏览器针对非简单请求的跨域 请求会主动向目标服务器先发起一个预检请求OPTIONS

  • 其目的就是为了判断实际发送的请求是否是安全的。

  • 什么是简单请求

    1. 不是 get也不是post的时候 基本就都是复杂请求
    2. 当get或者post 请求 自定义了 请求头的时候也会变成复杂请求,使得浏览器触发预检机制

node实现cors:

初始化

  • npm init -y
  • live-server: vscode插件安装,永远另外起一个不同端口的服务器做跨域请求准备

html

  • btn.addEventListener 监听click事件
  • click事件 里 xhr = new XMLHttpRequest()
  • xhr.open("POST", "http://localhost:3000/login", true);实现动态文件请求
    1. 静态文件:浏览器对服务器进行简单不会因为请求操作而发生变化的(html,js,img..),;
    2. 动态文件:浏览器访问这个资源,资源的源代码可能会发送改变
  • 通过setRequestHeader 将post 请求变成复杂请求 setRequestHeader和responseType的区别: 前者规定客户端向服务端传递的数据类型;后者规定的是客户端要求服务器需要返回的数据类型
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="btn">点击</button>
    <script>
      btn.addEventListener("click", () => {
        const xhr = new XMLHttpRequest();
        //我这里的默认服务器端口是3000
        xhr.open("POST", "http://localhost:3000/login", true);
        xhr.setRequestHeader("Content-Type", "application/json"); 
        xhr.responseType = "json"; 
        xhr.onload = function () {
          //浏览器会根据返回的类型进行解析
          console.log(xhr.response);
        };
        // 传递的数据格式是 字符串类型
        xhr.send('{"name":"ZOE"}');
      })
    </script>
  </body>
</html>

js

  • 创建服务,监听端口3000

  • 判断是否是跨域请求 是否存在 请求头origin,跨域请求 浏览器 会自带origin 请求头 是:

    1. Access-Control-Allow-Origin 白名单配置

    2. Access-Control-Allow-Headers 允许被自定义的请求头

    3. Access-Control-Max-Age 固定时间内的的跨域请求 可以无需重复发起option请求(浏览器里Disable cache 不能勾选 要允许 本地缓存,否则看不到效果)

    4. option请求的时候 做空值返回的处理 否:

    5. 获取请求路径 判断是文件还是文件目录

    6. 都不是 返回404

    7. 如果是文件目录 找到 目录下对应的index.html &fs.access 判断是否可访问,不可访问同样返回404;可访问 用可读流(fs.createReadStream)+管道(pipe) 进行文件内容读取,利用mime 获取文件内容格式,并设置编码规范为utf-8

    8. 如果是文件 直接用可读流(fs.createReadStream)+管道(pipe) 进行文件内容读取 默认text/html 格式 + utf-8编码 做普通请求的处理

const http = require("http");
const url = require("url");
const fs = require("fs");
const path = require("path");
const mime = require("mime");

const server = http.createServer((req, res) => {
 let { pathname, query } = url.parse(req.url, true);
 //跨域情况下,允许访问的处理
  if (req.headers.origin) {
  //实践应用场景肯定是判断是否是服务端预设的白名单里的来源,这里简单处理 用于效果呈现
    res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
    res.setHeader("Access-Control-Allow-Headers", "Content-Type");
    res.setHeader("Access-Control-Max-Age", "10");
    res.setHeader("Access-Control-Allow-Credentials", true);
    if (req.method === "OPTIONS") {
      return res.end();
    }
  }
    // 动态文件请求逻辑代码
  if (pathname === "/login" && req.method === "POST") {
   //文件读取
    req.on("data", (chunk) => {
      buffer.push(chunk);
    });
    //根据浏览器 不同的content-type 的要求 显示不同格式的数据,
    req.on("end", () => {
      let buf = Buffer.concat(buffer); // 浏览器传递来的数据
      if (req.headers["content-type"] === "application/json") {
        let obj = JSON.parse(buf.toString()); //回显为Json字符串
        res.setHeader("Content-Type", "application/json");
        //写入响应给浏览器的数据
        res.end(JSON.stringify(obj));
      } else if (req.headers["content-type"] === "text/plain") {
        res.setHeader("Content-Type", "text/plain");
         //写入响应给浏览器的数据
        res.end(buf.toString());
      }  else {
      //其他格式的content-type 暂不支持
        res.end("ok");
      }
    });
    
  }else {
     //静态文件请求逻辑代码
    let filePath = path.join(__dirname, pathname);
    // 获取请求路径 判断是文件还是文件目录
    fs.stat(filePath, function (err, statObj) {
      // 请求错误 没有找到对应url资源 返回404
      if (err) {
        res.statusCode = 404;
        res.end("NOT FOUND");
      } else {
        // 如果是文件,用可读流+管道 pipe 进行文件内容读取,利用mime 获取文件内容格式,并设置编码规范为utf-8
        if (statObj.isFile()) {
          res.setHeader(
            "Content-Type",
            mime.getType(filePath) + ";charset=utf-8"
          );
          fs.createReadStream(filePath).pipe(res);
        } else {
          //如果是文件目录 找到 目录下对应的index.html
          let htmlPath = path.join(filePath, "index.html");
          //fs.access判断拼接的路径是否可访问
          fs.access(htmlPath, function (err) {
            if (err) {
              // 不可访问 设置 状态码404
              res.statusCode = 404;
              res.end("NOT FOUND");
            } else {
              //  可访问,用可读流加管道 pipe 进行文件内容读取
              res.setHeader("Content-Type", "text/html;charset=utf-8");
              fs.createReadStream(htmlPath).pipe(res);
            }
          });
        }
      }
    });
  }
})
server.listen(3000, () => {
  console.log("server start 3000");
});

测试结果

  • 成功实现跨域请求 image.png
  • 10秒内不重复发起option请求

image.png

image.png

  • 这里的Disable cache 不能勾选 要允许 本地缓存,否则看不到效果

image.png

小结

跨域的解决 有很多种,例如:jsonp ,iframe,nginx方向代理,websocket; 但 cors 应该是 应用相对比较多了的 跨域解决方法,所以有了今天的这篇实践+总结

最后如果觉得本文有帮助 记得点赞三连哦 十分感谢