Node.js开发静态资源服务器

在09年Node.js出来后,让前端开发人员的开发路线变的不再那么单调,经过这么多年的发展,我们的开发基本已经离不开Node.js,不管是用作于工具类的开发,还是做完服务端的中间层,Node.js都占据了非常重要的地位,今天我们就一起通过原生的js+Node来实现一个简单的静态资源服务,如果你还不了解这方面的知识,那就跟我一起来学习吧!

静态资源服务器

Node.js经过这么多年的发展,已经有了很多很优秀的基础框架或类库,像express.jsKoa.jsegg.js等,它们都是基于原生的Node.js来实现的,而我们之所以不选择用这些框架,其实就是希望大家能够了解前面这几种框架是如何实现一个基础的静态资源服务的,只有当我们了解了这其中的知识点,再使用这些框架时才会更加得心应手,下面我们一起看一下这个基础的静态资源服务该如何开发吧!

首先,我们要了解的是,既然是要开发静态资源服务,那么什么是静态资源服务呢?简单来说就是可以静态访问的一个资源服务器,而这些静态资源包括但不限于类似htmlcssjs以及一些图片资源,音视频等等。我们能通过网络直接访问这些内容,就是因为它们通过静态资源服务器将这些内容挂载在网上。

我们首先要做的就是创建一个服务,在Node中我们通过http模块来创建一个服务,httpNode中的一个基础的API,相关的内容可以查阅官方文档,然后我们需要读取本地的资源,那么就需要用到另外一个模块fsfs模块能够操作本地的资源文件,具体的内容也可以通过官网的文档进行查看,下面我们一起来看一下相关的代码,代码如下:

const fs = require('fs');
const http = require('http');
http.createServer((req, res) => {
fs.readFile(__dirname + req.url, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
}
});
}).listen(8000);

在上述的代码中,我们首先通过http.createServer创建了一个服务器,并且通过listen(8000)来监听了8000端口的服务,这样我们就可以直接在浏览器中通过访问localhost:8000来进行访问;在这个服务的内部,我们通过fs.readFile方法来读取文件,因为我们没有指定读取的具体内容,而是通过获取请求信息来判断我们要展示给用户看到的内容,所以最终会在页面中展示404,如下图所示:

Node.js开发静态资源服务器

上面的代码已经简单了实现了一个静态资源服务了,但是代码看起来就比较零散,下面我们一起来对这个代码进行改造,通过模块化的思想对代码进行升级,这样不至于让我们的代码看起来像面条代码。

模块化

首先,我们读取的文件地址可能跟我们的这个文件服务不在一个目录中,为了解决问题问题,我们需要修改我们的文件目录,而关于目录的相关信息,就不得不用到Node中另外一个很重要的模块path了。通过path模块,我们能够解析不同目录中的内容,一起来看一下修改后的代码吧,如下:

const fs = require('fs');
const path = require('path');
const directoryName = './public';
const requestUrl = 'index.html';
const filePath = path.join(directoryName, requestUrl);
fs.readFile(filePath, (err, data) => {
// ...
});

我们通过path将静态资源的根目录拼接在一起,这样当我们使用fs.readFile来读取这个文件时,不至于因为路径错误而读取不到正确的内容。

接下来我们需要考虑的就是安全性的问题了,因为我们不希望用户能够在未授权的情况随意访问我们服务器中的任意资源,目前并不是不能访问除了指定的目录外的文件,这就是一个安全性的问题。为了解决这个问题,我们可以通过path模块来检测用户请求的文件是否是可以访问的,下面一起看看我们对上述代码的改造,如下:

const path = require('path');
const directoryName = './public';
const root = path.normalize(path.resolve(directoryName));
const requestUrl = 'index.html';
const filePath = path.join(root, fileName);
const isPathUnderRoot = path
.normalize(path.resolve(filePath))
.startsWith(root);

上述代码中,我们通过path.normalize来检测这个文件地址是否包含在根路径中,这样就能确保用户只能访问到我们允许访问的地址。同样的,我们还可以通过检查文件的类型来确保用户无法访问到一些敏感的文件。

