跨域:只要协议、域名、端口有任何一个不同,都被当作是不同的域
搭建静态资源服务器
项目目录结构
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.html 与 http://127.0.0.1:8883/index8883.html
能够成功的访问对应的静态资源,说明静态资源服务器搭建成功。
具体源码: 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 请求:
演示代码:options-request
演示二
跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。
在 node_8881_server.js 文件中加一段代码:
在浏览器打开 http://127.0.0.1:8883/index8883.html ,然后点击 send request to 8881 ,发现由于跨域的原因,只发送了浏览器只发送了 options 请求,实际的跨域请求没有发送:
演示代码:options-request
options 请求优化
options 请求可以被优化,一种方法是 将请求转为简单请求 ,如用 JSONP 做跨域请求,另一种方法是对 options 请求进行缓存。
我们可以通过设置 Access-Control-Max-Age 响应头,来设置预检请求结果的缓存时间
注意如果服务器不设置允许跨域,Access-Control-Max-Age 不会生效。
先将允许跨域的代码注释掉,并设置预检请求的缓存时间为 600 秒,发现 Access-Control-Max-Age 没有生效
由上图可以知,预检请求会发送多次。
将服务器端允许跨域的代码打开,然后发送多此跨域请求,会发现预检请求只发送了一次,因为预检请求的结果被缓存起来了。
服务器允许跨域了以后,Access-Control-Max-Age 生效了,预检请求成功的被缓存了起来,所以浏览器只在第一次跨域请求的时候发送了预检请求。
当然,如果浏览器勾上了 Disable cache ,那么 Access-Control-Max-Age 也是会不生效的
演示代码:options-request
总结
options 请求可用于检测服务器所支持的 http 请求方法,另外在跨域请求中,对于服务器数据产生副作用的 HTTP 请求,浏览器会先使用 OPTIONS 方法发起一个预检请求,检测该跨域请求是否被服务器所接受,服务器确认允许之后,才发起实际的 HTTP 请求,可以避免跨域请求对服务器的用户数据产生未预期的影响。