亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

深入理解nodejs搭建靜態服務器(實現命令行)

2024-05-06 16:47:59
字體:
來源:轉載
供稿:網友

靜態服務器

使用node搭建一個可在任何目錄下通過命令啟動的一個簡單http靜態服務器

完整代碼鏈接

安裝:npm install yg-server -g

啟動:yg-server

可通過以上命令安裝,啟動,來看一下最終的效果

TODO

  • 創建一個靜態服務器
  • 通過yargs來創建命令行工具
  • 處理緩存
  • 處理壓縮

初始化

  • 創建目錄:mkdir static-server
  • 進入到該目錄:cd static-server
  • 初始化項目:npm init
  • 構建文件夾目錄結構:

nodejs,靜態服務器,命令行

初始化靜態服務器

  • 首先在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 nodeconst 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而已,你也可以自己設置,這個博客寫多了是在是太卡了,字都打不動了。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
中文字幕亚洲欧美日韩高清| 欧美综合激情网| 亚洲国产美女精品久久久久∴| 精品国产一区二区三区久久狼5月| 综合136福利视频在线| 欧美中文字幕在线播放| 亚洲精品黄网在线观看| 久久久亚洲福利精品午夜| 亚洲精品动漫久久久久| 久久久久久成人精品| 91九色蝌蚪国产| 久久久久国色av免费观看性色| 九九热r在线视频精品| 亚洲石原莉奈一区二区在线观看| 91免费福利视频| 欧美另类老女人| 26uuu另类亚洲欧美日本一| 久久影视三级福利片| 78m国产成人精品视频| 亚洲人成欧美中文字幕| 日本午夜人人精品| 亚洲永久在线观看| 欧美性高潮在线| 亚洲www在线| 日韩av在线免费观看| 中文字幕亚洲欧美日韩高清| 欧美亚洲另类在线| 国产91久久婷婷一区二区| 久久亚洲成人精品| 欧美成人在线免费视频| 青青草国产精品一区二区| 国产盗摄xxxx视频xxx69| 色999日韩欧美国产| 国产精品视频xxx| 久久久精品亚洲| 毛片精品免费在线观看| 亚洲一区999| 美女999久久久精品视频| 亚州国产精品久久久| 欧美电影免费在线观看| 成人动漫网站在线观看| 亚洲国产高清高潮精品美女| 2019中文字幕在线观看| 欧美激情在线观看视频| 午夜精品三级视频福利| 91地址最新发布| 欧美激情视频一区二区三区不卡| 97人洗澡人人免费公开视频碰碰碰| 555www成人网| 久久久久久成人| 国产97人人超碰caoprom| 国产欧亚日韩视频| 国产精品国产福利国产秒拍| 国产精品十八以下禁看| www.国产精品一二区| 亚洲精品久久久久久久久久久| 国产亚洲激情视频在线| 久久91精品国产91久久跳| 日韩欧美在线免费| 国产精品一区=区| 国产精品久久久久久久久| 日韩美女免费视频| 日韩成人激情视频| 久久精品国产精品| 精品亚洲国产成av人片传媒| 欧美日韩日本国产| 久久中文字幕在线视频| 国产精品视频久久久| 国产精品精品久久久久久| 国产精品久久久久影院日本| 亚洲电影免费观看高清| 国产精品久久久久久亚洲影视| 久久久久亚洲精品国产| 大荫蒂欧美视频另类xxxx| 92国产精品久久久久首页| 日韩动漫免费观看电视剧高清| 国产一区二区三区在线免费观看| 国产免费成人av| 欧美高清视频一区二区| 久久久久久香蕉网| 国产三级精品网站| 日韩av在线资源| 国产一区二区三区中文| 欧美激情一区二区三区久久久| 欧美激情精品在线| 久久久久日韩精品久久久男男| 国产精品永久在线| 国产精品白嫩初高中害羞小美女| 在线成人激情视频| 欧美亚洲激情视频| 欧美成人精品一区| 久久久久久久97| 国产成人亚洲综合91| 91美女福利视频高清| 日韩成人激情影院| 日本亚洲欧洲色α| 国产97在线亚洲| 成人性生交大片免费看小说| 中文亚洲视频在线| 久久久天堂国产精品女人| 欧美精品福利在线| 国产精品96久久久久久又黄又硬| 欧美日韩色婷婷| 国产欧美日韩免费看aⅴ视频| 中文字幕在线国产精品| 中文字幕不卡在线视频极品| 这里只有精品丝袜| 日本精品视频网站| 国产精品第10页| 欧美亚洲激情在线| 社区色欧美激情 | 7777免费精品视频| 日韩经典中文字幕| 亚洲人成电影在线观看天堂色| 日韩精品欧美国产精品忘忧草| 亚洲国产成人精品一区二区| 久久精品久久久久久国产 免费| 在线视频精品一| 午夜精品一区二区三区在线视| 久久久久久久久久久久久久久久久久av| 91精品国产高清自在线看超| 国产日韩欧美日韩大片| 亚洲欧美制服第一页| 亚洲国产精彩中文乱码av在线播放| 国产精品午夜一区二区欲梦| 亚洲欧美一区二区三区在线| 亚洲一区二区黄| 免费97视频在线精品国自产拍| 亚洲福利在线观看| 97色在线视频观看| 国产精品久久久久久久久久久新郎| 色偷偷91综合久久噜噜| 欧美日韩在线第一页| 欧美电影免费在线观看| 97人人模人人爽人人喊中文字| 中文在线资源观看视频网站免费不卡| 日本精品在线视频| 中文字幕久久亚洲| 色爱精品视频一区| 91国产美女在线观看| y97精品国产97久久久久久| 欧美日韩国产一中文字不卡| 日韩av黄色在线观看| 欧美亚洲国产日韩2020| www.色综合| 亚洲欧美色图片| 久久久久国产一区二区三区| 色狠狠久久aa北条麻妃| 国产成人a亚洲精品| 国产一区二区三区四区福利| 啊v视频在线一区二区三区| 久久国内精品一国内精品| 国产成人中文字幕| 亚洲精品v欧美精品v日韩精品| 国产精品久久综合av爱欲tv| 国产一区二区丝袜高跟鞋图片| 91精品国产高清久久久久久| 亚洲午夜未删减在线观看| 亚洲淫片在线视频| 欧美精品久久久久a| 亚洲欧美成人网| 国产成人精品在线视频| 欧美俄罗斯性视频| 中文字幕日韩精品有码视频|