手写一个静态服务器

http-server包

前端开发人员不论是在开发还是测试中都会用到http服务器,方便快捷的使用更加有助于我们编写和调试代码,而http-server则是一个十分好用的包,它几乎不用配置,可以使用任何一个目录生成一个http服务器。

简单说一说http-server的使用。

首先全局安装它:

> npm install http-server -g
复制代码

安装成功后就可以使用命令行来启动一个http服务,进入你想启动的目录,使用命令:

> http-server
复制代码

打开浏览器访问http://localhost:8080/即可看到已经使用当前目录启动了http server。

然而这里并不是讨论http-server的使用,我们可以仿照http-server包手写一个功能近似的静态服务,以更深入的了解和学习。

功能说明

一个静态服务器都能做什么?

  • 首先应支持使用计算机上任何一个目录生成http服务;
  • 可以在浏览器中浏览目录结构
  • 可以访问服务器上的文件
  • 支持命令行,可让用户自己指定访问的端口

以上就是http服务器应该有的大致功能,我们使用node.js手写代码来实现它。

准备工作

自己手写一个http服务要有基本的文件读取,输入输出等工作要做,这些大部分功能node.js提供的库都可以实现,但是还需要有些第三方包的支持,下面简单说一下。

mime包

mime包是一个可以根据文件来分析出http内容类型的包,http的内容类型不难理解,当访问一个html页面时,浏览器会根据向应头的Content-Type类型来呈现内容。

使用npm install mime安装后即可使用:

const mime = require('mime');
var contentType = mime.getType('/Users/yuet/workspace/static-server/public/node.jpeg');//image/jpeg
复制代码

可以看到,传入正确的文件路径即可得到对应的内容类型,这样可以省去我们自己写代码判断生成Content-Type

chalk包

chalk包顾名思义,它译为粉笔,有了它就可以方便的在控制台输出内容时使用不同的颜色,同样安装它npm install chalk即可使用:

const chalk = require('chalk');
console.log(chalk.red('hello node'));
复制代码

以上使用chalk.red(),将输出内容变为红色显示,chalk有很多颜色方法支持,使用它可以突出重点内容,方便用户查看。

debug包

debug包可以在node.js环境中帮助我们调试。

举例:

var a = require('debug')('worker:a')
  , b = require('debug')('worker:b');
a('doing lots of uninteresting work');
b('doing some work');
复制代码

调用debug包会返回一个方法,它可以指定操作系统中某环境变量名,以上在操作系统中可以设置两个环境变量work:awork:b,当前若是为work:a的环境,则只会输出a方法中的内容,这有利于我们区别开发和生产环境。

默认配置

启动一个http服务器,端口、host主机、以及目录地址这三个内容必不可少,当然用户在使用时可以更改,但是需要有一个默认的配置:

const path = require('path');
let config = {
  host: '127.0.0.1',
  port: 8080,
  dir: path.join(__dirname, '../public')
};
复制代码

指定本机,默认端口号为8080,缺省目录就为当前项目中的/public目录。将此配置对象导出供外面使用。

代码基本结构

可以封装一个类,将静态文件操作、服务器的启停写在里面:

const config = require('./config');
class Server {
  constructor() {
    this.config = config;
  }
  //封装读取文件等操作
  handleRequest(req, res) {}
  /** * 启动服务器 */
  start() {}
  /** * 发送文件 */
  sendFile(req, res, filePath, stat) {}
  /** * 发送错误消息 */
  sendError(req, res, e) {}
}
new Server().start();
复制代码

以上代码就是大致的结构,其中将默认的配置对象读入并挂到类的this上,这样可以方便的调用配置信息。

核心代码

静态服务器是基于http的,因此要引入http模块来启动一个http服务,并监听端口:

start() {
    let server = http.createServer(this.handleRequest);
    let {host, port} = this.config;
    server.listen({
      host,
      port
    }, () => {
      debug(`服务器已经启动在 http://${host}:${chalk.greenBright(port)}/ 上...`);
    });
}
复制代码

当启动了服务,用户在浏览器中输入了一个地址,此地址可能是一个目录,也有可能是一个确切的文件,所以要分别处理。

