Node.js静态服务器的实现方法


Posted in Javascript onFebruary 28, 2018

当你输入一个url时,这个url可能对应服务器上的一个资源(文件)也可能对应一个目录。 So服务器会对这个url进行分析,针对不同的情况做不同的事。 如果这个url对应的是一个文件,那么服务器就会返回这个文件。 如果这个url对应的是一个文件夹,那么服务器会返回这个文件夹下包含的所有子文件/子文件夹的列表。 以上,就是一个静态服务器所主要干的事。

但真实的情况不会像这么简单, 我们所拿到的url可能是错误的,它所对应的文件或则文件夹或许根本不存在, 又或则有些文件和文件夹是被系统保护起来的是隐藏的,我们并不想让客户端知道。 因此,我们就要针对这些特殊情况进行一些不同的返回和提示。

再者,当我们真正返回一个文件前,我们需要和客户端进行一些协商。 我们需要知道客户端能够接受的语言类型、编码方式等等以便针对不同浏览器进行不同的返回处理。 我们需要告诉客户端一些关于返回文件的额外信息,以便客户端能更好的接收数据: 文件是否需要缓存,该怎样缓存? 文件是否进行了压缩处理,该以怎样的方式解压? 等等...

至此,我们已经初步了解了一个静态服务器所主要做的几乎所有事情, let's go!

实现

项目目录

static-server/
|
| - bin/
| | - start # 批处理文件
|  
|
| - src/
| | - App.js # main文件
| | - Config.js # 默认配置
|
|
·- package.json

配置文件

要启动一个服务器,我们需要知道这个服务器的启动时的端口号和静态服务器的工作目录

let config = {
 host:'localhost' //提升用
 ,port:8080 //服务器启动时候的默认端口号
 ,path:path.resolve(__dirname,'..','test-dir') //静态服务器启动时默认的工作目录
}

整体框架

注意

事件函数中的this默认指向绑定的对象(这里是小server),这里修改成了Server这个大对象,以便调用在回调函数中调用Server下的方法。

class Server(){
 constructor(options){
  /* === 合并配置参数 === */
  this.config = Object.assign({},config,options)
 }
 start(){
  /* === 启动http服务 === */
  let server = http.createServer();
  server.on('request',this.request.bind(this)); 
  server.listen(this.config.port,()=>{
   let url = `${this.config.host}:${this.config.port}`;
   console.log(`server started at ${chalk.green(url)}`)
  })
 }
 async request(req,res){
  /* === 处理客户端请求,决定响应信息 === */
  // try
  //如果是文件夹 -> 显示子文件、文件夹列表
  //如果是文件 -> sendFile()
  // catch
  //出错 -> sendError()
 }
 sendFile(){
  //对要返回的文件进行预处理并发送文件
 }
 handleCache(){
  //获取和设置缓存相关信息
 }
 getEncoding(){
  //获取和设置编码相关信息
 }
 getStream(){
  //获取和设置分块传输相关信息
 }
 sendError(){
  //错误提示
 }
}
module.exports = Server;

request请求处理

获取url的 pathname ,和 服务器本地的工作根目录地址 进行拼接,返回一个 filename 利用filename和 stat方法 检测是文件还是文件夹

是文件夹, 利用 readdir方法 返回该文件夹下的列表,将列表包装成一个对象组成的数组 然后结合handlebar将数组数据编译到模板中,最后返回这个模板给客户端

是文件, 将req、res、statObj、filepath传递给 sendFile ,接下来交由sendFile处理

async request(req,res){
 let pathname = url.parse(req.url);
 if(pathname == '/favicon.ico') return;
 let filepath = path.join(this.config.root,pathname);
 try{
  let statObj = await stat(filepath);
  if(statObj.isDirectory()){
   let files = awaity readdir(filepath);
   files.map(file=>{
    name:file
    ,path:path.join(pathname,file)
   });
   // 让handlebar 拿着数去编译模板
   let html = this.list({
    title:pathname
    ,files
   })
   res.setHeader('Content-Type','text/html');
   res.end(html);
  }else{
   this.sendFile(req,res,filepath,statObj);
  }
 }catch(e){
  this.sendError(e,req,res);
 }
}

[tip] 我们将 request 方法 async 化,这样我们就能像写同步代码一样写异步

