源码系列 —— koa-cors

这是我参与更文挑战的第 17 天,活动详情查看:更文挑战

koa-cors 是 Koa 用于配置 CORS 响应头的中间件,关于 CORS 相关知识,可以参看跨域资源共享 CORS 详解,主要用于解决跨域问题。

简单使用如下所示:

const Koa = require('koa');
const cors = require('@koa/cors');

const app = new Koa();
app.use(cors());
复制代码

cors 方法可以接受一个参数 options,返回一个中间件函数。可配置的参数如下所示:

 * @param {Object} [options]
 *  - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
 *  - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
 *  - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
 *  - {String|Array} allowHeaders `Access-Control-Allow-Headers`
 *  - {String|Number} maxAge `Access-Control-Max-Age` in seconds
 *  - {Boolean} credentials `Access-Control-Allow-Credentials`
 *  - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
 * @return {Function} cors middleware
复制代码

源码及分析如下所示:

module.exports = function(options) {
  // 默认的请求方法
  const defaults = {
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
  };
  // 如果有配置请求方法,会覆盖默认值
  options = {
    ...defaults,
    ...options,
  };

  // 可能为数组的配置项都转为字符串
  if (Array.isArray(options.exposeHeaders)) {
    options.exposeHeaders = options.exposeHeaders.join(',');
  }

  if (Array.isArray(options.allowMethods)) {
    options.allowMethods = options.allowMethods.join(',');
  }

  if (Array.isArray(options.allowHeaders)) {
    options.allowHeaders = options.allowHeaders.join(',');
  }

  // maxAge 可能为数字类型,也转为字符串处理
  if (options.maxAge) {
    options.maxAge = String(options.maxAge);
  }

  // keepHeadersOnError 默认为 true
  options.keepHeadersOnError = options.keepHeadersOnError === undefined || !!options.keepHeadersOnError;

  // 返回中间件函数
  return async function cors(ctx, next) {
    // 如果获取不到 origin 就终止流程
    const requestOrigin = ctx.get('Origin');

    // Always set Vary header
    // https://github.com/rs/cors/issues/10
    ctx.vary('Origin');

    // 如果请求没有 origin,不做处理,直接执行下一个中间件
    if (!requestOrigin) return await next();

    // 获取配置参数中的 origin,默认取 requestOrigin
    let origin;
    if (typeof options.origin === 'function') {
      origin = options.origin(ctx);
      if (origin instanceof Promise) origin = await origin;
      if (!origin) return await next();
    } else {
      origin = options.origin || requestOrigin;
    }

    // 获取配置参数中的 Credentials,默认 false
    let credentials;
    if (typeof options.credentials === 'function') {
      credentials = options.credentials(ctx);
      if (credentials instanceof Promise) credentials = await credentials;
    } else {
      credentials = !!options.credentials;
    }

    const headersSet = {};

    function set(key, value) {
      ctx.set(key, value);
      headersSet[key] = value;
    }

    // 非 options 请求,即非预检请求
    // 考虑配置 Access-Control-Allow-Origin,Access-Control-Allow-Credentials,Access-Control-Expose-Headers 三个响应头
    if (ctx.method !== 'OPTIONS') {
      // Simple Cross-Origin Request, Actual Request, and Redirects
      set('Access-Control-Allow-Origin', origin);

      if (credentials === true) {
        set('Access-Control-Allow-Credentials', 'true');
      }

      if (options.exposeHeaders) {
        set('Access-Control-Expose-Headers', options.exposeHeaders);
      }

      if (!options.keepHeadersOnError) {
        return await next();
      }
      // 异常情况处理
      try {
        return await next();
      } catch (err) {
        const errHeadersSet = err.headers || {};
        const varyWithOrigin = vary.append(errHeadersSet.vary || errHeadersSet.Vary || '', 'Origin');
        delete errHeadersSet.Vary;

        err.headers = {
          ...errHeadersSet,
          ...headersSet,
          ...{ vary: varyWithOrigin },
        };
        throw err;
      }
    } else {
      // 预检请求

      // 如果没有 Access-Control-Request-Method 请求头,终止流程
      if (!ctx.get('Access-Control-Request-Method')) {
        // this not preflight request, ignore it
        return await next();
      }

      // 根据情况设置相关响应头
      ctx.set('Access-Control-Allow-Origin', origin);

      if (credentials === true) {
        ctx.set('Access-Control-Allow-Credentials', 'true');
      }

      if (options.maxAge) {
        ctx.set('Access-Control-Max-Age', options.maxAge);
      }

      if (options.allowMethods) {
        ctx.set('Access-Control-Allow-Methods', options.allowMethods);
      }

      let allowHeaders = options.allowHeaders;
      if (!allowHeaders) {
        allowHeaders = ctx.get('Access-Control-Request-Headers');
      }
      if (allowHeaders) {
        ctx.set('Access-Control-Allow-Headers', allowHeaders);
      }

      ctx.status = 204;
    }
  };
};
复制代码

相关资料

跨域资源共享 CORS 详解

koa/cors 源码

最后

如果文章对你有帮助,点个赞呗~

分类:
前端