對於初學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