基于koa2.x实现静态资源服务器

2018-12-29  本文已影响123人  空无一码

详细很多人都看过阮一峰的 koa框架教程 , 非常通俗易懂的入门教程,但对于koa 框架的一些内部原理,特别是中间件机制不是很了解。因此,本文想通过 基于koa-static第三方中间件 去搭建静态资源服务器并分析其源码,来了解 koa 这个node流行web框架的一些内部机制。

学习准备

在开始前,先告诉大家需要掌握的js web开发基础知识和如何搭建开发环境。

基础知识

在开始学习前,希望您已经有了一定的web开发基础,可通过以下推荐的学习资料,来掌握本文所需的必备前端基础知识:

搭建环境

因为本人用的是Mac系统,开发的演示在Mac系统下进行,不过使用windows系统也差不多,都需要安装node、npm包管理器,和我推荐使用的vscode。

简单入门例子

本节我们将从0实现一个最简单的koa web网站,输入一个IP地址加端口号,就能返回一个特别简单的静态网站。

mkdir staticServerByKoa // 创建工程目录
cd staticServerByKoa // 进入目录
npm init -f // 初始化node项目

可以看到目录下生成了一个package.json 文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)等,内容如下:

{
  "name": "staticServerByKoa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
touch index.js // 创建文件
vi index.js // 使用终端的vi编辑器,也可以直接使用vscode等界面编辑器。

在英文输入法下按一下 i 键即进入编辑模式,输入如下内容:

const Koa = require('koa');  // 引入koa框架
const app = new Koa();
// ctx 为Koa 提供的 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复),通过操作ctx,就可以控制返回给用户的内容。
app.use(ctx => {
  // 该属性就是发送给用户的内容。
  ctx.response.body = '写代码很快乐!';
});

app.listen(8888);
console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');

输完后,按esc进入指令模式,然后按 shift + : 两个键进入命令行模式,输入 wq 保存退出。

{
  "name": "staticServerByKoa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.6.2"
  }
}
npm start

不出意外,会报错。。。(因为还没有安装koa的依赖)

npm i koa // 安装成功后可看到 package.json 文件 多了一个dependencies的配置项,里面包含了koa这个名称和所用的版本
> staticServerByKoa@1.0.0 start /Users/xian2/immoc/staticServerByKoa
> node index

恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问

把终端中的 http://localhost:8888/ 复制到浏览器即可看到网页显示:写代码很快乐,至此,一个简单的入门例子就实现了。

升级入门例子

在上一节我们需要手动开启浏览器,并且更改文件后还需要手动重启服务器,同时页面太丑伤不起,所以本节进行优化:

自动开启浏览器

const Koa = require('koa');  // 引入koa框架
const cp = require('child_process'); // 用来创建子进程
const app = new Koa();
// ctx 为Koa 提供的 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复),通过操作ctx,就可以控制返回给用户的内容。
app.use(ctx => {
  // 该属性就是发送给用户的内容。
  ctx.response.body = '写代码很快乐!';
});
app.listen(8888);
cp.exec('open http://localhost:8888/'); // 自动打开浏览器
console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');

文件更新后自动重启

// --save-dev 是为了让nodemon 配置到开发环境的依赖项即devDependencies中,因为生产环境不需要用它
npm i --save-dev nodemon // 它会监测项目中的所有文件,一旦发现文件有改动,会自动重启应用
 "start": "nodemon index",

此时,我们把index.js中的“带代码很快乐”改成“不聪明的码农,写代码很苦逼”,就会重新打开浏览器并显示最新内容。

app.use(ctx => {
  // 该属性就是发送给用户的内容。
  ctx.response.type = 'html';
  ctx.response.body = '<h1 style="color: red;height: 60px; background-color: black;">导航栏</h1>';
});

此时就可以返回 最常见的html内容了,但是写起来很不便,需要再次优化。

app.use(ctx => {
  // 该属性就是发送给用户的内容。
  ctx.response.type = 'html';
  ctx.response.body = fs.createReadStream('index.html');
});

不出意外,会报错,因为我们没有引入fs, 在前面加入:

const fs = require('fs');

此时,虽然不报错,但 页面显示 Not Found,因为我们没有 创建index.html,
创建 index.html,并输入以下内容:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>学习koa</title>
  </head>
  <body>
    <h1>学习真快乐!</h1>
  </body>
</html>

此时,每改动一次html文件,在浏览器刷新即可看到最新内容。并且添加内容和样式就方便很多了。

使用 koa-static 搭建静态服务器

