详细很多人都看过阮一峰的 koa框架教程 , 非常通俗易懂的入门教程,但对于koa 框架的一些内部原理,特别是中间件机制不是很了解。因此,本文想通过 基于koa-static第三方中间件 去搭建静态资源服务器并分析其源码,来了解 koa 这个node流行web框架的一些内部机制。
学习准备
在开始前,先告诉大家需要掌握的js web开发基础知识和如何搭建开发环境。
基础知识
在开始学习前,希望您已经有了一定的web开发基础,可通过以下推荐的学习资料,来掌握本文所需的必备前端基础知识:
搭建环境
因为本人用的是Mac系统,开发的演示在Mac系统下进行,不过使用windows系统也差不多,都需要安装node、npm包管理器,和我推荐使用的vscode。
- 安装node:不管是windows还是Mac系统,安装方式都有很多(我一般使用nvm),不过作为教程,我只提供去node官网https://nodejs.org/en/download/直接下载安装的方式,其他方式网上都能搜到。
- 安装npm:只要node版本不是特别低,在安装node的时候就一起安装npm了,当然也可单独安装yarn这个npm包管理器,不过高版本的npm和yarn相差不大,使用npm即可。
- 安装vscode: 无论使用什么系统,直接去官网https://code.visualstudio.com/Download找到对应的系统版本安装即可,当然也可以选择任何自己使用的编辑器。
简单入门例子
本节我们将从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 保存退出。
- 添加启动脚本: 在package.json 文件中的scripts 部分加入 "start": "node index", 如下所示:
{
"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的依赖)
- 安装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/ 复制到浏览器即可看到网页显示:写代码很快乐,至此,一个简单的入门例子就实现了。
升级入门例子
在上一节我们需要手动开启浏览器,并且更改文件后还需要手动重启服务器,同时页面太丑伤不起,所以本节进行优化:
自动开启浏览器
- 将 index.js文件改为:
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/ 到浏览器即可访问');
文件更新后自动重启
- 安装 nodemon:
// --save-dev 是为了让nodemon 配置到开发环境的依赖项即devDependencies中,因为生产环境不需要用它
npm i --save-dev nodemon // 它会监测项目中的所有文件,一旦发现文件有改动,会自动重启应用
- 修改package.json 文件中scripts的start脚本为:
"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 搭建静态服务器
- 在根目录下新建index.css文件,输入如下内容:
.header-wrap {
color: red;
}
- 更改index.html文件为:
<!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去实现。不过作为学习,我们不仅需要学会用,更需要掌握如何学习第三方依赖库的方法。
- 在根目录下创建static目录,并把index.css和index.html移到该目录。
- 修改index.js,如下所示:
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/ 到浏览器即可访问');
- 报错的话,极有可能是因为没有安装koa-static(小白常见错误),执行如下命令:
npm i koa-static
- 重新启动 服务器后,我们就能看到 页面文字显示为红色了,并且也可以正常使用图片了。
koa-static 源码分析
细心的人会发现,我们只是输入http://localhost:8888/,并没有输入index.html, 浏览器自动打开却显示了它的内容,这是为什么呢?想了解原因,我们就需要去了解源码。
学习源码的方式有很多种,我们可以去npm官网直接搜该库,一般都会托管在github, 我们可以直接下载来看,还可以参与该库的开发维护。如果只是简单看看,我们通过编辑器在项目根目录的node_modules找到该依赖库即可。
- 查看其相关依赖,一般通过其package.json 文件中的dependencies查看:
"dependencies": {
"debug": "^3.1.0",
"koa-send": "^5.0.0"
},
我们看到它主要依赖koa-send,一会我们也需要去查看一下koa-send。
- 点开node_modules的koa-static目录,我们看到它只有四个文件,代码只在index.js文件,可以看到它只是对koa-send作了简单封装,涉及到解决我们疑惑的问题主要在如下的几行(30行前后找):
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。
- 我们再点开node_modules的koa-send目录的index.js文件, 找到 如下几行:
// 大概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目录,包含了如下文件:
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如何实现的?
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
- app.use() 干了啥?
use(fn) {
...省略了各种异常和兼容处理
if (isGeneratorFunction(fn)) {
fn = convert(fn);
// koa@2中间件只支持 async/await 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用
}
this.middleware.push(fn);
return this;
}
- callback 源码
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()中请求、响应、上下文以及中间件的有关逻辑了。更深入的探究,就是基于此一步步分析各个模块的实现了。
- 中间件简介
Koa.js 中间件 可为 狭义中间件和广义中间件 两种类型,其区别如下:
狭义中间件特点:
一般直接被 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。
网友评论