(function () {
'use strict';
var assign = require('object-assign');
var vary = require('vary');
var defaults = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204
};
function isString(s) {
return typeof s === 'string' || s instanceof String;
}
function isOriginAllowed(origin, allowedOrigin) {
// 多次递归返回一个地址
if (Array.isArray(allowedOrigin)) {
for (var i = 0; i < allowedOrigin.length; ++i) {
if (isOriginAllowed(origin, allowedOrigin[i])) {
return true;
}
}
return false;
// 在白名单里面就通过
} else if (isString(allowedOrigin)) {
return origin === allowedOrigin;
// 正则匹配
} else if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
} else {
return !!allowedOrigin;
}
}
function configureOrigin(options, req) {
var requestOrigin = req.headers.origin,
headers = [],
isAllowed;
// 默认所有域名都可以使用
if (!options.origin || options.origin === '*') {
// allow any origin
headers.push([{
key: 'Access-Control-Allow-Origin',
value: '*'
}]);
// 固定的一个域名
} else if (isString(options.origin)) {
// fixed origin
headers.push([{
key: 'Access-Control-Allow-Origin',
value: options.origin
}]);
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
} else {
// 这里通过白名单列表
isAllowed = isOriginAllowed(requestOrigin, options.origin);
// reflect origin
headers.push([{
key: 'Access-Control-Allow-Origin',
value: isAllowed ? requestOrigin : false
}]);
headers.push([{
key: 'Vary',
value: 'Origin'
}]);
}
return headers;
}
// 允许的方法
function configureMethods(options) {
var methods = options.methods;
if (methods.join) {
methods = options.methods.join(','); // .methods is an array, so turn it into a string
}
return {
key: 'Access-Control-Allow-Methods',
value: methods
};
}
function configureCredentials(options) {
// 是否带cookies
if (options.credentials === true) {
return {
key: 'Access-Control-Allow-Credentials',
value: 'true'
};
}
return null;
}
function configureAllowedHeaders(options, req) {
var allowedHeaders = options.allowedHeaders || options.headers;
var headers = [];
// 如果没有请求头, 那么指定请求头
if (!allowedHeaders) {
allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers
headers.push([{
key: 'Vary',
value: 'Access-Control-Request-Headers'
}]);
} else if (allowedHeaders.join) {
allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string
}
if (allowedHeaders && allowedHeaders.length) {
headers.push([{
key: 'Access-Control-Allow-Headers',
value: allowedHeaders
}]);
}
return headers;
}
// 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
// 如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
// https://github.com/expressjs/cors#configuration-options
function configureExposedHeaders(options) {
var headers = options.exposedHeaders;
if (!headers) {
return null;
} else if (headers.join) {
headers = headers.join(','); // .headers is an array, so turn it into a string
}
if (headers && headers.length) {
return {
key: 'Access-Control-Expose-Headers',
value: headers
};
}
return null;
}
// 在此时间内不需要发出预检查请求
function configureMaxAge(options) {
var maxAge = options.maxAge && options.maxAge.toString();
if (maxAge && maxAge.length) {
return {
key: 'Access-Control-Max-Age',
value: maxAge
};
}
return null;
}
// 发送响应头
function applyHeaders(headers, res) {
for (var i = 0, n = headers.length; i < n; i++) {
var header = headers[i];
if (header) {
if (Array.isArray(header)) {
applyHeaders(header, res);
} else if (header.key === 'Vary' && header.value) {
vary(res, header.value);
} else if (header.value) {
res.setHeader(header.key, header.value);
}
}
}
}
function cors(options, req, res, next) {
var headers = [],
method = req.method && req.method.toUpperCase && req.method.toUpperCase();
// 嗅探请求 如果是非简单请求,那么会先发送这个
// 关于简单请求和非简单请求
// 这里写的很详细 http://www.lcode.cc/2016/12/06/cors-explain.html
if (method === 'OPTIONS') {
// preflight
headers.push(configureOrigin(options, req));
headers.push(configureCredentials(options, req));
headers.push(configureMethods(options, req));
headers.push(configureAllowedHeaders(options, req));
headers.push(configureMaxAge(options, req));
headers.push(configureExposedHeaders(options, req));
applyHeaders(headers, res);
// 兼容一波zz浏览器
if (options.preflightContinue ) {
next();
} else {
// Safari (and potentially other browsers) need content-length 0,
// for 204 or they just hang waiting for a body
res.statusCode = options.optionsSuccessStatus || defaults.optionsSuccessStatus;
res.setHeader('Content-Length', '0');
res.end();
}
} else {
// 返回正常的结果
// actual response
headers.push(configureOrigin(options, req));
headers.push(configureCredentials(options, req));
headers.push(configureExposedHeaders(options, req));
applyHeaders(headers, res);
next();
}
}
function middlewareWrapper(o) {
// 设置回调函数
// if options are static (either via defaults or custom options passed in), wrap in a function
var optionsCallback = null;
if (typeof o === 'function') {
optionsCallback = o;
} else {
optionsCallback = function (req, cb) {
cb(null, o);
};
}
// 使用到了闭包
return function corsMiddleware(req, res, next) {
optionsCallback(req, function (err, options) {
if (err) {
// 出错了直接进入下一个中间件
next(err);
} else {
var corsOptions = assign({}, defaults, options);
var originCallback = null;
// 通过传参数进来可以动态使用cors。
// 见文档 https://github.com/expressjs/cors#configuring-cors-w-dynamic-origin
if (corsOptions.origin && typeof corsOptions.origin === 'function') {
originCallback = corsOptions.origin;
} else if (corsOptions.origin) {
originCallback = function (origin, cb) {
cb(null, corsOptions.origin);
};
}
if (originCallback) {
// 用来设置白名单
originCallback(req.headers.origin, function (err2, origin) {
if (err2 || !origin) {
next(err2);
} else {
corsOptions.origin = origin;
cors(corsOptions, req, res, next);
}
});
} else {
next();
}
}
});
};
}
// can pass either an options hash, an options delegate, or nothing
module.exports = middlewareWrapper;
}());