koa-static源码

268 阅读4分钟

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是一个可选的配置参数,它控制着静态文件服务中间件的执行时机。具体来说:

  1. 如果defer设置为false(默认值),静态文件服务中间件会立即执行,尝试根据请求的路径发送相应的静态文件。如果文件发送成功(即找到了请求的文件),则不会执行后续的中间件(await next())。
  2. 如果defer设置为true,则静态文件服务中间件会延迟执行,首先执行await next(),让后续的中间件有机会处理请求。只有当后续中间件没有处理响应(即ctx.bodynullctx.status为404)时,才会执行静态文件服务中间件来尝试发送文件。

通过defer选项,我们可以控制静态文件服务中间件与其他中间件的执行顺序,以实现更灵活的请求处理逻辑。例如,我们可以在某些情况下让自定义的中间件优先处理请求,只有在自定义中间件没有处理响应时,才由静态文件服务中间件提供默认的静态文件服务。