为此,我们需要指定能够访问的文件类型的数组或对象,只有当用户访问的文件在这个数组或者对象中,才能展示给用户看到,因此我们还需要用到path模块来检查文件的后缀名,修改后代码如下:

const path = require('path');
const types = ['html', 'css', 'js', 'json'];
const requestUrl = 'index.html';
const extension = path.extname(requestUrl).slice(1);
const isTypeSupported = types.includes(extension);

我们定义了一个文件类型的数组,其中就包含了用户可以访问的资源类型,然后我们通过path.extname来检测用户请求的文件的后缀,只有在这个类型文件的数组包含的类型,才会展示给用户进行查看。

当然,当我们访问html的时候,我们一般都不会也不需要在浏览器中输入xxx.html这样的后缀,因此我们还需要对html这样的后缀做省略,让用户可以直接通过网址就能访问正确的页码,而不需要添加.html。下面我们一起看一下改造后的代码,如下:

const fs = require('fs');
const path = require('path');
const directoryName = './public';
const root = path.normalize(path.resolve(directoryName));
const extension = path.extname(req.url).slice(1);
let fileName = requestUrl;
if (requestUrl === '/') {
fileName = 'index.html';
} else if (!extension) {
try {
fs.accessSync(path.join(root, requestUrl + '.html'), fs.constants.F_OK);
fileName = requestUrl + '.html';
} catch (e) {
fileName = path.join(requestUrl, 'index.html');
}
}

上述的代码中,我们通过判断用户访问的文件类型是否包含在前面的数组中,当用户访问的类型不包含时,我们通过fs.accessSync来测试用户访问的文件是否是允许访问的,如果允许访问则直接返回一个.html的文件,如果用户访问的地址是不允许访问的,则直接返回index.html

最后,当我们将前面所有的内容都完成后,我们可以将这些内容都整合在一起,下面我们一起来看一下最终完成的这个静态资源服务的完整代码吧,如下:

const fs = require('fs');
const http = require('http');
const path = require('path');
// 静态资源服务器地址
const port = 8000;
// 静态资源文件夹
const directoryName = './public';
// 允许访问的文件类型
const types = {
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
json: 'application/json',
xml: 'application/xml',
};
// 静态资源文件根路径
const root = path.normalize(path.resolve(directoryName));
// 创建静态资源服务器
const server = http.createServer((req, res) => {
// 获取访问的文件类型
const extension = path.extname(req.url).slice(1);
// 文件类型后缀
const type = extension ? types[extension] : types.html;
// 是否支持的文件类型
const supportedExtension = Boolean(type);
// 如果这个文件类型不允许访问,则直接返回404
if (!supportedExtension) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
return;
}
// 通过url获取访问的文件名称
let fileName = req.url;
// 如果访问的路径是 /
if (req.url === '/') {
// 则文件名是 index.html
fileName = 'index.html';
} else if (!extension) {
try {
// 检测文件是否允许访问
fs.accessSync(path.join(root, req.url + '.html'), fs.constants.F_OK);
// 当允许访问时,则返回对应的页面
fileName = req.url + '.html';
} catch (e) {
// 否则直接返回 index.html
fileName = path.join(req.url, 'index.html');
}
}
const filePath = path.join(root, fileName);
const isPathUnderRoot = path.normalize(path.resolve(filePath)).startsWith(root);
if (!isPathUnderRoot) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
return;
}
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
} else {
res.writeHead(200, { 'Content-Type': type });
res.end(data);
}
});
});
server.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});

最终我们通过不到100行的代码就实现了这个静态资源服务,我们可以看一下具体的运行效果。

Node.js开发静态资源服务器

当我们访问首页时,就直接展示默认的index.html中的文件,如果我们访问的内容不允许访问,则直接显示404,如下所示:

Node.js开发静态资源服务器

最后

我们只通过Node.js中一些简单的API就开发了一个基础的静态资源服务器,也让大家了解了一些Node.js相关的基础操作,了解这些基础的操作不仅有利于提高我们自身的知识储备,也更有利于我们在实际开发中少踩一些坑。