实战node静态文件服务器的示例代码


Posted in Javascript onMarch 08, 2018

本篇文章主要介绍了实战node静态文件服务器的示例,分享给大家,具体如下:

支持功能:

  1. 读取静态文件
  2. 访问目录可以自动寻找下面的index.html文件, 如果没有index.html则列出文件列表
  3. MIME类型支持
  4. 缓存支持/控制
  5. 支持gzip压缩
  6. Range支持,断点续传
  7. 全局命令执行
  8. 子进程运行

1. 创建服务读取静态文件

首先引入http模块,创建一个服务器,并监听配置端口:

const http = require('http');
 
 const server = http.createServer();
 
 // 监听请求
 server.on('request', request.bind(this));
 
 server.listen(config.port, () => {
  console.log(`静态文件服务启动成功, 访问localhost:${config.port}`);
 });

写一个fn专门处理请求, 返回静态文件, url模块获取路径:

const url = require('url');
 const fs = require('fs');
 function request(req, res) {
 const { pathname } = url.parse(req.url); // 访问路径
 
 const filepath = path.join(config.root, pathname); // 文件路径
 
 fs.createReadStream(filepath).pipe(res); // 读取文件,并响应
 }

支持寻找index.html:

if (pathname === '/') {
  const rootPath = path.join(config.root, 'index.html');
  try{
   const indexStat = fs.statSync(rootPath);
   if (indexStat) {
    filepath = rootPath;
   }
  } catch(e) {
   
  }
 }

访问目录时,列出文件目录:

fs.stat(filepath, (err, stats) => {
 if (err) {
  res.end('not found');
  return;
 }
 if (stats.isDirectory()) {
  let files = fs.readdirSync(filepath);
  files = files.map(file => ({
   name: file,
   url: path.join(pathname, file)
  }));
  let html = this.list()({
   title: pathname,
   files
  });
  res.setHeader('Content-Type', 'text/html');
  res.end(html);
 }
 }

html模板:

function list() {
  let tmpl = fs.readFileSync(path.resolve(__dirname, 'template', 'list.html'), 'utf8');
  return handlebars.compile(tmpl);
 }
<!DOCTYPE html>
 <html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>{{title}}</title>
 </head>
 <body>
 <h1>hope-server静态文件服务器</h1>
 <ul>
  {{#each files}}
  <li>
   <a href={{url}}>{{name}}</a>
  </li>
  {{/each}}
 </ul>
 </body>
 </html>

2.MIME类型支持

利用mime模块得到文件类型,并设置编码:

res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');

3.缓存支持

http协议缓存:

Cache-Control: http1.1内容,告诉客户端如何缓存数据,以及规则

  1. private 客户端可以缓存
  2. public 客户端和代理服务器都可以缓存
  3. max-age=60 缓存内容将在60秒后失效
  4. no-cache 需要使用对比缓存验证数据,强制向源服务器再次验证
  5. no-store 所有内容都不会缓存,强制缓存和对比缓存都不会触发

Expires: http1.0内容,cache-control会覆盖,告诉客户端缓存什么时候过期

ETag: 内容的hash值 下一次客户端请求在请求头里添加if-none-match: etag值

Last-Modified: 最后的修改时间 下一次客户端请求在请求头里添加if-modified-since: Last-Modified值

handleCache(req, res, stats, hash) {
 // 当资源过期时, 客户端发现上一次请求资源,服务器有发送Last-Modified, 则再次请求时带上if-modified-since
 const ifModifiedSince = req.headers['if-modified-since'];
 // 服务器发送了etag,客户端再次请求时用If-None-Match字段来询问是否过期
 const ifNoneMatch = req.headers['if-none-match'];
 // http1.1内容 max-age=30 为强行缓存30秒 30秒内再次请求则用缓存 private 仅客户端缓存,代理服务器不可缓存
 res.setHeader('Cache-Control', 'private,max-age=30');
 // http1.0内容 作用与Cache-Control一致 告诉客户端什么时间,资源过期 优先级低于Cache-Control
 res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString());
 // 设置ETag 根据内容生成的hash
 res.setHeader('ETag', hash);
 // 设置Last-Modified 文件最后修改时间
 const lastModified = stats.ctime.toGMTString();
 res.setHeader('Last-Modified', lastModified);
 
 // 判断ETag是否过期
 if (ifNoneMatch && ifNoneMatch != hash) {
  return false;
 }
 // 判断文件最后修改时间
 if (ifModifiedSince && ifModifiedSince != lastModified) {
  return false;
 }
 // 如果存在且相等,走缓存304
 if (ifNoneMatch || ifModifiedSince) {
  res.writeHead(304);
  res.end();
  return true;
 } else {
  return false;
 }
 }

4.压缩

客户端发送内容,通过请求头里Accept-Encoding: gzip, deflate告诉服务器支持哪些压缩格式,服务器根据支持的压缩格式,压缩内容。如服务器不支持,则不压缩。

getEncoding(req, res) {
  const acceptEncoding = req.headers['accept-encoding'];
  // gzip和deflate压缩
  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;
  }
 }

5.断点续传

服务器通过请求头中的Range: bytes=0-xxx来判断是否是做Range请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,表示Partial Content,并设置Content-Range。如果无效,则返回416状态码,表明Request Range Not Satisfiable。如果不包含Range的请求头,则继续通过常规的方式响应。

getStream(req, res, filepath, statObj) {
  let start = 0;
  let end = statObj.size - 1;
  const 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]) ? start : parseInt(result[1]);
    end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
   }
  }
  return fs.createReadStream(filepath, {
   start, end
  });
 }

6.全局命令执行

通过npm link实现

  1. 为npm包目录创建软链接,将其链到{prefix}/lib/node_modules/
  2. 为可执行文件(bin)创建软链接,将其链到{prefix}/bin/{name}

npm link命令通过链接目录和可执行文件,实现npm包命令的全局可执行。

package.json里面配置

{
 bin: {
 "hope-server": "bin/hope"
 }
 }

在项目下面创建bin目录 hope文件, 利用yargs配置命令行传参数

// 告诉电脑用node运行我的文件
 #! /usr/bin/env node
 
 const yargs = require('yargs');
 const init = require('../src/index.js');
 const argv = yargs.option('d', {
 alias: 'root',
 demand: 'false',
 type: 'string',
 default: process.cwd(),
 description: '静态文件根目录'
 }).option('o', {
 alias: 'host',
 demand: 'false',
 default: 'localhost',
 type: 'string',
 description: '配置监听的主机'
 }).option('p', {
 alias: 'port',
 demand: 'false',
 type: 'number',
 default: 8080,
 description: '配置端口号'
 }).option('c', {
 alias: 'child',
 demand: 'false',
 type: 'boolean',
 default: false,
 description: '是否子进程运行'
 })
 .usage('hope-server [options]')
 .example(
 'hope-server -d / -p 9090 -o localhost', '在本机的9090端口上监听客户端的请求'
 ).help('h').argv;
 
 // 启动服务
 init(argv);

7.子进程运行

通过spawn实现

index.js

const { spawn } = require('child_process');
 const Server = require('./hope');
 
 function init(argv) {
  // 如果配置为子进程开启服务
  if (argv.child) {
   //子进程启动服务
   const child = spawn('node', ['hope.js', JSON.stringify(argv)], {
    cwd: __dirname,
    detached: true,
    stdio: 'inherit'
   });
 
   //后台运行
   child.unref();
   //退出主线程,让子线程单独运行
   process.exit(0);
  } else {
   const server = new Server(argv);
   server.start();
  }
 }
 
 module.exports = init;
hope.js
 if (process.argv[2] && process.argv[2].startsWith('{')) {
 const argv = JSON.parse(process.argv[2]);
 const server = new Hope(argv);
 server.start();
 }

8.源码及测试

源码地址: hope-server

npm install hope-server -g

进入任意目录

hope-server

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

