在文件夹命令行启动js服务器,深入nodejs-搭建静态服务器(实现命令行)

静态服务器

使用node搭建一个可在任何目录下通过命令启动的一个简单http静态服务器

完整代码链接

安装:npm install yg-server -g

启动:yg-server

可通过以上命令安装,启动,来看一下最终的效果

TODO

创建一个静态服务器

通过yargs来创建命令行工具

处理缓存

处理压缩

初始化

创建目录:mkdir static-server

进入到该目录:cd static-server

初始化项目:npm init

构建文件夹目录结构:

bVbn64E?w=214&h=178

初始化静态服务器

首先在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'

};

然后创建一个static-server.js

这里设置的是一些可执行的命令

并实例化了我们最初在app.js里写的server类,将options作为参数传入

最后调用server.start()来启动我们的服务器

注意 #! /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"

},

yg-server是启动该服务器的命令,可以自己定义

然后执行npm link生成一个符号链接文件

这样你就可以通过命令来执行自己的服务器了

或者将包托管到npm上,然后全局安装,在任何目录下你都可以通过你设置的命令来开启一个静态服务器,在我们平时总会需要这样一个静态服务器

总结

写到这里基本上就写完了,另外还有几个模版文件,是用来在客户端展示的,可以看我的github,我就不贴了,只是一些html而已,你也可以自己设置,这个博客写多了是在是太卡了,字都打不动了。

另外有哪里写的不好的地方或看不懂的地方可以给我留言。如果你觉得还有点用,给我github这个上点个star我会很感激你的哈

个人公众号欢迎关注

bVbn20e?w=258&h=258

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值