方法

sendFile

涉及缓存、编码、分段传输等功能

sendFile(){
 if(this.handleCache(req,res,filepath,statObj)) return; //如果走缓存,则直接返回。
 res.setHeader('Content-type',mime.getType(filepath)+';charset=utf-8');
 let encoding = this.getEncoding(req,res); //获取浏览器能接收的编码并选择一种
 let rs = this.getStream(req,res,filepath,statObj); //支持断点续传
 if(encoding){
  rs.pipe(encoding).pipe(res);
 }else{
  rs.pipe(res);
 }
}

handleCache

缓存处理时要注意的是,缓存分为强制缓存和对比缓存,且强制缓存的优先级是高于相对缓存的。 也就是说,当强制缓存生效的时候并不会走相对缓存,不会像服务器发起请求。 但一旦强制缓存失效,就会走相对缓存,如果 文件标识 没有改变,则相对缓存生效, 客户端仍然会去缓存数据拿取数据,所以强制缓存和相对缓存并不冲突。 强制缓存和相对缓存一起使用时,能在减少服务器的压力的同事又保持请求数据的及时更新。

另外需要注意的是,如果同时设置了两种相对缓存的文件标识,必须要两种都没有改变时,缓存才生效。

handleCache(req,res,filepath,statObj){
 let ifModifiedSince = req.headers['if-modified-since']; //第一次请求是不会有的
 let isNoneMatch = req.headers['is-none-match'];
 res.setHeader('Cache-Control','private,max-age=30');
 res.setHeader('Expires',new Date(Date.now()+30*1000).toGMTString()); //此时间必须为GMT
 
 let etag = statObj.size;
 let lastModified = statObj.ctime.toGMTString(); //此时间格式可配置
 res.setHeader('Etag',etag);
 res.setHeader('Last-Modified',lastModified);
 
 if(isNoneMatch && isNoneMatch != etag) return false; //若是第一次请求已经返回false
 if(ifModifiedSince && ifModifiedSince != lastModified) return false;
 if(isNoneMatch || ifModifiedSince){
 // 说明设置了isNoneMatch或则isModifiedSince且文件没有改变
  res.writeHead(304);
  res.end();
  return true;
 }esle{
  return false;
 }
}

getEncoding

从请求头中拿取到浏览器能接收的编码类型,利用正则匹配匹配出最前面那个, 创建出对应的zlib实例返回给sendFile方法,以便在返回文件时进行编码。

getEncoding(req,res){
 let acceptEncoding = req.headers['accept-encoding'];
 if(/\bgzip\b/.test(acceptEncoding)){
  res.setHeader('Content-Encoding','gzip');
  return zlib.createGzip();
 }else if(/\bdeflate\b/.test(acceptEncoding)){
  res.setHeader('Content-Encoding','deflate');
  return zlib.createDeflate();
 }else{
  return null;
 }
}

getStream

分段传输,主要利用的是请求头中的 req.headers['range'] 来确认要接收的文件是从哪里开始到哪里结束,然而真正拿到这部分数据是通过 fs.createReadStream 来读取到的。

getStream(req,res,filepath,statObj){
 let start = 0;
 let end = startObj.size - 1;
 let range = req.headers['range'];
 if(range){
  res.setHeader('Accept-Range','bytes');
  res.statusCode = 206; //返回整个数据的一块
  let result = range.match(/bytes = (\d*)-(\d*)/); //不可能有小数,网络传输的最小单位为一个字节
  if(result){
   start = isNaN(result[1])?0:parseInt(result[1]);
   end = isNaN(result[2])?end:parseInt(result[2])-1; //因为readstream的索引是包前又包后故要减去1
  }
 }
 return fs.createReadStream(filepath,{
  start,end
 });
}

包装成命令行工具

我们可以像在命令行中输入 npm start 启动一个dev-server一样自定义一个启动命令来启动我们的静态服务器。

大体实现的思路是: 在 packge.json 中的 bin 属性下配置一个启动命令和这个执行这个命令的文件的路径。 然后我们需要准备一个批处理文件,在文件中引入我们的静态服务器文件,让我们的服务器跑起来 然后将这个文件 node link 即可。

总结