let {pathname} = url.parse(req.url, true);
let resource = path.join(this.config.dir, pathname);
let s = await stat(resource);
if (s.isDirectory()) {
    //是目录
} else {
    //是文件
}
复制代码

以上代码,首先使用url的模块parse方法将浏览器地址解析出来得到绝对路径,并使用fs模块的stat方法判断是否是一个目录,这里使用了async-await操作简化了异步代码。

当是目录的时候,我们的工作就是把目录下的所有文件都读取出来并展示给用户:

let files = await readdir(resource);//目录下的所有文件
files = files.map((i) => {
  return {
    fileName: i,
    path: path.join(pathname, i)
  }
});
let html = ejs.render(temp, {files});
res.setHeader('Content-Type', 'text/html;charset=utf8');
res.end(html);
复制代码

其中fs.readdir()方法可以将当前目录下的所有文件全部返回,它返回一个Array类型,使用map映射将文件地址拼接到对象中的目的则是为了ejs模版渲染。这样在模版中循环输出,即可将当前目录下的所有文件展示在浏览器中。

ejs模版:

<ul>
  <%files.forEach((i)=>{%>
      <li><a href="<%=i.path%>"><%=i.fileName%></a></li>
  <%})%>
</ul>
复制代码

若用户访问的是个确切的文件,那么使用流的方式输出即可:

let contentType = mime.getType(filePath)
  ? mime.getType(filePath)
  : 'text/plain';
res.setHeader('Content-Type', contentType + ';charset=utf8');
fs.createReadStream(filePat).pipe(res);
复制代码

可以看到使用mime模块得到真正的内容类型,使用流pipe到响应对象中即可。

使用命令行运行

到此为止代码已基本实现,但是运行程序时依然使用node命令来运行,这显然不符合要求,比如http-server包,在全局安装之后是使用http-server命令来执行。那么怎么也达到这样的效果呢?

配置命令行工具

在package.json文件中,使用bin配置节来配置自己的命令:

package.json:

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

这个配置节的意义是当执行my-http-server命令,运行bin/www文件。

接下来就是在项目目录中创建bin/www文件:

#! /usr/bin/env node
console.log('hello');
复制代码

其中井号惊叹号!/usr/bin/env node是要说明去环境变量中找到node所在的目录,使用node来执行下面的脚本。

这时,打开控制台使用npm link命令把我们的命令链接到全局

这时就可以正确使用我们自己编写的命令了:

> my-http-server
复制代码

执行结果:

hello
复制代码

使用命令行传递用户参数

配置好命令行工具后,下面的任务就是处理命令行参数,以达到用户自己指定端口的目的。

yargs包

处理命令行参数,完全可以自己写代码截取,但是第三方yargs已经做好了这部分工作,我们只需安装使用就好,使用npm安装:npm install yargs

使用方法:

#! /usr/bin/env node
const yargs=require('yargs').argv;
console.log(yargs);
复制代码

此时执行命令行,可以使用参数:

my-http-server --port 3000
复制代码

运行结果:

 _: [],
  help: false,
  version: false,
  port: 3000,
  '$0': '/usr/local/bin/my-http-server' }
复制代码

可以看到,使用yargs包,可以将命令行参数转为对象,我们可以很方便的拿到port端口号进行操作。

配置命令并传参覆盖默认参数:

#! /usr/bin/env node
const yargs=require('yargs');
const Server=require('../src/app');

let commandParameter=yargs.option('port',{
  alias:'p',
  default:3000,
  type:Number,
  description:'端口号'
}).option('host',{
  default:'127.0.0.1',
  type:String,
  description:'IP地址,默认为127.0.0.1'
}).option('dir',{
  default:process.cwd(),
  type:String,
  description:'静态文件地址,默认为当前目录'
}).usage('my-http-server [options]').argv;

new Server(commandParameter).start();
复制代码

这样,在www文件中接收到的参数传入new Server(),覆盖掉默认参数即可:

constructor(args) {
  this.config = {
    ...config,
    ...args //命令行参数覆盖默认参数
  };
}
复制代码
    原文作者:HTTP
    原文地址: https://juejin.im/post/5b27bcf3f265da599b74b1a6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