博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
node之搭建一个http完整的静态服务器(命令行工具)
阅读量:5894 次
发布时间:2019-06-19

本文共 5563 字,大约阅读时间需要 18 分钟。

本文主要写一个完整的静态服务(命令行工具),http压缩、缓存以及范围请求。 大致功能是:在当前目录启动一个服务,服务用来监听请求,处理请求。并在浏览器上渲染出当前服务下的所有目录

1.配置一个服务参数对象

let path = require('path');let config = {  hostname:'localhost',  port:3000,  dir:path.join(__dirname,'..','public')}module.exports = config;复制代码

2.写出服务的基本框架

let config = require('./config');let template = fs.readFileSync(path.resolve(__dirname, 'tmpl.html'), 'utf8');class Server {  constructor(args) {    this.config = config;// 将配置挂载在我们的实例上    this.template = template; //用于渲染当前服务下的所有目录  }  //处理请求  handleRequest() {        } start() {    let server = http.createServer(this.handleRequest.bind(this));    let { hostname, port } = this.config;    server.listen(port, hostname);  }}复制代码

这样一个服务就可以开启了,下面写如何处理请求。 处理请求主要功能是:接收请求后,解析当前请求路径下,判断该路径是否是目录,如果是,则解析该目录下所有子目录,渲染到页面上,如果不是,则直接渲染该文件。为了方便处理,我们使用async+await实现路径解析,代码如下:

async handleRequest(req, res) {     let { pathname } = url.parse(req.url, true);    let p = path.join(this.config.dir, pathname);    // 1.根据路径 如果是文件夹 显示文件夹里的内容    // 2.如果是文件 显示文件的内容    try { // 如果没错误说明文件存在      let statObj = await stat(p);      if (statObj.isDirectory()) {        // 现在需要一个当前目录下的解析出的对象或者数组        let dirs = await readdir(p);        dirs = dirs.map(dir => { // dirs就是要渲染的数据          return {            filename: dir,            pathname: path.join(pathname, dir)          }        });        let str = ejs.render(this.template, { dirs, title: 'ejs' });        res.setHeader('Content-Type', 'text/html;charset=utf8');        res.end(str);      } else {        // 文件 发送文件        this.sendFile(req, res, p, statObj);      }    } catch (e) {      // 文件不存在的情况      //错误    }  }复制代码

其中用到的模版功能是将当前目录下的子文件,渲染到页面上,代码如下:

  
<%=title%> <%dirs.map(item=>{%>
  • <%=item.filename%>
  • <%})%>复制代码

    这样一个基本的http服务就成形了,结下来我们实现sendFile功能,主要包括:缓存、压缩、范围请求。 缓存:主要包括强制缓存和对比缓存。代码如下:

    cache(req, res, p, stat) {    // 实现缓存     let since = req.headers['if-modified-since'];    let match = req.headers['if-none-match'];    let ssince = stat.ctime.toUTCString();//文件修改时间    let smatch = stat.ctime.getTime() + stat.size;//文件修改时间和文件大小    res.setHeader('Last-Modified', ssince);    res.setHeader('ETag', smatch);    res.setHeader('Cache-Control','max-age=6');//强制缓存只要它成立,就走缓存    if (since != ssince) { // if-modified-since和last-modified      return false;    }    if (match != smatch) {      return false    }    return true;  }复制代码

    实现范围请求:主要根据 请求头Range,如Range:bytes=0-3表示请求前四个字节,将响应发给浏览器时,需要设置响应头 Accept-Ranges: bytes

    Content-Length: 4
    Content-Range: bytes 0-3/7877
    设置状态码206

    range(req, res, p, stat) {    let range = req.headers['range'];    if(range){      let [, start, end] = range.match(/(\d*)-(\d*)/) || [];      start = start ? parseInt(start) : 0;      end = end ? parseInt(end) : stat.size;      res.statusCode = 206;      res.setHeader('Accept-Ranges', 'bytes');      res.setHeader('Content-Length', end - start + 1);      res.setHeader('Content-Range', `bytes ${start}-${end}/${stat.size}`);      return { start, end };    }else{      return {start:0,end:stat.size}    }  }复制代码

    文件压缩功能:判断请求头中的Accept-Encoding是否有gzip,deflate等,有就压缩,并设置响应头Content-Encoding

    // 实现服务端压缩  gzip(req, res, p, stat) {    let header = req.headers['accept-encoding'];    if (header) {      if (header.match(/\bgzip\b/)) {        res.setHeader('Content-Encoding', 'gzip');        fs.createReadStream(p).pipe(zlib.createGzip()).pipe(res);      } else if (header.match(/\bdeflate\b/)) {        res.setHeader('Content-Encoding', 'deflate');        fs.createReadStream(p).pipe(zlib.createDeflate()).pipe(res);      }    } else {      fs.createReadStream(p).pipe(res);    }  }复制代码

    然后综合缓存,压缩,范围请求,整理下sendFile方法

    sendFile(req, res, p, stat) {    if (this.cache(req, res, p, stat)) {// 检测是否有缓存      res.statusCode = 304;      res.end();      return    };    this.compressAndRange(req, res, p, stat);  }  compressAndRange(req, res, p, stat) {      let compress = this.gzip(req, res, p, stat);      let { start, end } = this.range(req, res, p, stat); //范围请求,返回开始位置和结束位置      if (compress) { // 返回的是一个压缩流          res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8');          fs.createReadStream(p, { start, end }).pipe(compress).pipe(res);      } else {          res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8');          fs.createReadStream(p, { start, end }).pipe(res);      }  }复制代码

    这样服务器的功能都具备了,在实现一个命令行工具 在package.json中,添加:

    "bin": {    "my-http-server": "bin/www.js"  }复制代码

    然后npm link

    在bin/www.js 中启动http server,这样就可以通过my-http-server 命令启动http服务器. 写www.js

    #! /usr/bin/env nodelet Server = require('../src/app');new Server().start(); // 开启服务复制代码

    这样就可以通过my-http-server启动服务,但是如果我想在任意目录任意端口,任意域名下访问呢。 使用yargs包,通过命令行参数控制

    #! /usr/bin/env node// 第一执行了命令后 会执行 bin/www.jslet yargs = require('yargs')let argv = yargs.option('port', {//配置端口号  alias: 'p',  default: 3000,  demand: false,  description: 'this is port'}).option('hostname', {//配置域名  alias: 'host',  default: 'localhost',  type: String,  demand: false,  description: 'this is hostname'}).option('dir', {//配置目录  alias: 'd',  default: process.cwd(),//命令的打开目录,任意目录  type: String,  demand: false,  description: 'this is cwd'}).usage('zf-http-server [options]').argv;let Server = require('../src/app');new Server(argv).start(); // 开启服务复制代码

    注意这里将参数argv传入了Server类,然后需要修改下Server

    class Server {  constructor(args) {    this.config = { ...config, ...args };// 将命令行中的参数传给server    this.template = template;  }  ...  ...}复制代码

    这样我们通过:my-http-server -host localhost -p 3003 -d './public'命令就可以指定端口(3003),目录(当前命令所在目录/public),域名(localhost)启动服务

    转载于:https://juejin.im/post/5b25e50c6fb9a00e850cad53

    你可能感兴趣的文章
    多行编辑软件
    查看>>
    win7x64安装wince6
    查看>>
    二叉查找树详解
    查看>>
    Atom 检测php错误扩展linter-php
    查看>>
    改变Android按钮背景颜色的高效方法
    查看>>
    出现“unrecognized selector sent to instance”问题原因之一及解决方法。
    查看>>
    jni c++
    查看>>
    nginx配置ssl双向验证 nginx https ssl证书配置
    查看>>
    ORB:新一代 Linux 应用
    查看>>
    Linux系统如何查看CPU型号等
    查看>>
    ACM OJ Collection
    查看>>
    Redis主从复制原理总结
    查看>>
    Java反射API使用实例
    查看>>
    第三十三节,sys解释器相关模块
    查看>>
    CDC spyglass
    查看>>
    Node.js模块 require和 exports
    查看>>
    快速集成iOS基于RTMP的视频推流
    查看>>
    在C#中获取如PHP函数time()一样的时间戳
    查看>>
    Redis List数据类型
    查看>>
    [Ramda] Getter and Setter in Ramda & lens
    查看>>