深入理解nodejs搭建静态服务器(实现命令行)

静态服务器

使用node搭建一个可在任何目录下通过命令启动的一个简单http静态服务器

完整代码链接

安装:npm install yg-server -g

启动:yg-server

可通过以上命令安装,启动,来看一下最终的效果

TODO

  • 创建一个静态服务器
  • 通过yargs来创建命令行工具
  • 处理缓存
  • 处理压缩

初始化

  • 创建目录:mkdir static-server
  • 进入到该目录:cd static-server
  • 初始化项目:npm init
  • 构建文件夹目录结构:

初始化静态服务器

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

总结

写到这里基本上就写完了,另外还有几个模版文件,是用来在客户端展示的,可以看我的github,我就不贴了,只是一些html而已,你也可以自己设置,这个博客写多了是在是太卡了,字都打不动了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Node.js静态服务器的实现方法

    当你输入一个url时,这个url可能对应服务器上的一个资源(文件)也可能对应一个目录. So服务器会对这个url进行分析,针对不同的情况做不同的事. 如果这个url对应的是一个文件,那么服务器就会返回这个文件. 如果这个url对应的是一个文件夹,那么服务器会返回这个文件夹下包含的所有子文件/子文件夹的列表. 以上,就是一个静态服务器所主要干的事. 但真实的情况不会像这么简单, 我们所拿到的url可能是错误的,它所对应的文件或则文件夹或许根本不存在, 又或则有些文件和文件夹是被系统保护起来的是隐藏

  • Nodejs实现的一个静态服务器实例

    参考cnodejs.org上面的静态服务器例子,写了下面的一个nodejs静态服务器例子,里面包含cache,压缩,贴代码如下: 复制代码 代码如下: /**  * 静态文件服务器测试例子  * User: xuwm  * Date: 13-5-17  * Time: 上午8:38  * To change this template use File | Settings | File Templates.  */ var port=3333; var http = require("http

  • 深入理解nodejs搭建静态服务器(实现命令行)

    静态服务器 使用node搭建一个可在任何目录下通过命令启动的一个简单http静态服务器 完整代码链接 安装:npm install yg-server -g 启动:yg-server 可通过以上命令安装,启动,来看一下最终的效果 TODO 创建一个静态服务器 通过yargs来创建命令行工具 处理缓存 处理压缩 初始化 创建目录:mkdir static-server 进入到该目录:cd static-server 初始化项目:npm init 构建文件夹目录结构: 初始化静态服务器 首先在src

  • 在windows上用nodejs搭建静态文件服务器的简单方法

    在windows上用nodejs搭建一个静态文件服务器,即使你一点基础没有也能学会nodejs静态文件服务器的搭建,本文介绍的非常详细,很适合零基础入门的朋友学习. 首先安装nodejs: •新建一个node文件夹 •下载node.exe到该文件夹 •下载npm然后解压到该文件夹 •现在node文件夹是这样的 •把该目录加入到path环境变量 •在命令行执行 node -v npm -v 如果得到了版本号则表示nodejs安装完成 •在命令行中执行 npm config set registry

  • nodejs搭建本地服务器轻松解决跨域问题

    最近把以前用jquery写的一个小demo拿出来运行的,刚开始的时候忘了开启本地服务导致控制台一直报XMLHttpRequest cannot load file:///C:/Users/79883/Desktop/ajax/data.json?{%22username%22:%22lcl@qq.com%22,%22pwd%22:%22Home20170702%22}. Cross origin requests are only supported for protocol schemes:

  • 浅谈使用nodejs搭建web服务器的过程

    使用 Node 创建 Web 服务器 什么是 Web 服务器? Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,Web服务器的基本功能就是提供Web信息浏览服务.它只需支持HTTP协议.HTML文档格式及URL,与客户端的网络浏览器配合. 大多数 web 服务器都支持服务端的脚本语言(php.python.ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器. 目前最主流的三个Web服务器是Apache.Nginx.IIS. Node.js 提供了 htt

  • nodejs 搭建简易服务器的图文教程(推荐)

    www文件夹下 template文件夹下 server.js代码: const express=require('express'); const static=require('express-static'); //托管静态文件 const cookieParser=require('cookie-parser'); //解析cookie const cookieSession=require('cookie-session'); //解析session const bodyParser=r

  • Nodejs搭建wss服务器教程

    本文为大家分享了Nodejs搭建wss服务器的教程,供大家参考,具体内容如下 首先使用OpenSSL创建自签名证书: #生成私钥key文件 openssl genrsa 1024 > /path/to/private.pem // #通过私钥文件生成CSR证书签名 openssl req -new -key /path/to/private.pem -out csr.pem // #通过私钥文件和CSR证书签名生成证书文件 openssl x509 -req -days 365 -in csr.

  • nodejs搭建本地服务器并访问文件操作示例

    本文实例讲述了nodejs搭建本地服务器并访问文件操作.分享给大家供大家参考,具体如下: 安装node:https://nodejs.org/en/download/ 在本地建立目录:f:/nodetest,在该目录下新建index.html作为我们将要访问的内容.新建server.js作为node开启的入口: $ cd f:/nodetest $ mkdir index.html $ mkdir server.js index.html文件里简单写一点东西: <!DOCTYPE html>

  • NodeJS搭建HTTP服务器的实现步骤

    前言 在 NodeJS 中用来创建服务的模块是 http 核心模块,本篇就来介绍关于使用 http 模块搭建 HTTP 服务器和客户端的方法,以及模块的基本 API. HTTP 服务器 1.创建 HTTP 服务器 在 NodeJS 中,创建 HTTP 服务器可以与 net 模块创建 TCP 服务器对比,创建服务器有也两种方式. 方式 1: const http = require("http"); const server = http.createServer(function(re

  • Linux下搭建SFTP服务器的命令详解

    1.创建用户并设置密码 useradd -s /bin/false sftpuser passwd sftpuser # 若需要多个sftp用户 新建sftp的用户组: groupadd sftp useradd -g sftp -m sftpuser1 useradd -g sftp -m sftpuser2 passwd sftpuser1 passwd sftpuser2 usermod -g sftp sftpuser 2.配置ssh和权限 首先关闭SElinux vim /etc/sy

  • 用nodejs搭建websocket服务器

    简单开始 1.安装node.https://nodejs.org/en/ 2.安装ws模块 ws:是nodejs的一个WebSocket库,可以用来创建服务. https://github.com/websockets/ws 3.server.js 在项目里面新建一个server.js,创建服务,指定8181端口,将收到的消息log出来. var WebSocketServer = require('ws').Server, wss = new WebSocketServer({ port: 8

随机推荐