Javascript 相关文章推荐
实例:用 JavaScript 来操作字符串(一些字符串函数)
Feb 15 Javascript
javascript数组快速打乱重排的方法
Jan 02 Javascript
引入autocomplete组件时JS报未结束字符串常量错误
Mar 19 Javascript
JavaScript实现判断图片是否加载完成的3种方法整理
Mar 13 Javascript
jQuery插件expander实现图片翻转特效
May 21 Javascript
JavaScript和JQuery的鼠标mouse事件冒泡处理
Jun 19 Javascript
javascript简单判断输入内容是否合法的方法
May 11 Javascript
微信小程序商城项目之侧栏分类效果(1)
Apr 17 Javascript
JS将unicode码转中文方法
May 08 Javascript
详解在React项目中安装并使用Less(用法总结)
Mar 18 Javascript
vue-i18n结合Element-ui的配置方法
May 20 Javascript
一起来了解一下JavaScript的预编译(小结)
Mar 01 Javascript
vue.js或js实现中文A-Z排序的方法
Mar 08 #Javascript
vue.js移动数组位置,同时更新视图的方法
Mar 08 #Javascript
在react-router4中进行代码拆分的方法(基于webpack)
Mar 08 #Javascript
JQuery选中select组件被选中的值方法
Mar 08 #jQuery
vue.js中$set与数组更新方法
Mar 08 #Javascript
vue与vue-i18n结合实现后台数据的多语言切换方法
Mar 08 #Javascript
详解使用vue-cli脚手架初始化Vue项目下的项目结构
Mar 08 #Javascript
You might like
让PHP开发者事半功倍的十大技巧小结
2010/04/20 PHP
php数组函数序列之array_values() 获取数组元素值的函数与方法
2011/10/30 PHP
利用PHP实现图片等比例放大和缩小的方法详解
2013/06/06 PHP
使用php将某个目录下面的所有文件罗列出来的方法详解
2013/06/21 PHP
Yii中Model(模型)的创建及使用方法
2015/12/28 PHP
Yii框架表单模型和验证用法
2016/05/20 PHP
firefox下input type=&quot;file&quot;的size是多大
2011/10/24 Javascript
jquery如何实现锚点链接之间的平滑滚动
2013/12/02 Javascript
Javascript 拖拽的一些简单的应用(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
javascript表格隔行变色加鼠标移入移出及点击效果的方法
2015/04/10 Javascript
jQuery实现手机号码输入提示功能实例
2015/04/30 Javascript
浅谈JavaScript中setInterval和setTimeout的使用问题
2015/08/01 Javascript
详解前端自动化工具gulp自动添加版本号
2016/12/20 Javascript
微信小程序 图片宽度自适应的实现
2017/04/06 Javascript
MUI实现上拉加载和下拉刷新效果
2017/06/30 Javascript
ionic选择多张图片上传的示例代码
2017/10/10 Javascript
[00:35]DOTA2上海特级锦标赛 VP战队宣传片
2016/03/04 DOTA
学习python (1)
2006/10/31 Python
使用Python获取CPU、内存和硬盘等windowns系统信息的2个例子
2014/04/15 Python
简单介绍Ruby中的CGI编程
2015/04/10 Python
python实现在字符串中查找子字符串的方法
2015/07/11 Python
TensorFlow 合并/连接数组的方法
2018/07/27 Python
matplotlib实现区域颜色填充
2019/03/18 Python
解决python中用matplotlib画多幅图时出现图形部分重叠的问题
2019/07/07 Python
Python实现时间序列可视化的方法
2019/08/06 Python
flask框架渲染Jinja模板与传入模板变量操作详解
2020/01/25 Python
numpy库reshape用法详解
2020/04/19 Python
带你学习Python如何实现回归树模型
2020/07/16 Python
HTML5边玩边学(3)像素和颜色
2010/09/21 HTML / CSS
房屋改造计划书
2014/01/10 职场文书
暑期培训班策划方案
2014/08/26 职场文书
酒店采购员岗位职责
2015/04/03 职场文书
2015年信息宣传工作总结
2015/05/26 职场文书
公司晚宴祝酒词
2015/08/11 职场文书
CSS3新特性详解(五):多列columns column-count和flex布局
2021/04/30 HTML / CSS
css3属性选择器 “~”(波浪号) “,”(逗号) “+”(加号)和 “>”(大于号)
2022/04/19 HTML / CSS