温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

nodejs搭建静态服务器的示例

发布时间:2021-02-19 13:45:30 来源:亿速云 阅读:284 作者:小新 栏目:web开发

这篇文章给大家分享的是有关nodejs搭建静态服务器的示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

静态服务器

使用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 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端口的静态服务器')  .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上,然后全局安装,在任何目录下你都可以通过你设置的命令来开启一个静态服务器,在我们平时总会需要这样一个静态服务器

感谢各位的阅读!关于“nodejs搭建静态服务器的示例”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI