對於初學Node.js,搭建一個靜態服務器能夠加深對TCP/IP的理解,在學習過程當中參考了Node大神樸靈,本文主要記述在搭建中的思路,以加深對服務器的瞭解。主要實現如下幾個功能:html
### 項目目錄
vue
#! /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)}`) }) }
建立服務器的響應node
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) } }
對文件類型的處理vue-cli
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
一、 在當前目錄執行npm link
能夠將當前statc-server1命令添加到命令中
具體能夠查看
npm
能夠查看到當目錄已經生成,原來一直用的vue-cli命令就是能過這裏能夠找到。
二、執行 ~set DEBUG=static*~
三、執行命令 ~static-server1~ 默認是9090 當前能夠訪問http:localhost:9090緩存