实战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 相关文章推荐
jQuery ui1.7 dialog只能弹出一次问题
Aug 27 Javascript
js png图片(有含有透明)在IE6中为什么不透明了
Feb 07 Javascript
仿微博字符限制效果实现代码
Apr 20 Javascript
从数据结构分析看:用for each...in 比 for...in 要快些
Apr 17 Javascript
利用js正则表达式验证手机号,email地址,邮政编码
Jan 23 Javascript
JS实现让访问者自助选择网页文字颜色的方法
Feb 24 Javascript
JavaScript数组前面插入元素的方法
Apr 06 Javascript
js调用百度地图及调用百度地图的搜索功能
Sep 07 Javascript
AngularJs自定义服务之实现签名和加密
Aug 02 Javascript
vue组件中使用iframe元素的示例代码
Dec 13 Javascript
Vue插槽原理与用法详解
Mar 05 Javascript
Layui数据表格之单元格编辑方式
Oct 26 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 日期时间处理函数小结
2009/12/18 PHP
提高PHP编程效率 引入缓存机制提升性能
2010/02/15 PHP
浅析关于PHP位运算的简单权限设计
2013/06/30 PHP
PHP判断远程图片或文件是否存在的实现代码
2014/02/20 PHP
ThinkPHP实现跨模块调用操作方法概述
2014/06/20 PHP
PHP之sprintf函数用法详解
2014/11/12 PHP
PHP的运行机制与原理(底层)
2015/11/16 PHP
详解PHP变量传值赋值和引用赋值变量销毁
2019/03/23 PHP
javascript 实用的文字链提示框效果
2010/06/30 Javascript
为JavaScript提供睡眠功能(sleep) 自编译JS引擎
2010/08/16 Javascript
详谈nodejs异步编程
2014/12/04 NodeJs
基于node.js的fs核心模块读写文件操作(实例讲解)
2017/09/10 Javascript
JS中min函数实例讲解
2019/02/18 Javascript
解决vue的过渡动画无法正常实现问题
2019/10/31 Javascript
vue实现微信浏览器左上角返回按钮拦截功能
2020/01/18 Javascript
JsonServer安装及启动过程图解
2020/02/28 Javascript
vue中的使用token的方法示例
2020/03/10 Javascript
Django实现简单分页功能的方法详解
2017/12/05 Python
python如何制作缩略图
2019/04/30 Python
Python可迭代对象操作示例
2019/05/07 Python
python获取当前文件路径以及父文件路径的方法
2019/07/10 Python
python进程的状态、创建及使用方法详解
2019/12/06 Python
快速解决Django关闭Debug模式无法加载media图片与static静态文件
2020/04/07 Python
python 实现简易的记事本
2020/11/30 Python
CSS3移动端vw+rem不依赖JS实现响应式布局的方法
2019/01/23 HTML / CSS
泰国办公用品购物网站:OfficeMate
2018/02/04 全球购物
eBay比利时购物网站:eBay.be
2019/08/09 全球购物
函授自我鉴定
2013/11/06 职场文书
广告业务员岗位职责
2014/02/06 职场文书
需求分析说明书
2014/05/09 职场文书
男生贾里读书笔记
2015/06/30 职场文书
2016年基层党组织创先争优承诺书
2016/03/25 职场文书
2019年度开业庆典祝福语大全!
2019/07/05 职场文书
创业计划书之校园跑腿公司
2019/09/24 职场文书
关于SpringBoot 使用 Redis 分布式锁解决并发问题
2021/11/17 Redis
详细介绍MySQL中limit和offset的用法
2022/05/06 MySQL