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

当你输入一个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静态服务器的实现方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

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

    当你输入一个url时,这个url可能对应服务器上的一个资源(文件)也可能对应一个目录. So服务器会对这个url进行分析,针对不同的情况做不同的事. 如果这个url对应的是一个文件,那么服务器就会返回这个文件. 如果这个url对应的是一个文件夹,那么服务器会返回这个文件夹下包含的所有子文件/子文件夹的列表. 以上,就是一个静态服务器所主要干的事. 但真实的情况不会像这么简单, 我们所拿到的url可能是错误的,它所对应的文件或则文件夹或许根本不存在, 又或则有些文件和文件夹是被系统保护起来的是隐藏

  • 使用node.js搭建服务器

    使用node搭建小型服务器(其实就是分析url然后输出文件给客户端) 最近需要完成一个课程设计,被项目经理(组长)分配写界面,但是总觉得只写前端的话缺了点什么,所以想自己写下后端玩一下. 期间还稍微纠结了一下用什么语言,本来打算正好学习一下PHP,可后来转念一想,用nodejs岂不美哉,不仅了解了后台开发,也相当于巩固了js基础,一举两得,美滋滋. 在学习node的过程中,学到了使用node实现一个服务器这一块,感觉是对前面所学模块的一个很好的总结.用到了四个基本的模块fs stream htt

  • Node.js中的require.resolve方法使用简介

    前言 网上关于NodeJs的论述很多,此处不多说.个人认为,NodeJs的编程思想和客户端Javascript保持了一种理念,没有什么变化,只是增加了"require()"函数,因此只要学好require函数,剩下的问题就是如何更好的使用API了.本文则主要介绍了Node.js中的require.resolve方法,下面来看看详细介绍吧. 简单的说,在 Node.js 中使用 fs 读取文件的时候,经常碰到要拼一个文件的绝对路径的问题 (fs 处理相对路径均以进程执行目录为准). 之前

  • Node.js中流(stream)的使用方法示例

    前言 本文主要给大家介绍了关于Node.js 流(stream)的使用方法,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 流是基于事件的API,用于管理和处理数据,而且有不错的效率.借助事件和非阻塞I/O库,流模块允许在其可用的时候动态处理,在其不需要的时候释放掉. 使用流的好处 举一个读取文件的例子: 使用fs.readFileSync同步读取一个文件,程序会被阻塞,所有的数据都会被读取到内存中. 换用fs.readFile读取文件,程序不会被阻塞,但是所有的数据依旧会被一次性

  • 使用JavaScript实现node.js中的path.join方法

    Node.JS中的 path.join 非常方便,能直接按相对或绝对合并路径,使用: path.join([path1], [path2], [...]),有时侯前端也需要这种方法,如何实现呢? 其实直接从 node.js 的 path.js 拿到源码加工一下就可以了: 1. 将 const 等 es6 属性改为 var,以便前端浏览器兼容 2. 添加一个判断路戏分隔符的变量 sep,即左斜杠还是右斜杠,以第一个路戏分隔符为准 3. 将引用的变量和函数放到一个文件里就可以了: Path 的源码:

  • node.js Promise对象的使用方法实例分析

    本文实例讲述了node.js Promise对象的使用方法.分享给大家供大家参考,具体如下: Promise对象是干嘛用的? 将异步操作以同步操作的流程表达出来 一.Promise对象的定义 let flag = true; const hello = new Promise(function (resolve, reject) { if (false) {//异步操作成功 resolve("success"); } else { reject("error");

  • Node.js HTTP服务器中的文件、图片上传的方法

    HTTP协议中,multipart/form-data格式用于向服务器发送二进制数据,通过这一内容类型(Content-Type)可以实现文件.图片的上传.由于这种格式发送的是二进制数据,在服务器端接收和处理数据时会与其它内容类型有所有区别. HTTP协议中的文件上传 最早的HTTP协议中是不支持文件上传的,在1995年制定的rfc1867规范中,在HTTP POST请求的内容类型Content-Type中扩展了multipart/form-data类型,该类型用于向服务器发送二进制数据,以便支

  • 充分发挥Node.js程序性能的一些方法介绍

    一个Node.JS 的进程只会运行在单个的物理核心上,就是因为这一点,在开发可扩展的服务器的时候就需要格外的注意. 因为有一系列稳定的API,加上原生扩展的开发来管理进程,所以有很多不同的方法来设计一个可以并行的Node.JS运用.在这篇博文里,我们就来比较下这些可能的架构. 这篇文章同时也介绍compute-cluster 模块:一个小型的Node.JS库,可以用来很方便的管理进程,从来二线分布式计算. 遇到的问题 我们在Mozilla Persona的项目中需要可以处理大量不同特征的请求,所

  • node.js中的http.createServer方法使用说明

    方法说明: 该函数用来创建一个HTTP服务器,并将 requestListener 作为 request 事件的监听函数. 语法: 复制代码 代码如下: http.createServer([requestListener]) 由于该方法属于http模块,使用前需要引入http模块(var http= require("http") ) 接收参数: requestListener   请求处理函数,自动添加到 request 事件,函数传递两个参数: req  请求对象,想知道req有

  • 利用node.js制作命令行工具方法教程(一)

    前言 之前使用过一些全局安装的NPM包,安装完之后,可以通过其提供的命令,完成一些任务.比如Fis3,可以通过fis3 server start 开启fis的静态文件服务,通过fis3 release开启文件编译与发布:还有vue-cli,可以通过vue init webpack my-project来初始化vue+webpack的项目基础配置.最近有一个需求,需要写一个类似vue-cli的NPM包,通过命令行操作实现项目初始配置,所以就查看了相关资料,学习了一下如何使用node来生成自己的命令

随机推荐