内容简介:使用node搭建一个可在任何目录下通过命令启动的一个简单http静态服务器可通过以上命令安装,启动,来看一下最终的效果在写具体业务前,先封装几个处理响应的函数,分别是错误的响应处理,没有找到资源的响应处理,在后面会调用这么几个函数来做响应
静态服务器
使用node搭建一个可在任何目录下通过命令启动的一个简单http静态服务器
可通过以上命令安装,启动,来看一下最终的效果
TODO
- 创建一个静态服务器
- 通过yargs来创建命令行工具
- 处理缓存
- 处理压缩
初始化
- 创建目录:
mkdir static-server
- 进入到该目录:
cd static-server
- 初始化项目:
npm init
- 构建文件夹目录结构:
初始化静态服务器
- 首先在src目录下创建一个app.js
- 引入所有需要的包,非node自带的需要npm安装一下
-
初始化构造函数,options参数由命令行传入,后续会讲到
- this.host 主机名
- this.port 端口号
- this.rootPath 根目录
- this.cors 是否开启跨域
- this.openbrowser 是否自动打开浏览器
const http = require('http'); // http模块 const url = require('url'); // 解析路径 const path = require('path'); // path模块 const fs = require('fs'); // 文件处理模块 const mime = require('mime'); // 解析文件类型 const crypto = require('crypto'); // 加密模块 const zlib = require('zlib'); // 压缩 const openbrowser = require('open'); //自动启动浏览器 const handlebars = require('handlebars'); // 模版 const templates = require('./templates'); // 用来渲染的模版文件 class StaticServer { constructor(options) { this.host = options.host; this.port = options.port; this.rootPath = process.cwd(); this.cors = options.cors; this.openbrowser = options.openbrowser; } }
处理错误响应
在写具体业务前,先封装几个处理响应的函数,分别是错误的响应处理,没有找到资源的响应处理,在后面会调用这么几个函数来做响应
- 处理错误
- 返回状态码500
- 返回错误信息
responseError(req, res, err) { res.writeHead(500); res.end(`there is something wrong in th server! please try later!`); }
- 处理资源未找到的响应
- 返回状态码404
- 返回一个404html
responseNotFound(req, res) { // 这里是用handlerbar处理了一个模版并返回,这个模版只是单纯的一个写着404html const html = handlebars.compile(templates.notFound)(); res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(html); }
处理缓存
在前面的一篇文章里我介绍过node处理缓存的几种方式,这里为了方便我只使用的协商缓存,通过ETag来做验证
cacheHandler(req, res, filepath) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filepath); const md5 = crypto.createHash('md5'); const ifNoneMatch = req.headers['if-none-match']; readStream.on('data', data => { md5.update(data); }); readStream.on('end', () => { let etag = md5.digest('hex'); if (ifNoneMatch === etag) { resolve(true); } resolve(etag); }); readStream.on('error', err => { reject(err); }); }); }
处理压缩
- 通过请求头accept-encoding来判断浏览器支持的压缩方式
- 设置压缩响应头,并创建对文件的压缩方式
compressHandler(req, res) { const 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 false; } }
启动静态服务器
- 添加一个启动服务器的方法
- 所有请求都交给this.requestHandler这个函数来处理
- 监听端口号
start() { const server = http.createSercer((req, res) => this.requestHandler(req, res)); server.listen(this.port, () => { if (this.openbrowser) { openbrowser(`http://${this.host}:${this.port}`); } console.log(`server started in http://${this.host}:${this.port}`); }); }
请求处理
- 通过url模块解析请求路径,获取请求资源名
- 获取请求的文件路径
-
通过fs模块判断文件是否存在,这里分三种情况
- 请求路径是一个文件夹,则调用responseDirectory处理
- 请求路径是一个文件,则调用responseFile处理
- 如果请求的文件不存在,则调用responseNotFound处理
requestHandler(req, res) { // 通过url模块解析请求路径,获取请求文件 const { pathname } = url.parse(req.url); // 获取请求的文件路径 const filepath = path.join(this.rootPath, pathname); // 判断文件是否存在 fs.stat(filepath, (err, stat) => { if (!err) { if (stat.isDirectory()) { this.responseDirectory(req, res, filepath, pathname); } else { this.responseFile(req, res, filepath, stat); } } else { this.responseNotFound(req, res); } }); }
处理请求的文件
- 每次返回文件前,先调用前面我们写的cacheHandler模块来处理缓存
- 如果有缓存则返回304
- 如果不存在缓存,则设置文件类型,etag,跨域响应头
- 调用compressHandler对返回的文件进行压缩处理
- 返回资源
responseFile(req, res, filepath, stat) { this.cacheHandler(req, res, filepath).then( data => { if (data === true) { res.writeHead(304); res.end(); } else { res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8'); res.setHeader('Etag', data); this.cors && res.setHeader('Access-Control-Allow-Origin', '*'); const compress = this.compressHandler(req, res); if (compress) { fs.createReadStream(filepath) .pipe(compress) .pipe(res); } else { fs.createReadStream(filepath).pipe(res); } } }, error => { this.responseError(req, res, error); } ); }
处理请求的文件夹
- 如果客户端请求的是一个文件夹,则返回的应该是该目录下的所有资源列表,而非一个具体的文件
- 通过fs.readdir可以获取到该文件夹下面所有的文件或文件夹
- 通过map来获取一个数组对象,是为了把该目录下的所有资源通过模版去渲染返回给客户端
responseDirectory(req, res, filepath, pathname) { fs.readdir(filepath, (err, files) => { if (!err) { const fileList = files.map(file => { const isDirectory = fs.statSync(filepath + '/' + file).isDirectory(); return { filename: file, url: path.join(pathname, file), isDirectory }; }); const html = handlebars.compile(templates.fileList)({ title: pathname, fileList }); res.setHeader('Content-Type', 'text/html'); res.end(html); } });
app.js完整代码
const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); const mime = require('mime'); const crypto = require('crypto'); const zlib = require('zlib'); const openbrowser = require('open'); const handlebars = require('handlebars'); const templates = require('./templates'); class StaticServer { constructor(options) { this.host = options.host; this.port = options.port; this.rootPath = process.cwd(); this.cors = options.cors; this.openbrowser = options.openbrowser; } /** * handler request * @param {*} req * @param {*} res */ requestHandler(req, res) { const { pathname } = url.parse(req.url); const filepath = path.join(this.rootPath, pathname); // To check if a file exists fs.stat(filepath, (err, stat) => { if (!err) { if (stat.isDirectory()) { this.responseDirectory(req, res, filepath, pathname); } else { this.responseFile(req, res, filepath, stat); } } else { this.responseNotFound(req, res); } }); } /** * Reads the contents of a directory , response files list to client * @param {*} req * @param {*} res * @param {*} filepath */ responseDirectory(req, res, filepath, pathname) { fs.readdir(filepath, (err, files) => { if (!err) { const fileList = files.map(file => { const isDirectory = fs.statSync(filepath + '/' + file).isDirectory(); return { filename: file, url: path.join(pathname, file), isDirectory }; }); const html = handlebars.compile(templates.fileList)({ title: pathname, fileList }); res.setHeader('Content-Type', 'text/html'); res.end(html); } }); } /** * response resource * @param {*} req * @param {*} res * @param {*} filepath */ async responseFile(req, res, filepath, stat) { this.cacheHandler(req, res, filepath).then( data => { if (data === true) { res.writeHead(304); res.end(); } else { res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8'); res.setHeader('Etag', data); this.cors && res.setHeader('Access-Control-Allow-Origin', '*'); const compress = this.compressHandler(req, res); if (compress) { fs.createReadStream(filepath) .pipe(compress) .pipe(res); } else { fs.createReadStream(filepath).pipe(res); } } }, error => { this.responseError(req, res, error); } ); } /** * not found request file * @param {*} req * @param {*} res */ responseNotFound(req, res) { const html = handlebars.compile(templates.notFound)(); res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(html); } /** * server error * @param {*} req * @param {*} res * @param {*} err */ responseError(req, res, err) { res.writeHead(500); res.end(`there is something wrong in th server! please try later!`); } /** * To check if a file have cache * @param {*} req * @param {*} res * @param {*} filepath */ cacheHandler(req, res, filepath) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filepath); const md5 = crypto.createHash('md5'); const ifNoneMatch = req.headers['if-none-match']; readStream.on('data', data => { md5.update(data); }); readStream.on('end', () => { let etag = md5.digest('hex'); if (ifNoneMatch === etag) { resolve(true); } resolve(etag); }); readStream.on('error', err => { reject(err); }); }); } /** * compress file * @param {*} req * @param {*} res */ compressHandler(req, res) { const 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 false; } } /** * server start */ start() { const server = http.createServer((req, res) => this.requestHandler(req, res)); server.listen(this.port, () => { if (this.openbrowser) { openbrowser(`http://${this.host}:${this.port}`); } console.log(`server started in http://${this.host}:${this.port}`); }); } } module.exports = StaticServer;
创建命令行工具
- 首先在bin目录下创建一个config.js
- 导出一些默认的配置
module.exports = { host: 'localhost', port: 3000, cors: true, openbrowser: true, index: 'index.html', charset: 'utf8' };
#! /usr/bin/env node
#! /usr/bin/env node const yargs = require('yargs'); const path = require('path'); const config = require('./config'); const StaticServer = require('../src/app'); const pkg = require(path.join(__dirname, '..', 'package.json')); const options = yargs .version(pkg.name + '@' + pkg.version) .usage('yg-server [options]') .option('p', { alias: 'port', describe: '设置服务器端口号', type: 'number', default: config.port }) .option('o', { alias: 'openbrowser', describe: '是否打开浏览器', type: 'boolean', default: config.openbrowser }) .option('n', { alias: 'host', describe: '设置主机名', type: 'string', default: config.host }) .option('c', { alias: 'cors', describe: '是否允许跨域', type: 'string', default: config.cors }) .option('v', { alias: 'version', type: 'string' }) .example('yg-server -p 8000 -o localhost', '在根目录开启监听8000端口的静态服务器') .help('h').argv; const server = new StaticServer(options); server.start();
入口文件
最后回到根目录下的index.js,将我们的模块导出,这样可以在根目录下通过 node index
来调试
module.exports = require('./bin/static-server');
配置命令
配置命令非常简单,进入到package.json文件里
加入一句话
"bin": { "yg-server": "bin/static-server.js" },
npm link
总结
写到这里基本上就写完了,另外还有几个模版文件,是用来在客户端展示的,可以看我的 github ,我就不贴了,只是一些html而已,你也可以自己设置,这个博客写多了是在是太卡了,字都打不动了。
另外有哪里写的不好的地方或看不懂的地方可以给我留言。
个人公众号欢迎关注
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 利用 MinIO 轻松搭建静态资源服务
- Flow 静态类型检查开发环境搭建
- 如何快速搭建静态资源服务器
- # golang搭建静态web服务器
- 快速搭建 HTTP 静态服务的 10 种方法
- Node.js 系列 - 搭建静态资源服务器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Essential C++中文版
李普曼 (Stanley B.Lippman) / 侯捷 / 电子工业出版社 / 2013-8-1 / CNY 65.00
本书以四个面向来表现C++的本质:procedural(面向过程的)、generic(泛型的)、object-based(基于对象的)、objectoriented(面向对象的)。全书围绕一系列逐渐繁复的程序问题,以及用以解决这些问题的语言特性来组织。循此方式,你将不只学到C++的功能和结构,也可学到它们的设计目的和基本原理。 本书适合那些已经开始从事软件设计,又抽不出太多时间学习新技术的程......一起来看看 《Essential C++中文版》 这本书的介绍吧!