.header-wrap {
  color: red;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>学习koa</title>
    <link href="index.css" rel="stylesheet">
  </head>
  <body>
    <h1 class="header-wrap">学习真快乐!</h1>
  </body>
</html>

此时,重启服务器,发现页面内容的文字并没有变成红色。这是因为我们没有做对css文件格式的处理,此时如果我们使用图片,也会不能正常显示。那么我们需要一个个去处理吗?当然可以,但是这样开发效率就太低了,一般我们会选择koa-static去实现。不过作为学习,我们不仅需要学会用,更需要掌握如何学习第三方依赖库的方法。

const Koa = require('koa');  // 引入koa框架
const cp = require('child_process'); // 用来创建子进程
const path = require('path');
const KoaStatic = require('koa-static');

const app = new Koa();

app.use(KoaStatic(path.join( __dirname, './static')));

app.listen(8888);
cp.exec('open http://localhost:8888/'); // 自动打开浏览器

console.log('恭喜你,服务器启动成功:复制 http://localhost:8888/ 到浏览器即可访问');
npm i koa-static

koa-static 源码分析

细心的人会发现,我们只是输入http://localhost:8888/,并没有输入index.html, 浏览器自动打开却显示了它的内容,这是为什么呢?想了解原因,我们就需要去了解源码。

学习源码的方式有很多种,我们可以去npm官网直接搜该库,一般都会托管在github, 我们可以直接下载来看,还可以参与该库的开发维护。如果只是简单看看,我们通过编辑器在项目根目录的node_modules找到该依赖库即可。

  "dependencies": {
    "debug": "^3.1.0",
    "koa-send": "^5.0.0"
  },

我们看到它主要依赖koa-send,一会我们也需要去查看一下koa-send。

function serve (root, opts) {
  opts = Object.assign({}, opts)
  opts.root = resolve(root)
  if (opts.index !== false) opts.index = opts.index || 'index.html'

因为我们没有传入opts对象作为koa-static的第二个参数,所以opts为空对象opts, 此时 opts.index的值就被设为了index.html。

  // 大概40多行
  path = path.substr(parse(path).root.length)
  const index = opts.index
  const maxage = opts.maxage || opts.maxAge || 0

  // 大概60多行
  if (path === -1) return ctx.throw(400, 'failed to decode')

  // index file support
  if (index && trailingSlash) path += index

  path = resolvePath(root, path)

可以看到在koa-send首先接收了koa-static调用时传进来的opts参数,然后把默认的index.html文件,加到了请求路径当中,这样在们直接输入http://localhost:8888/就相当于访问http://localhost:8888/index.html文件了。因此,我们可以看到页面显示其内容。

koa 源码简析

既然已经掌握了看依赖库源码的方法,我们何不也简单一下koa的源码呢?

node_modules/koa
├── History.md
├── LICENSE
├── Readme.md
├── lib
│   ├── application.js //入口文件,封装了context,request,response,以及最核心的中间件处理流程。
│   ├── context.js //处理应用上下文,里面直接封装部分request.js和response.js的方法
│   ├── request.js // 处理http请求
│   └── response.js // 处理http响应
└── package.json

我们看到源码都在lib目录下, koa2.x作为一个web框架,只提供了封装好的HTTP服务,以及基于async/await的中间件容器。用Koa.js想实现大部分Web功能的话,就需要通过中间件来实现,如我们前面用到的koa-static。

 listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  use(fn) {
    ...省略了各种异常和兼容处理
    if (isGeneratorFunction(fn)) {
      fn = convert(fn); 
      // koa@2中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用
    }
    this.middleware.push(fn);
    return this;
  }
  callback() {
    const fn = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }

可以看到,在我们实例化 const app = new koa(); 后,执行 app.use() 系列中间件后,会把各中间件添加进 middleware 这个数组,然后再执行app.listen() 的时候,会把callback()回调函数传入 node原生的http模块的createServer()方法,然后在启动服务器以后就会执行callback()中请求、响应、上下文以及中间件的有关逻辑了。更深入的探究,就是基于此一步步分析各个模块的实现了。

一般直接被 app.use() 加载
中间件内请求拦截 request
中间件内响应拦截 response
中间件内上下文代理,初始化实例时候挂载代理在app.context上,请求时候挂载代理在ctx上

例如, koa-bodyparser主要是拦截请求后解析出HTTP请求体中的POST数据,而koa-static主要是靠拦截请求和响应,加载静态资源,再挂载到ctx上。

广义中间件特点

间接被 app.use() 加载
间接提供中间件或者子中间件
其他方式接入koa切面

例如中间koa-router 是先注册路由后形成多个子中间件,后面再封装成一个父中间件提供给app.use()加载,让所有子中间件加载到Koa.js的请求洋葱模型中。

结语

本文 通过 从0 到 基于koa-static 一步步 去搭建静态资源服务器和分析其源码,且简单看了看koa2的源码,了解 koa2一些内部机制,特别是掌握了分析源码的一些方法,便于大家后续学习提高。项目有关的代码都托管在GitHub上: https://github.com/yibiankeji/staticServerByKoa.git

上一篇下一篇

猜你喜欢

热点阅读