本文介绍了一个简单的静态资源服务器的实例项目,希望能给Node.js初学者带来帮助。项目涉及到http、fs、url、path、zlib、process、child_process等模块,涵盖大量常用api;还包括了基于http协议的缓存策略选取、gzip压缩优化等;最终我们会发布到npm上,做成一个可以全局安装、使用的小工具。麻雀虽小,五脏俱全,一想是不是还有点小激动?话不多说,放码过来。
文中源码地址在最后附录中。
可先行体验项目效果:
安装:npm i -g here11
任意文件夹地址输入命令:here
step1 新建项目
因为我们要发布到npm上,所以我们先按照国际惯例,npm init,走你!在命令行可以一路回车,有些配置会在最后的发布步骤中细说。
目录结构如下:
bin文件夹存放我们的执行代码,web作为一个测试文件夹,里面放了些网页。
step2 码码
step2.1 雏形
静态资源服务器,通俗讲就是我们在浏览器地址栏输入形如“域名/test/index.html”的一个地址,服务器从根目录下的对应文件夹找到index.html,读出文件内容并返回给浏览器,浏览器渲染给用户。
const http = require("http"); const url = require("url"); const fs = require("fs"); const path = require("path"); const item = (name, parentPath) => { let path = parentPath = `${parentPath}/${name}`.slice(1); return `<div><a href="https://www.jb51.net/${path}" >${name}</a></div>`; } const list = (arr, parentPath) => { return arr.map(name => item(name, parentPath)).join(""); } const server = http.createServer((req, res) => { let _path = url.parse(req.url).pathname;//去掉search let parentPath = _path; _path = path.join(__dirname, _path); try { //拿到路径所对应的文件描述对象 let stats = fs.statSync(_path); if (stats.isFile()) { //是文件,返回文件内容 let file = fs.readFileSync(_path); res.end(file); } else if (stats.isDirectory()) { //是目录,返回目录列表,让用户可以继续点击 let dirArray = fs.readdirSync(_path); res.end(list(dirArray, parentPath)); } else { res.end(); } } catch (err) { res.writeHead(404, "Not Found"); res.end(); } }); const port = 2234; const hostname = "127.0.0.1"; server.listen(port, hostname, () => { console.log(`server is running on ${hostname}:${port}`); });
以上这段code就是我们的核心代码了,已经实现了核心功能,本地运行即可看到返回了文件目录,点击文件名便可浏览对应的网页、图片、文本啦。
step2.2 优化
功能实现了,但是我们可以在某些方面做做优化,提升实用性,顺便多学习几个api(装逼技巧)。
1. stream
我们目前读取文件返回给浏览器的操作是通过readFile一次性读出来,一次性返回,这样当然可以实现功能,但我们有更好的方式——用stream(流)进行IO操作。stream并不是node.js独有的概念,而是操作系统最基本的一种操作形式,所以理论上讲,任何一门server端语言都实现了stream的API。
为什么讲用stream是一种更好的方式?因为一次性读取、操作大文件,内存和网络是吃不消的,尤其在用户访问量比较大的情况下更为明显;而借助stream可以让数据流动起来,一点一点操作,从而提升性能。代码修改如下:
if (stats.isFile()) { //是文件,返回文件内容 //在createServer时传入的回调函数被添加到了"request"事件上,回调函数的两个形参req和res //分别为http.IncomingMessage对象和http.ServerResponse对象 //并且它们都实现了流接口 let readStream = fs.createReadStream(_path); readStream.pipe(res); }
编码实现非常简单,在需要返回文件内容时,我们创建了一个可读流,并把它直接导向了res对象。
2. gzip压缩
gzip压缩带来的性能(用户访问体验)提升是非常明显的,尤其在当下spa应用大行其道的时代,开启gzip压缩,可以大幅减小js、css等文件资源的体积,提升用户访问速度。作为一个静态资源服务器,我们当然要加上这个功能。
node中有一个zlib的模块,提供了很多压缩相关的api,我们就用它来实现:
const zlib = require("zlib"); if (stats.isFile()) { //是文件,返回文件内容 res.setHeader("content-encoding", "gzip"); const gzip = zlib.createGzip(); let readStream = fs.createReadStream(_path); readStream.pipe(gzip).pipe(res); }
有了stream的使用经验,我们再看这段代码的时候就好理解多了。把文件流先导向gzip对象,再导向res对象。此外,使用gzip压缩的时候还需要注意一点:需要把响应头里的content-encoding设置为gzip。否则浏览器会把一堆乱码展示出来。
3. http缓存