以上所述是小编给大家介绍的Node.js静态服务器的实现方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
Mootools 1.2教程 定时器和哈希简介
Sep 15 Javascript
jQuery获取样式中的背景颜色属性值/颜色值
Dec 17 Javascript
JS实现一键回顶功能示例代码
Oct 28 Javascript
js判断IE浏览器版本过低示例代码
Nov 22 Javascript
eclipse导入jquery包后报错的解决方法
Feb 17 Javascript
js验证框架之RealyEasy验证详解
Jun 08 Javascript
bootstrap实现的自适应页面简单应用示例
Mar 09 Javascript
JavaScript 对引擎、运行时、调用堆栈的概述理解
Oct 22 Javascript
jQuery选择器选中最后一个元素,倒数第二个元素操作示例
Dec 10 jQuery
Vue.js实现的购物车功能详解
Jan 27 Javascript
JS实现的碰撞检测与周期移动完整示例
Sep 02 Javascript
Vue中使用Echarts仪表盘展示实时数据的实现
Nov 01 Javascript
JS脚本加载后执行相应回调函数的操作方法
Feb 28 #Javascript
vue+webpack 打包文件 404 页面空白的解决方法
Feb 28 #Javascript
webpack项目调试以及独立打包配置文件的方法
Feb 28 #Javascript
vue-cli+webpack项目 修改项目名称的方法
Feb 28 #Javascript
vue 组件 全局注册和局部注册的实现
Feb 28 #Javascript
vue 自定义全局方法,在组件里面的使用介绍
Feb 28 #Javascript
Vue2.0子同级组件之间数据交互方法
Feb 28 #Javascript
You might like
PHP生成静态页面详解
2006/12/05 PHP
详解Yii2.0使用AR联表查询实例
2017/06/16 PHP
jQuery EasyUI API 中文文档 - PropertyGrid属性表格
2011/11/18 Javascript
Jquery Mobile 自定义按钮图标
2015/11/18 Javascript
深入理解Angular2 模板语法
2016/08/07 Javascript
Node.js connect ECONNREFUSED错误解决办法
2016/09/15 Javascript
jQuery中复合选择器简单用法示例
2018/03/31 jQuery
vue mint-ui tabbar变组件使用
2018/05/04 Javascript
Vue.js特性Scoped Slots的浅析
2019/02/20 Javascript
Electron vue的使用教程图文详解
2019/07/05 Javascript
vue中通过使用$attrs实现组件之间的数据传递功能
2019/09/01 Javascript
layui扩展上传组件模拟进度条的方法
2019/09/23 Javascript
小程序按钮避免多次调用接口和点击方案实现(不用showLoading)
2020/04/15 Javascript
python实现感知器
2017/12/19 Python
python链接oracle数据库以及数据库的增删改查实例
2018/01/30 Python
python进行两个表格对比的方法
2018/06/27 Python
python实现网页自动签到功能
2019/01/21 Python
python3对拉勾数据进行可视化分析的方法详解
2019/04/03 Python
Python多线程:主线程等待所有子线程结束代码
2020/04/25 Python
解决windows上安装tensorflow时报错,“DLL load failed: 找不到指定的模块”的问题
2020/05/20 Python
解决tensorflow读取本地MNITS_data失败的原因
2020/06/22 Python
css3实现动画的三种方式
2020/08/24 HTML / CSS
Yves Rocher伊夫·黎雪美国官网:法国始创植物美肌1959
2019/01/09 全球购物
日本动漫周边服饰销售网站:Atsuko
2019/12/16 全球购物
霸王洗发水广告词
2014/03/14 职场文书
教师民族团结演讲稿
2014/08/27 职场文书
2014年安全管理工作总结
2014/12/01 职场文书
毕业设计论文评语
2014/12/31 职场文书
办公室主任个人总结
2015/02/28 职场文书
出纳试用期自我评价
2015/03/10 职场文书
2016新年慰问信范文
2015/03/25 职场文书
幼儿园秋季开学通知
2015/07/16 职场文书
《猴王出世》教学反思
2016/02/23 职场文书
2019年感恩励志演讲稿(收藏备用)
2019/09/11 职场文书
详解CocosCreator项目结构机制
2021/04/14 Javascript
python 离散点图画法的实现
2022/04/01 Python