Node.js一行代码实现静态文件服务器的方法步骤


Posted in Javascript onMay 07, 2019

静态文件服务器实现

nodejs不仅仅可以用来写服务端接口,用来做静态文件服务器替代nginx的功能, 也是分分钟可以搞定的。 话不多说,先上代码:

var server=http.createServer(function (req,res){
 fs.createReadStream(Path.resolve(__dirname,"."+req.url)).pipe(res);
})

在项目根目录建一个hello.html文件测试一下 hello.html内容如下:

<h1>hello,world</h1>

node app.js运行,打开浏览器访问一下: http://localhost/hello.html

Node.js一行代码实现静态文件服务器的方法步骤

我们再回头审视一下代码,的确就只有这么简单,这要归功于node Stream类 pipe方法的强大,fs.createReadStream读取本地文件创建一个可读流(ReadStream类的实例),再使用pipe导流到res响应流,res是一个http.ServerResponse类的实例,是一个可写流,继承自 Stream类

http.ServerResponse类的继承关系如下:

Node.js一行代码实现静态文件服务器的方法步骤

安全性考虑

上述代码实现静态文件服务器后,意味着项目根目录下所有的文件(递归)都可以通过浏览器直接访问和下载了,这样会带来一些安全性的问题,想想看,你的服务器端代码和配置文件都能通过浏览器直接下载了,因此需要在代码里加一些限制,例如只能访问特定的目录下的文件和特定扩展名的文件,这样还不够,参考OWasp Top 10安全风险(第4条-不安全的对象直接引用),攻击者仍然可以通过../../目录回溯的方法访问到其它目录,对于访问路径中包含..的也要全部过滤掉。

实现mine type

mime type是指http 响应头中的content-type字段,它决定了浏览器如何解析文件,是直接当做纯文件显示(text/plain),还是做为html文件渲染(text/html),或者当做二进制文件下载,没有输出正确的mine type,可能导致图片文件无法显示,字体文件无效,视频文件无法播放的问题。要实现起来也十分简单,只需要做一个映射表,不同文件扩展名,在响应头的content-type字段中输出对应的mine type就行了。

完整代码如下:

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);

实现gzip

对于文本类型的文件,如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(相对时间)两个用于控制缓存过期时间的参数,浏览器下次请求该文件时,分为以下几种情况:

  • 如果没到过期时间,浏览器不会请求文件直接读缓存
  • 如果已到过期时间,则会在请求头中last-modified字段携带文件的最后修改日期,如果对比时间戳与服务器文件一致,则HTTP 返回 304: Not Modified
  • 如果按下f5刷新,会在请求头中if-modified-since字段中携带缓存的过期时间,如果对比时间戳与服务器文件一致,则HTTP 返回 304: Not Modified
  • ctrl+f5刷新,请求头中携带 cache-control: no-cache,强制禁用缓存。重新下载文件

逻辑分支较多,但都是日期比对,搞清楚缓存协商过程比较容易写出来,有兴趣的同学可以自行实现

高性能静态文件服务器优化

如果要做一个高性能的静态文件服务器仅实现gzip和缓存协商是不够的,涉及到本地文件的频繁读取,高并发下I/O必定成为瓶颈,考虑到服务器上的文件是很少更新的, 可以用Buffer把文件流缓存到内存中,每次请求时先在内存中查找匹配项,如果命中了直接从内存中返回,避免了读取磁盘,gzip也不用压缩了,直接用压缩好的文件流返回,可以成倍的大幅提升性能。当然如果文件太多了,内存也会飙升,需要考虑淘汰算法,只缓存访问次数高的文件,剔除低访问量的文件。

采用fs.watch监控目录文件的变化,如果文件有更新,则删掉缓存。

小结

Node.js 内置的pipe方法可以非常简便的实现将服务器本地文件输出到http 响应流中,gzip压缩也同样可以通过pipe实现,再配合输出mine type 实现的静态服务器已经可以满足一般业务的使用。如果要实现高性能的静态文件服务器,还需要实现客户端缓存、服务端缓存功能(本文提供了思路,按图索骥也非难事)。

