options 请求与跨域

677 阅读4分钟

跨域:只要协议、域名、端口有任何一个不同,都被当作是不同的域

搭建静态资源服务器

项目目录结构

pic15.png

readStaticFile 接收 response 对象和静态资源的绝对路径,使用 response 对象设置返回的消息体,使用 path 模块解析静态资源的拓展名,借助 mime 包设置返回的 MIME 类型,使用 fs 模块读取静态资源后将数据写入响应正文。

// readStaticFile.js

// 引入依赖的模块
var path = require("path");
var fs = require("fs");
var mime = require("mime");

function readStaticFile(res, filePathname) {
  console.log("filePathname  ", filePathname);
  var ext = path.parse(filePathname).ext;
  var mimeType = mime.getType(ext);

  // 判断路径是否有后缀, 有的话则说明客户端要请求的是一个文件
  if (ext) {
    // 根据传入的目标文件路径来读取对应文件
    fs.readFile(filePathname, (err, data) => {
      // 错误处理
      if (err) {
        res.writeHead(404, { "Content-Type": "text/plain" });
        res.write("404 - NOT FOUND");
        res.end();
      } else {
        res.writeHead(200, { "Content-Type": mimeType });
        res.write(data);
        res.end();
      }
    });
    // 返回 false 表示, 客户端想要的 是 静态文件
    return true;
  } else {
    // 返回 false 表示, 客户端想要的 不是 静态文件
    return false;
  }
}

// 导出函数
module.exports = readStaticFile;

node_8881_server.js 的源码

const http = require("http");
const url = require("url");
const path = require("path");
const readStaticFile = require("./modules/readStaticFile");

http
  .createServer(function (request, response) {
    var urlObj = url.parse(request.url);
    var urlPathname = urlObj.pathname;
    var filePathname = path.join(__dirname, "/public", urlPathname);

    // 读取静态文件
    readStaticFile(response, filePathname);
  })
  .listen(8881);

// 终端打印如下信息
console.log("Server running at http://127.0.0.1:8881/");

node_8883_client.js 的源码

const http = require("http");
const url = require("url");
const path = require("path");
const readStaticFile = require("./modules/readStaticFile");

http
  .createServer(function (request, response) {
    var urlObj = url.parse(request.url);
    var urlPathname = urlObj.pathname;
    var filePathname = path.join(__dirname, "/public", urlPathname);

    // 读取静态文件
    readStaticFile(response, filePathname);
  })
  .listen(8883);

// 终端打印如下信息
console.log("Server running at http://127.0.0.1:8883/");

此时,node_8881_server.js 与 node_8883_client.js 除了监听的端口不一样,其它都一样

index8881.html 源码

<!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>server</title>
  </head>
  <body>
    Welcome to 8881
  </body>
</html>

index8883.html 源码

<!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>client</title>
  </head>
  <body>
    Welcome to 8883
  </body>
</html>

使用浏览器打开 http://127.0.0.1:8881/index8881.htmlhttp://127.0.0.1:8883/index8883.html

能够成功的访问对应的静态资源,说明静态资源服务器搭建成功。

pic16.png

pic17.png

具体源码: options-request

演示跨域

演示一

OPTIONS 请求即预检请求,可用于检测服务器支持的请求方法(GET、POST、PUT 等),或者在跨域请求中,检测实际请求是否被服务器所接受,可以避免跨域请求对服务器的用户数据产生未预期的影响。

修改 index8883.html 的代码,添加一行代码,修改 Content-Type 为 text/html,将请求变为复杂请求

<!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>client</title>
  </head>
  <body>
    Welcome to 8883
    <button onclick="send()">send request to 8881</button>
  </body>
  <script>
    function send() {
      const xhr = new XMLHttpRequest();
      xhr.open("GET", "http://127.0.0.1:8881/", true);
      // 修改默认的 Content-Type ,变为复杂请求
      xhr.setRequestHeader("Content-Type", "text/html");
      // 获取数据后的处理程序
      xhr.onreadystatechange = function () {};
      xhr.send();
    }
  </script>
</html>

点击 send request to 8881 按钮,发现产生了 options 请求:

pic22.png

演示代码:options-request

演示二

跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

在 node_8881_server.js 文件中加一段代码:

img14.png

在浏览器打开 http://127.0.0.1:8883/index8883.html ,然后点击 send request to 8881 ,发现由于跨域的原因,只发送了浏览器只发送了 options 请求,实际的跨域请求没有发送:

img15.png

演示代码:options-request

options 请求优化

options 请求可以被优化,一种方法是 将请求转为简单请求 ,如用 JSONP 做跨域请求,另一种方法是对 options 请求进行缓存。

我们可以通过设置 Access-Control-Max-Age 响应头,来设置预检请求结果的缓存时间

注意如果服务器不设置允许跨域,Access-Control-Max-Age 不会生效。

先将允许跨域的代码注释掉,并设置预检请求的缓存时间为 600 秒,发现 Access-Control-Max-Age 没有生效

pic24.png

pic23.png

由上图可以知,预检请求会发送多次。

将服务器端允许跨域的代码打开,然后发送多此跨域请求,会发现预检请求只发送了一次,因为预检请求的结果被缓存起来了。

pic25.png

pic26.png

服务器允许跨域了以后,Access-Control-Max-Age 生效了,预检请求成功的被缓存了起来,所以浏览器只在第一次跨域请求的时候发送了预检请求。

当然,如果浏览器勾上了 Disable cache ,那么 Access-Control-Max-Age 也是会不生效的

pic27.png

演示代码:options-request

总结

options 请求可用于检测服务器所支持的 http 请求方法,另外在跨域请求中,对于服务器数据产生副作用的 HTTP 请求,浏览器会先使用 OPTIONS 方法发起一个预检请求,检测该跨域请求是否被服务器所接受,服务器确认允许之后,才发起实际的 HTTP 请求,可以避免跨域请求对服务器的用户数据产生未预期的影响。

参考

  1. 什么时候会发送options请求

  2. 面试官:说说你对 options 请求的理解

  3. 【web】http请求中的 OPTIONS 详解 & 跨域

  4. Node.js 系列 - 搭建静态资源服务器

  5. OPTIONS - HTTP | MDN

  6. Cross-Origin Resource Sharing (CORS) - HTTP | MDN