对于初学Node.js,搭建一个静态服务器可以加深对TCP/IP的理解,在学习过程中参考了Node大神朴灵,本文主要记述在搭建中的思路,以加深对服务器的了解。主要实现以下几个功能:
- 执行命令可以到指定的目录
- 读取静态文件
- MIME类型支持
- 缓存支持/控制
- 支持gzip压缩
- 只能访问指定目录, 不能访问指定目录的上级目录,保证安全
- 访问目录可以自动寻找下面的index.html文件
- Range支持,断点续传
项目初始化
### 项目目录
指定命令行文件
创建命令行
#! /usr/bin/env node
let yargs = require('yargs');
let Server = require('../src/app.js');
let 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: 9090,
description: '请配置端口号'
})
.usage('static-server1 [options]')
.example(
'static-server1 -d / -p 9890 -o localhost', '在本机的9090端口上监听客户端的请求'
).help('h').argv;
// argv = {d,root,o,host,p,port}
let server = new Server(argv);
server.start();
//static-server1
//命令行中的命令指向了npm目录bat文件,而 bat文件又指向了当前目录 的www文件
静态服务器核心类
- 创建服务器实例
start() {
// 创建服务实例
let server = http.createServer()
server.on('request', this.request.bind(this))
server.listen(this.config.port, () => {
let url = `http://${this.config.host}:${this.config.port}`
debug(`server started at ${chalk.green(url)}`)
})
}
-
创建服务器的响应
async request(req, res) { let {pathname} = url.parse(req.url) if (pathname === '/favicon.ico') { return this.sendError('not found', req, res) } let filePath = path.join(this.config.root, pathname) try { let statObj = await stat(filePath) if (statObj.isDirectory()) { let files = await readdir(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) } else { this.sendFile(req, res, filePath, statObj) } } catch (e) { debug(inspect(e)) // inspect把一个对象转成字符 this.sendError(e, req, res) } }
-
对文件类型的处理
sendFile(req, res, filePath, statObj) { // 如果走缓存 ,则直接返回 if (this.handleCache(req, res, filePath, statObj)) return res.setHeader('Content-Type', mime.getType(filePath) + ';charset= utf-8') let encoding = this.getEncoding(req, res) let rs = this.getStream(req, res, filePath, statObj) if (encoding) { rs.pipe(encoding).pipe(res) } else { rs.pipe(res) }
- Range支持,断点续传
getStream(req, res, filePath, statObj) {
let start = 0;
let end = statObj.size - 1
let 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])
}
}
return fs.createReadStream(filePath, {
start, end
})
}
- 处理缓存
handleCache(req, res, filePath, statObj) {
let ifModifiedSince = req.headers['if-modified-since']
let isNoneMatch = req.headers['is-none-match']
res.setHeader('Cache-Control', 'private,max-age=30')
res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString())
let etag = statObj.size
let lastModified = statObj.ctime.toGMTString()
res.setHeader('ETag',etag)
res.setHeader('Last-Modified',lastModified)
if(isNoneMatch && isNoneMatch != etag)
{
return false
}
if(ifModifiedSince && ifModifiedSince != lastModified)
{
return false
}
if(isNoneMatch || ifModifiedSince)
{
res.writeHead(304)
res.end()
return true
}else {
return false
}
}
- 错误处理
sendError(err,req,res)
{
res.statusCode = 500
res.end(`${err.toString()}`)
}
- 压缩处理
getEncoding(req,res)
{
let 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 null
}
}
项目运行
代码已发布在npm
1、 在当前目录执行npm link
可以将当前statc-server1命令添加到命令中
具体可以查看
可以查看到当目录已经生成,原来一直用的vue-cli命令就是能过这里可以找到。
2、执行 ~set DEBUG=static*~
3、执行命令 ~static-server1~ 默认是9090 当前可以访问http:localhost:9090
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。