Koa 静态文件服务中间件,其中使用的是koa-send,源码很简单。 先说下koa-send的使用
koa-send
koa-send是send静态文件的,用法如下:
app.use(async (ctx) => {
await send(ctx, ctx.path, { root: __dirname + '/public' });
})
// or
app.use(async (ctx) => {
await send(ctx, 'path/to/my.js');
})
send方法有三个参数:ctx, path,和options, options中包含的选项如下:
maxage
:浏览器缓存maxage,以毫秒为单位。(默认为0)。immutable
:告诉浏览器资源是不可变的,并且可以无限期缓存。(默认为false)。hidden
:允许传输隐藏文件。(默认为false)。root
:限制文件访问的根目录。index
:访问根位置时自动服务的索引文件名。(默认为none)。gzip
:在客户端支持gzip时,并且存在gzip文件,则会使用gzip文件。(默认为true)。brotli
:同gizp一个意思。(默认为true)。format
:如果不是false(默认为true),格式化路径以服务静态文件服务器,并且不需要目录的尾随斜杠,这样你就可以同时使用/directory和/directory/。setHeaders
:设置自定义响应头的函数。extensions
:尝试将传递的数组中的扩展名与搜索文件匹配,当URL中没有足够的扩展名时。首先找到的将被服务。(默认为false)
koa-static使用
const Koa = require('koa');
const app = new Koa();
app.use(require('koa-static')(root, opts));
opts的值如下:
maxage
:浏览器缓存的maxage(以毫秒为单位)。默认值为0。hidden
:允许传输隐藏文件。默认值为false(即不允许)。index
:默认的文件名,默认为'index.html'。defer
:如果为true,则在返回next()之后提供服务,允许任何下游中间件首先响应。gzip
:在客户端支持gzip时,并且存在gzip文件,则会使用gzip文件。默认值为true。br
:同gizp一个意思。默认值为true。setHeaders
:用于在响应上设置自定义头部的函数。extensions
:尝试匹配传递数组中的扩展名,以在URL中没有指定扩展名时搜索文件。首先找到的文件将被提供服务。默认值为false(即不启用此功能)。 可以看到和koa-send的配置对象很像,起内部就是使用了koa-send.看下源码。
源码
// 引入debug模块,用于调试输出信息
const debug = require('debug')('koa-static')
// 引入path模块中的resolve方法,用于解析路径
const { resolve } = require('path')
// 引入assert模块,用于断言检查
const assert = require('assert')
// 引入koa-send模块,用于发送文件
const send = require('koa-send')
// 将serve函数暴露为模块的默认导出
module.exports = serve
/**
* serve函数用于创建一个Koa中间件,该中间件能够提供静态文件服务。
*
* @param {String} root 静态文件的根目录路径
* @param {Object} [opts] 可选的配置对象
* @return {Function} 返回一个Koa中间件函数
* @api public 表示这个函数是公共API,可以被外部使用
*/
function serve (root, opts) {
opts = Object.assign({}, opts) // 复制传入的配置对象,避免修改原对象
// 断言检查root参数必须提供
assert(root, 'root directory is required to serve files')
// 调试输出静态文件服务的配置信息
debug('static "%s" %j', root, opts)
// 将root参数解析为绝对路径
opts.root = resolve(root)
// 如果没有明确设置index文件,则默认为'index.html'
if (opts.index !== false) opts.index = opts.index || 'index.html'
// 如果没有设置defer选项,则立即返回静态文件服务中间件
if (!opts.defer) {
return async function serve (ctx, next) {
let done = false
// 只处理HEAD和GET请求
if (ctx.method === 'HEAD' || ctx.method === 'GET') {
try {
// 尝试发送文件
done = await send(ctx, ctx.path, opts)
} catch (err) {
// 如果错误状态不是404,则抛出错误
if (err.status !== 404) {
throw err
}
}
}
// 如果文件发送成功,则不执行后续中间件
if (!done) {
await next()
}
}
}
// 如果设置了defer选项,则延迟处理静态文件服务
return async function serve (ctx, next) {
await next()
// 只处理HEAD和GET请求
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return
// 如果响应已经被处理,则不执行静态文件服务
if (ctx.body != null || ctx.status !== 404) return // eslint-disable-line
try {
// 尝试发送文件
await send(ctx, ctx.path, opts)
} catch (err) {
// 如果错误状态不是404,则抛出错误
if (err.status !== 404) {
throw err
}
}
}
}
defer
是一个可选的配置参数,它控制着静态文件服务中间件的执行时机。具体来说:
- 如果
defer
设置为false
(默认值),静态文件服务中间件会立即执行,尝试根据请求的路径发送相应的静态文件。如果文件发送成功(即找到了请求的文件),则不会执行后续的中间件(await next()
)。 - 如果
defer
设置为true
,则静态文件服务中间件会延迟执行,首先执行await next()
,让后续的中间件有机会处理请求。只有当后续中间件没有处理响应(即ctx.body
为null
且ctx.status
为404)时,才会执行静态文件服务中间件来尝试发送文件。
通过defer
选项,我们可以控制静态文件服务中间件与其他中间件的执行顺序,以实现更灵活的请求处理逻辑。例如,我们可以在某些情况下让自定义的中间件优先处理请求,只有在自定义中间件没有处理响应时,才由静态文件服务中间件提供默认的静态文件服务。