nodejs不只仅能够用来写服务端接口,用来作静态文件服务器替代nginx的功能, 也是分分钟能够搞定的。 话很少说,先上代码:css
var server=http.createServer(function (req,res){
fs.createReadStream(Path.resolve(__dirname,"."+req.url)).pipe(res);
})
复制代码
在项目根目录建一个hello.html文件测试一下 hello.html内容以下:html
<h1>hello,world</h1>
复制代码
node app.js运行,打开浏览器访问一下:http://localhost/hello.htmlnode
咱们再回头审视一下代码,的确就只有这么简单,这要归功于node Stream类 pipe方法的强大,fs.createReadStream读取本地文件建立一个可读流(ReadStream类的实例),再使用pipe导流到res响应流,res是一个http.ServerResponse类的实例,是一个可写流,继承自 Stream类nginx
http.ServerResponse类的继承关系以下:git
上述代码实现静态文件服务器后,意味着项目根目录下全部的文件(递归)均可以经过浏览器直接访问和下载了,这样会带来一些安全性的问题,想一想看,你的服务器端代码和配置文件都能经过浏览器直接下载了,所以须要在代码里加一些限制,例如只能访问特定的目录下的文件和特定扩展名的文件,这样还不够,参考OWasp Top 10安全风险(第4条-不安全的对象直接引用),攻击者仍然能够经过../../目录回溯的方法访问到其它目录,对于访问路径中包含..的也要所有过滤掉。github
mime type是指http 响应头中的content-type字段,它决定了浏览器如何解析文件,是直接当作纯文件显示(text/plain),仍是作为html文件渲染(text/html),或者当作二进制文件下载,没有输出正确的mine type,可能致使图片文件没法显示,字体文件无效,视频文件没法播放的问题。要实现起来也十分简单,只须要作一个映射表,不一样文件扩展名,在响应头的content-type字段中输出对应的mine type就好了。web
完整代码以下:算法
const http=require("http");
const Path=require("path");
const fs=require("fs");
var server=http.createServer(function (req,res){
const fileName=Path.resolve(__dirname,"."+req.url);
const extName=Path.extname(fileName).substr(1);
if (fs.existsSync(fileName)) { //判断本地文件是否存在
var mineTypeMap={
html:'text/html;charset=utf-8',
htm:'text/html;charset=utf-8',
xml:"text/xml;charset=utf-8",
png:"image/png",
jpg:"image/jpeg",
jpeg:"image/jpeg",
gif:"image/gif",
css:"text/css;charset=utf-8",
txt:"text/plain;charset=utf-8",
mp3:"audio/mpeg",
mp4:"video/mp4",
ico:"image/x-icon",
tif:"image/tiff",
svg:"image/svg+xml",
zip:"application/zip",
ttf:"font/ttf",
woff:"font/woff",
woff2:"font/woff2",
}
if (mineTypeMap[extName]) {
res.setHeader('Content-Type', mineTypeMap[extName]);
}
var stream=fs.createReadStream(fileName);
stream.pipe(res);
}
})
server.listen(80);
复制代码
对于文本类型的文件,如html,js,css,采用gzip压缩能够大幅减小传输量,提高服务器传输性能,固然这会损耗一点服务器的cpu性能作为代价,若是客户端浏览器支持gzip压缩,则会在请求头的accept-encoding中携带gzip关键字,用node自带的zlib类就能够实现gzip压缩了,只要在stream.pip实多加一层,先导流到gzip流,再导出到res流,固然,还要在响应头中添加Content-Encoding为gzip,这样浏览器才能正确识别到http body是采用gzip算法压缩的,并进行自动解压缩。浏览器
代码以下:缓存
const zlib = require('zlib');
if (req.headers["accept-encoding"].indexOf("gzip")>=0 && (extName=="js" || extName=="css" || extName=="html"))) {
res.setHeader('Content-Encoding', "gzip");
const gzip = zlib.createGzip();
stream.pipe(gzip).pipe(res);
}
复制代码
http协议的缓存协商流程比较长,最终在响应头中生成expire(绝对时间)和cache-control(相对时间)两个用于控制缓存过时时间的参数,浏览器下次请求该文件时,分为如下几种状况:
逻辑分支较多,但都是日期比对,搞清楚缓存协商过程比较容易写出来,有兴趣的同窗能够自行实现
若是要作一个高性能的静态文件服务器仅实现gzip和缓存协商是不够的,涉及到本地文件的频繁读取,高并发下I/O一定成为瓶颈,考虑到服务器上的文件是不多更新的, 能够用Buffer把文件流缓存到内存中,每次请求时先在内存中查找匹配项,若是命中了直接从内存中返回,避免了读取磁盘,gzip也不用压缩了,直接用压缩好的文件流返回,能够成倍的大幅提高性能。固然若是文件太多了,内存也会飙升,须要考虑淘汰算法,只缓存访问次数高的文件,剔除低访问量的文件。
采用fs.watch监控目录文件的变化,若是文件有更新,则删掉缓存。
Node.js 内置的pipe方法能够很是简便的实现将服务器本地文件输出到http 响应流中,gzip压缩也一样能够经过pipe实现,再配合输出mine type 实现的静态服务器已经能够知足通常业务的使用。若是要实现高性能的静态文件服务器,还须要实现客户端缓存、服务端缓存功能(本文提供了思路,按图索骥也非难事)。
最后,推荐一下我的的开源项目, node.js web开发框架,已包含本文静态文件服务器的功能 webcontext: github.com/windyfancy/…