实战node静态文件服务器的示例代码
本篇文章主要介绍了实战node静态文件服务器的示例,分享给大家,具体如下:
支持功能:
- 读取静态文件
- 访问目录可以自动寻找下面的index.html文件, 如果没有index.html则列出文件列表
- mime类型支持
- 缓存支持/控制
- 支持gzip压缩
- range支持,断点续传
- 全局命令执行
- 子进程运行
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类型支持
利用模块得到文件类型,并设置编码:
res.setheader('content-type', mime.gettype(filepath) + ';charset=utf-8');
3.缓存支持
http协议缓存:
cache-control: http1.1内容,告诉客户端如何缓存数据,以及规则
- private 客户端可以缓存
- public 客户端和代理服务器都可以缓存
- max-age=60 缓存内容将在60秒后失效
- no-cache 需要使用对比缓存验证数据,强制向源服务器再次验证
- 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实现
- 为npm包目录创建软链接,将其链到{prefix}/lib/node_modules/
- 为可执行文件(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.源码及测试
源码地址:
npm install hope-server -g
进入任意目录
hope-server
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: CSS3选择器新增问题的实现