这是我参与更文挑战的第 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;
}
};
};
复制代码
相关资料
最后
如果文章对你有帮助,点个赞呗~