这篇文章继续前面的Koa
源码系列,这个系列已经有两篇文章了:
- 第一篇讲解了
Koa
的核心架构和源码:手写Koa.js源码 - 第二篇讲解了
@koa/router
的架构和源码:手写@koa/router源码
本文会接着讲一个常用的中间件----koa-static
,这个中间件是用来搭建静态服务器的。
其实在我之前使用Node.js原生API写一个web服务器已经讲过怎么返回一个静态文件了,代码虽然比较丑,基本流程还是差不多的:
- 通过请求路径取出正确的文件地址
- 通过地址获取对应的文件
- 使用
Node.js
的API返回对应的文件,并设置相应的header
koa-static
的代码更通用,更优雅,而且对大文件有更好的支持,下面我们来看看他是怎么做的吧。本文还是采用一贯套路,先看一下他的基本用法,然后从基本用法入手去读源码,并手写一个简化版的源码来替换他。
基本用法
koa-static
使用很简单,主要代码就一行:
const Koa = require('koa');
const serve = require('koa-static');
const app = new Koa();
// 主要就是这行代码
app.use(serve('public'));
app.listen(3001, () => {
console.log('listening on port 3001');
});
上述代码中的serve
就是koa-static
,他运行后会返回一个Koa
中间件,然后Koa
的实例直接引用这个中间件就行了。
serve
方法支持两个参数,第一个是静态文件的目录,第二个参数是一些配置项,可以不传。像上面的代码serve('public')
就表示public
文件夹下面的文件都可以被外部访问。比如我在里面放了一张图片:
跑起来就是这样子:
注意上面这个路径请求的是/test.jpg
,前面并没有public
,说明koa-static
对请求路径进行了判断,发现是文件就映射到服务器的public
目录下面,这样可以防止外部使用者探知服务器目录结构。
手写源码
返回的是一个Koa
中间件
我们看到koa-static
导出的是一个方法serve
,这个方法运行后返回的应该是一个Koa
中间件,这样Koa
才能引用他,所以我们先来写一下这个结构吧:
module.exports = serve; // 导出的是serve方法
// serve接受两个参数
// 第一个参数是路径地址
// 第二个是配置选项
function serve(root, opts) {
// 返回一个方法,这个方法符合koa中间件的定义
return async function serve(ctx, next) {
await next();
}
}
调用koa-send
返回文件
现在这个中间件是空的,其实他应该做的是将文件返回,返回文件的功能也被单独抽取出来成了一个库----koa-send
,我们后面会看他源码,这里先直接用吧。
function serve(root, opts) {
// 这行代码如果效果就是
// 如果没传opts,opts就是空对象{}
// 同时将它的原型置为null
opts = Object.assign(Object.create(null), opts);
// 将root解析为一个合法路径,并放到opts上去
// 因为koa-send接收的路径是在opts上
opts.root = resolve(root);
// 这个是用来兼容文件夹的,如果请求路径是一个文件夹,默认去取index
// 如果用户没有配置index,默认index就是index.html
if (opts.index !== false) opts.index = opts.index || 'index.html';
// 整个serve方法的返回值是一个koa中间件
// 符合koa中间件的范式: (ctx, next) => {}
return async function serve(ctx, next) {
let done = false; // 这个变量标记文件是否成功返回
// 只有HEAD和GET请求才响应
if (ctx.method === 'HEAD' || ctx.method === 'GET') {
try {
// 调用koa-send发送文件
// 如果发送成功,koa-send会返回路径,赋值给done
// done转换为bool值就是true
done = await send(