最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解nodejs搭建靜態(tài)服務器(實現(xiàn)命令行)

 更新時間:2019年02月05日 15:20:08   作者:accord  
這篇文章主要介紹了深入理解nodejs搭建靜態(tài)服務器(實現(xiàn)命令行),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

靜態(tài)服務器

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

完整代碼鏈接

安裝:npm install yg-server -g

啟動:yg-server

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

TODO

  • 創(chuàng)建一個靜態(tài)服務器
  • 通過yargs來創(chuàng)建命令行工具
  • 處理緩存
  • 處理壓縮

初始化

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

初始化靜態(tài)服務器

  • 首先在src目錄下創(chuàng)建一個app.js
  • 引入所有需要的包,非node自帶的需要npm安裝一下
  • 初始化構造函數(shù),options參數(shù)由命令行傳入,后續(xù)會講到
    • 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;
 }
}

處理錯誤響應

在寫具體業(yè)務前,先封裝幾個處理響應的函數(shù),分別是錯誤的響應處理,沒有找到資源的響應處理,在后面會調(diào)用這么幾個函數(shù)來做響應

  • 處理錯誤
  • 返回狀態(tài)碼500
  • 返回錯誤信息
 responseError(req, res, err) {
  res.writeHead(500);
  res.end(`there is something wrong in th server! please try later!`);
 }
  • 處理資源未找到的響應
  • 返回狀態(tài)碼404
  • 返回一個404html
 responseNotFound(req, res) {
  // 這里是用handlerbar處理了一個模版并返回,這個模版只是單純的一個寫著404html
  const html = handlebars.compile(templates.notFound)();
  res.writeHead(404, {
   'Content-Type': 'text/html'
  });
  res.end(html);
 }

處理緩存

在前面的一篇文章里我介紹過node處理緩存的幾種方式,這里為了方便我只使用的協(xié)商緩存,通過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來判斷瀏覽器支持的壓縮方式
  • 設置壓縮響應頭,并創(chuàng)建對文件的壓縮方式
 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;
  }
 }

啟動靜態(tài)服務器

  • 添加一個啟動服務器的方法
  • 所有請求都交給this.requestHandler這個函數(shù)來處理
  • 端口號
 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模塊判斷文件是否存在,這里分三種情況
    • 請求路徑是一個文件夾,則調(diào)用responseDirectory處理
    • 請求路徑是一個文件,則調(diào)用responseFile處理
    • 如果請求的文件不存在,則調(diào)用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);
   }
  });
 }

處理請求的文件

  • 每次返回文件前,先調(diào)用前面我們寫的cacheHandler模塊來處理緩存
  • 如果有緩存則返回304
  • 如果不存在緩存,則設置文件類型,etag,跨域響應頭
  • 調(diào)用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來獲取一個數(shù)組對象,是為了把該目錄下的所有資源通過模版去渲染返回給客戶端
 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;

創(chuàng)建命令行工具

  • 首先在bin目錄下創(chuàng)建一個config.js
  • 導出一些默認的配置
module.exports = {
 host: 'localhost',
 port: 3000,
 cors: true,
 openbrowser: true,
 index: 'index.html',
 charset: 'utf8'
};
  • 然后創(chuàng)建一個static-server.js
  • 這里設置的是一些可執(zhí)行的命令
  • 并實例化了我們最初在app.js里寫的server類,將options作為參數(shù)傳入
  • 最后調(diào)用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端口的靜態(tài)服務器')
 .help('h').argv;

const server = new StaticServer(options);

server.start();

入口文件

最后回到根目錄下的index.js,將我們的模塊導出,這樣可以在根目錄下通過node index來調(diào)試

module.exports = require('./bin/static-server');

配置命令

配置命令非常簡單,進入到package.json文件里

加入一句話

 "bin": {
  "yg-server": "bin/static-server.js"
 },
  • yg-server是啟動該服務器的命令,可以自己定義
  • 然后執(zhí)行npm link生成一個符號鏈接文件
  • 這樣你就可以通過命令來執(zhí)行自己的服務器了
  • 或者將包托管到npm上,然后全局安裝,在任何目錄下你都可以通過你設置的命令來開啟一個靜態(tài)服務器,在我們平時總會需要這樣一個靜態(tài)服務器

總結

寫到這里基本上就寫完了,另外還有幾個模版文件,是用來在客戶端展示的,可以看我的github,我就不貼了,只是一些html而已,你也可以自己設置,這個博客寫多了是在是太卡了,字都打不動了。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • 使用Express處理請求和托管靜態(tài)資源方式

    使用Express處理請求和托管靜態(tài)資源方式

    這篇文章主要介紹了使用Express處理請求和托管靜態(tài)資源方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 用Node.JS打造一個惡劣天氣實時預警系統(tǒng)

    用Node.JS打造一個惡劣天氣實時預警系統(tǒng)

    本文將從實戰(zhàn)的角度出發(fā),利用NodeJS以及聚合數(shù)據(jù)的第三方免費接口打造一個完整的天氣實時預警項目系統(tǒng),具有一定的參考價值,感興趣的可以了解一下
    2021-12-12
  • node.js入門教程之querystring模塊的使用方法

    node.js入門教程之querystring模塊的使用方法

    querystring模塊主要用來解析查詢字符串,下面這篇文章主要介紹了關于node.js中querystring模塊使用方法的相關資料,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • node版本快速切換及管理方法

    node版本快速切換及管理方法

    這篇文章主要為大家介紹了node版本快速切換及管理方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • node.js程序作為服務并在windows下開機自啟動(用forever)

    node.js程序作為服務并在windows下開機自啟動(用forever)

    這篇文章主要介紹了node.js程序作為服務并在windows下開機自啟動的相關資料,因為實現(xiàn)的功能比較簡單,沒有選擇功能比較強大的pm2,文中選擇利用了forever,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-03-03
  • Nodejs實現(xiàn)多文件夾文件同步

    Nodejs實現(xiàn)多文件夾文件同步

    這篇文章主要為大家介紹了Nodejs實現(xiàn)多文件夾文件同步,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • 詳解一些適用于Node.js的命名約定

    詳解一些適用于Node.js的命名約定

    這篇文章主要介紹了詳解一些適用于Node.js的命名約定,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • Nodejs 構建Cluster集群多線程Worker threads

    Nodejs 構建Cluster集群多線程Worker threads

    這篇文章主要為大家介紹了Nodejs 構建Cluster集群多線程Worker threads示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Node.js?子線程Crash?問題的排查方法

    Node.js?子線程Crash?問題的排查方法

    這篇文章主要介紹了Node.js?子線程Crash?問題的排查,本文通過代碼例子給大家詳細講解,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • 理解Koa2中的async&await的用法

    理解Koa2中的async&await的用法

    這篇文章主要介紹了理解Koa2中的async&await的用法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02

最新評論

波密县| 石狮市| 来安县| 临澧县| 江孜县| 凤山县| 金秀| 蒙城县| 通榆县| 北海市| 逊克县| 福安市| 潜江市| 华阴市| 加查县| 色达县| 略阳县| 昂仁县| 乌兰浩特市| 延边| 九龙县| 桂东县| 东至县| 亚东县| 深州市| 宜宾市| 峡江县| 苍溪县| 翁牛特旗| 竹北市| 海南省| 海兴县| 英吉沙县| 明水县| 枣强县| 沽源县| 松江区| 京山县| 福海县| 乾安县| 万州区|