最后,推荐一下个人的开源项目, node.js web开发框架,已包含本文静态文件服务器的功能 webcontext: https://github.com/windyfancy/webcontext

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
不懂JavaScript应该怎样学
Apr 16 Javascript
ASP.NET中使用后端代码注册脚本 生成JQUERY-EASYUI的界面错位的解决方法
Jun 12 Javascript
aspx中利用js实现确认删除代码
Jul 22 Javascript
多种方法判断Javascript对象是否存在
Sep 22 Javascript
随鼠标移动的时钟非常漂亮遗憾的是只支持IE
Aug 12 Javascript
node.js中的fs.fstat方法使用说明
Dec 15 Javascript
js给selected添加options的方法
May 06 Javascript
基于BootStrap Metronic开发框架经验小结【八】框架功能总体界面介绍
May 12 Javascript
在js代码拼接dom对象到页面上去的模板总结(必看)
Feb 14 Javascript
jQuery实现的form转json经典示例
Oct 10 jQuery
node.js学习笔记之koa框架和简单爬虫练习
Dec 13 Javascript
微信小程序实现可长按移动控件
Nov 01 Javascript
微信小程序扫描二维码获取信息实例详解
May 07 #Javascript
Vue数据绑定简析小结
May 07 #Javascript
javascript实现对话框功能警告(alert 消息对话框)确认(confirm 消息对话框)
May 07 #Javascript
详解Vue、element-ui、axios实现省市区三级联动
May 07 #Javascript
webpack结合express实现自动刷新的方法
May 07 #Javascript
记录一次开发微信网页分享的步骤
May 07 #Javascript
Vue 幸运大转盘实现思路详解
May 06 #Javascript
You might like
thinkPHP导出csv文件及用表格输出excel的方法
2015/12/30 PHP
浅谈PHPANALYSIS提取关键字
2019/03/08 PHP
PHP面向对象类型约束用法分析
2019/06/12 PHP
TP5框架实现自定义分页样式的方法示例
2020/04/05 PHP
jquery select(列表)的操作(取值/赋值)
2009/08/06 Javascript
JQuery.uploadify 上传文件插件的使用详解 for ASP.NET
2010/01/22 Javascript
JavaScript DOM 学习第三章 内容表格
2010/02/19 Javascript
jQuery ajax在GBK编码下表单提交终极解决方案(非二次编码方法)
2010/10/20 Javascript
详解jquery事件delegate()的使用方法
2016/01/25 Javascript
JavaScript重载函数实例剖析
2016/05/13 Javascript
浅谈js图片前端预览之filereader和window.URL.createObjectURL
2016/06/30 Javascript
利用node.js爬取指定排名网站的JS引用库详解
2017/07/25 Javascript
JavaScript数据结构与算法之检索算法示例【二分查找法、计算重复次数】
2019/02/22 Javascript
解决node.js含有%百分号时发送get请求时浏览器地址自动编码的问题
2019/11/20 Javascript
[51:11]2014 DOTA2国际邀请赛中国区预选赛5.21 LGD-CDEC VS DT
2014/05/22 DOTA
[01:01:24]DOTA2上海特级锦标赛A组败者赛 EHOME VS CDEC第三局
2016/02/25 DOTA
[00:48]完美“圣”典2016风云人物:xiao8宣传片
2016/11/30 DOTA
python实现apahce网站日志分析示例
2014/04/02 Python
python中的代码编码格式转换问题
2015/06/10 Python
Python正则表达式经典入门教程
2017/05/22 Python
python中的break、continue、exit()、pass全面解析
2017/08/05 Python
python装饰器代替set get方法实例
2019/12/19 Python
TensorFlow实现保存训练模型为pd文件并恢复
2020/02/06 Python
Python 程序员必须掌握的日志记录
2020/08/17 Python
python 动态渲染 mysql 配置文件的示例
2020/11/20 Python
Pycharm-community-2020.2.3 社区版安装教程图文详解
2020/12/08 Python
Python实现小黑屋游戏的完整实例
2021/01/06 Python
利用CSS3实现毛玻璃效果示例源码
2016/09/25 HTML / CSS
CSS3样式linear-gradient的使用实例
2017/01/16 HTML / CSS
如何用Python输出一个Fibonacci数列
2016/08/28 面试题
高考备战决心书
2014/03/11 职场文书
试用期转正后的自我评价
2014/09/21 职场文书
护理医院见习报告
2014/11/03 职场文书
2015年中秋晚会主持稿
2015/07/30 职场文书
红灯733-1型14管5波段半导体收音机
2021/04/22 无线电
VUE使用draggable实现组件拖拽
2022/04/06 Vue.js