koa2-router
Advanced tools
Comparing version 1.0.6 to 1.0.7
566
lib/index.js
@@ -8,3 +8,3 @@ /*! | ||
'use strict'; | ||
'use strict' | ||
@@ -16,11 +16,11 @@ /** | ||
var Route = require('./route'); | ||
var Layer = require('./layer'); | ||
var methods = require('methods'); | ||
var mixin = require('utils-merge'); | ||
var debug = require('debug')('koa2-router'); | ||
var deprecate = require('depd')('koa2-router'); | ||
var flatten = require('array-flatten'); | ||
var parseUrl = require('parseurl'); | ||
var setPrototypeOf = require('setprototypeof'); | ||
var Route = require('./route') | ||
var Layer = require('./layer') | ||
var methods = require('methods') | ||
var mixin = require('utils-merge') | ||
var debug = require('debug')('koa2-router') | ||
var deprecate = require('depd')('koa2-router') | ||
var flatten = require('array-flatten') | ||
var parseUrl = require('parseurl') | ||
var setPrototypeOf = require('setprototypeof') | ||
@@ -32,7 +32,19 @@ /** | ||
var objectRegExp = /^\[object (\S+)\]$/; | ||
var slice = Array.prototype.slice; | ||
var toString = Object.prototype.toString; | ||
var objectRegExp = /^\[object (\S+)\]$/ | ||
var slice = Array.prototype.slice | ||
var toString = Object.prototype.toString | ||
/** | ||
* Expose `Router`. | ||
*/ | ||
module.exports = Router | ||
/** | ||
* Expose `Route`. | ||
*/ | ||
module.exports.Route = Route | ||
/** | ||
* Initialize a new `Router` with the given `options`. | ||
@@ -45,25 +57,63 @@ * | ||
var proto = module.exports = function(options) { | ||
var opts = options || {}; | ||
var name = opts.name; | ||
function Router(options) { | ||
if (!(this instanceof Router)) { | ||
return new Router(options) | ||
} | ||
var opts = options || {} | ||
function router(ctx, next) { | ||
return router.handle(ctx, next); | ||
// set ctx.originalUrl and ctx.req.originalUrl | ||
if (typeof ctx.originalUrl !== 'string') { | ||
Object.defineProperties(ctx, { | ||
originalUrl: { | ||
get: function() { return ctx.req.originalUrl }, | ||
set: function(originalUrl) { return ctx.req.originalUrl = originalUrl }, | ||
enumerable: true, | ||
configurable: true | ||
}, | ||
baseUrl: { | ||
get: function() { return ctx.req.baseUrl }, | ||
set: function(baseUrl) { return ctx.req.baseUrl = baseUrl }, | ||
enumerable: true, | ||
configurable: true | ||
}, | ||
params: { | ||
get: function() { return ctx.params.params }, | ||
set: function(params) { return ctx.req.params = params }, | ||
enumerable: true, | ||
configurable: true | ||
} | ||
}) | ||
// initialize ctx.req with `originalUrl`, `baseUrl`, `params` | ||
ctx.req.originalUrl = ctx.req.originalUrl || ctx.req.url | ||
ctx.req.baseUrl = ctx.req.baseUrl || '' | ||
ctx.req.params = ctx.req.params || {} | ||
} | ||
return router.handle(ctx, next) | ||
} | ||
router._name = name; | ||
router._name = typeof opts === 'object' ? opts.name : opts | ||
// mixin Router class functions | ||
setPrototypeOf(router, proto); | ||
// inherit from the correct prototype | ||
setPrototypeOf(router, this) | ||
router.params = {}; | ||
router.caseSensitive = opts.caseSensitive; | ||
router.mergeParams = opts.mergeParams; | ||
router.strict = opts.strict; | ||
router.middleware = []; | ||
router.caseSensitive = opts.caseSensitive | ||
router.mergeParams = opts.mergeParams | ||
router.params = {} | ||
router.strict = opts.strict | ||
router.stack = [] | ||
return router; | ||
return router | ||
} | ||
/** | ||
* Router prototype inherits from a Function. | ||
*/ | ||
/* istanbul ignore next */ | ||
Router.prototype = function () {} | ||
/** | ||
* Map the given param placeholder `name`(s) to the given callback. | ||
@@ -85,9 +135,9 @@ * | ||
* router.param('user_id', async function(ctx, next, id) { | ||
* const user = await User.find(id); | ||
* const user = await User.find(id) | ||
* if (!user) { | ||
* throw new Error('failed to load user'); | ||
* throw new Error('failed to load user') | ||
* } | ||
* ctx.user = user; | ||
* await next(); | ||
* }); | ||
* ctx.user = user | ||
* await next() | ||
* }) | ||
* | ||
@@ -99,29 +149,31 @@ * @param {String} name | ||
*/ | ||
proto.param = function param(name, fn) { | ||
Router.prototype.param = function param(name, fn) { | ||
// if name is an array, make each being invoked again | ||
if (Array.isArray(name) && name.length) { | ||
for (var i = 0; i < name.length; i++) { | ||
param.call(this, name[i], fn); | ||
param.call(this, name[i], fn) | ||
} | ||
return this; | ||
return this | ||
} | ||
if ('string' !== typeof name) { | ||
throw new TypeError('invalid param() call, got name type ' + gettype(name)); | ||
throw new TypeError('invalid param() call, got name type ' + gettype(name)) | ||
} | ||
if (name[0] === ':') { | ||
deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead'); | ||
deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead') | ||
// eslint-disable-next-line no-param-reassign | ||
name = name.substr(1); | ||
name = name.substr(1) | ||
} | ||
if ('function' !== typeof fn) { | ||
throw new TypeError('invalid param() call for ' + name + ', got ' + gettype(fn)); | ||
throw new TypeError('invalid param() call for ' + name + ', got ' + gettype(fn)) | ||
} | ||
(this.params[name] = this.params[name] || []).push(fn); | ||
return this; | ||
}; | ||
(this.params[name] = this.params[name] || []).push(fn) | ||
return this | ||
} | ||
/** | ||
@@ -132,31 +184,42 @@ * Dispatch a koa context into the router. | ||
proto.handle = async function handle(ctx, upstream) { | ||
Router.prototype.handle = async function handle(ctx, upstream) { | ||
if (typeof upstream !== 'function') { | ||
throw new TypeError('argument next(upstream) is required') | ||
} | ||
// store options for OPTIONS request | ||
// only used if OPTIONS request | ||
var options = []; | ||
var methods = [] | ||
// initialize mounted base url and params | ||
ctx.baseUrl = ctx.baseUrl || ''; | ||
ctx.params = ctx.params || {}; | ||
// save point 1 | ||
var restore1 = restore(ctx, 'baseUrl', 'params', 'url') | ||
// setup basic req values (fix `morgan` middleware `:url` print) | ||
ctx.req.originalUrl = ctx.originalUrl || ctx.url; | ||
var restore1 = restore(ctx, 'baseUrl', 'params', 'url'); | ||
try { | ||
await this.downstream(ctx, async function() { | ||
var restore2 = restore(ctx, 'baseUrl', 'params', 'url'); | ||
// dispatch into the current router | ||
await this.dispatch(ctx, methods) | ||
} catch(e) { | ||
if (e === 'router') { | ||
// if just want to jump the current router | ||
// save point 2 | ||
var restore2 = restore(ctx, 'baseUrl', 'params', 'url') | ||
try { | ||
restore1(); | ||
await upstream(); | ||
// restore point 1 | ||
restore1() | ||
// go upstream | ||
await upstream() | ||
} finally { | ||
restore2(); | ||
// restore point 2 | ||
restore2() | ||
} | ||
}, options); | ||
} else { | ||
// any other errors just throw it out | ||
throw e | ||
} | ||
} finally { | ||
// restore all url state after handling this router | ||
restore1(); | ||
// restore point 1 | ||
restore1() | ||
// for options requests, respond with a default if nothing else responds | ||
if (ctx.status === 404 && options.length && ctx.method === 'OPTIONS') { | ||
sendOptionsResponse(ctx, options); | ||
if (ctx.status === 404 && methods.length && ctx.method === 'OPTIONS') { | ||
sendOptionsResponse(ctx, methods) | ||
} | ||
@@ -167,25 +230,26 @@ } | ||
/** | ||
* Middleware downstream. | ||
* dispatch koa context into this router | ||
* @param methods collection for methods it has | ||
* @private | ||
*/ | ||
proto.downstream = function downstream(ctx, done, options) { | ||
var self = this; | ||
Router.prototype.dispatch = function dispatch(ctx, methods) { | ||
var self = this | ||
debug('dispatching %s %s%s', ctx.method, ctx.baseUrl, ctx.url); | ||
debug('dispatching %s %s%s', ctx.method, ctx.baseUrl, ctx.url) | ||
var idx = 0; | ||
var protohost = getProtohost(ctx.url) || ''; | ||
var removed = ''; | ||
var slashAdded = false; | ||
var paramcalled = {}; | ||
var idx = 0 | ||
var protohost = getProtohost(ctx.url) || '' | ||
var removed = '' | ||
var slashAdded = false | ||
var paramcalled = {} | ||
// middleware and routes | ||
var middleware = self.middleware; | ||
var stack = self.stack | ||
// manage inter-router variables | ||
var parentParams = ctx.params; | ||
var parentUrl = ctx.baseUrl; | ||
var parentParams = ctx.params | ||
var parentUrl = ctx.baseUrl | ||
return next(); | ||
return next() | ||
@@ -195,4 +259,4 @@ function next() { | ||
if (slashAdded) { | ||
ctx.url = ctx.url.substr(1); | ||
slashAdded = false; | ||
ctx.url = ctx.url.substr(1) | ||
slashAdded = false | ||
} | ||
@@ -202,38 +266,33 @@ | ||
if (removed.length !== 0) { | ||
ctx.baseUrl = parentUrl; | ||
ctx.url = protohost + removed + ctx.url.substr(protohost.length); | ||
removed = ''; | ||
ctx.baseUrl = parentUrl | ||
ctx.url = protohost + removed + ctx.url.substr(protohost.length) | ||
removed = '' | ||
} | ||
// no more matching layers | ||
if (idx >= middleware.length) { | ||
return done(); | ||
if (idx >= stack.length) { | ||
throw 'router' | ||
} | ||
// get pathname of request | ||
var path = getPathname(ctx); | ||
var path = getPathname(ctx) | ||
if (path == null) { | ||
return done(); | ||
// layer error for this router | ||
console.error('null pathname in ctx') | ||
throw 'router' | ||
} | ||
// find next matching layer | ||
var layer; | ||
var match; | ||
var route; | ||
var result; | ||
var layer | ||
var match | ||
var route | ||
while (match !== true && idx < middleware.length) { | ||
layer = middleware[idx++]; | ||
result = matchLayer(layer, path); | ||
route = layer.route; | ||
match = result.matched; | ||
while (match !== true && idx < stack.length) { | ||
layer = stack[idx++] | ||
match = layer.match(path) | ||
route = layer.route | ||
if (typeof match !== 'boolean') { | ||
// FIXME hold on to layerError? | ||
return Promise.reject(match); | ||
} | ||
if (match !== true) { | ||
continue; | ||
continue | ||
} | ||
@@ -243,11 +302,11 @@ | ||
// process non-route handlers normally | ||
continue; | ||
continue | ||
} | ||
var method = ctx.method; | ||
var has_method = route._handles_method(method); | ||
var method = ctx.method | ||
var has_method = route._handles_method(method) | ||
// build up automatic options response | ||
if (!has_method && method === 'OPTIONS') { | ||
appendMethods(options, route._options()); | ||
methods.push.apply(methods, route._methods()) | ||
} | ||
@@ -257,4 +316,4 @@ | ||
if (!has_method && method !== 'HEAD') { | ||
match = false; | ||
continue; | ||
match = false | ||
continue | ||
} | ||
@@ -265,3 +324,3 @@ } | ||
if (match !== true) { | ||
return done(); | ||
throw 'router' | ||
} | ||
@@ -271,3 +330,3 @@ | ||
if (route) { | ||
ctx.route = route; | ||
ctx.route = route | ||
} | ||
@@ -277,27 +336,13 @@ | ||
ctx.params = self.mergeParams | ||
? mergeParams(result.params, parentParams) | ||
: result.params; | ||
var layerPath = result.path; | ||
? mergeParams(layer.params, parentParams) | ||
: layer.params | ||
var layerPath = layer.path | ||
// this should be done for the layer | ||
return self.process_params(layer, paramcalled, ctx, function () { | ||
return self.process_params(layer, paramcalled, ctx, function() { | ||
if (route) { | ||
return layer.handle(ctx, next); | ||
return layer.handle(ctx, next) | ||
} | ||
return trim_prefix(layer, layerPath, path, next); | ||
}).catch(function(e) { | ||
// signal to exit route | ||
if (e === 'route') { | ||
return next(); | ||
} | ||
// signal to exit router with no error | ||
if (e === 'router') { | ||
return done(); | ||
} | ||
// signal to exit router with error | ||
throw e; | ||
}); | ||
return trim_prefix(layer, layerPath, path, next) | ||
}) | ||
} | ||
@@ -308,4 +353,4 @@ | ||
// Validate path breaks on a path separator | ||
var c = path[layerPath.length]; | ||
if (c && c !== '/' && c !== '.') return next(); | ||
var c = path[layerPath.length] | ||
if (c && c !== '/' && c !== '.') return next() | ||
@@ -315,9 +360,9 @@ // Trim off the part of the url that matches the route | ||
debug('trim prefix (%s) from url %s', layerPath, ctx.url); | ||
removed = layerPath; | ||
ctx.url = protohost + ctx.url.substr(protohost.length + removed.length); | ||
removed = layerPath | ||
ctx.url = protohost + ctx.url.substr(protohost.length + removed.length) | ||
// Ensure leading slash | ||
if (!protohost && ctx.url[0] !== '/') { | ||
ctx.url = '/' + ctx.url; | ||
slashAdded = true; | ||
ctx.url = '/' + ctx.url | ||
slashAdded = true | ||
} | ||
@@ -328,10 +373,10 @@ | ||
? removed.substring(0, removed.length - 1) | ||
: removed); | ||
: removed) | ||
} | ||
debug('%s %s : %s', layer.name, layerPath, ctx.originalUrl); | ||
debug('%s %s : %s', layer.name, layerPath, ctx.originalUrl) | ||
return layer.handle(ctx, next); | ||
return layer.handle(ctx, next) | ||
} | ||
}; | ||
} | ||
@@ -343,22 +388,22 @@ /** | ||
proto.process_params = function process_params(layer, called, ctx, done) { | ||
var params = this.params; | ||
Router.prototype.process_params = function process_params(layer, called, ctx, done) { | ||
var params = this.params | ||
// captured parameters from the layer, keys and values | ||
var keys = layer.keys; | ||
var keys = layer.keys | ||
// fast track | ||
if (!keys || keys.length === 0) { | ||
return done(); | ||
return done() | ||
} | ||
var i = 0; | ||
var name; | ||
var paramIndex = 0; | ||
var key; | ||
var paramVal; | ||
var paramCallbacks; | ||
var paramCalled; | ||
var i = 0 | ||
var name | ||
var paramIndex = 0 | ||
var key | ||
var paramVal | ||
var paramCallbacks | ||
var paramCalled | ||
return param(); | ||
return param() | ||
@@ -368,15 +413,15 @@ // process params in order | ||
function param() { | ||
if (i >= keys.length ) { | ||
return done(); | ||
if (i >= keys.length) { | ||
return done() | ||
} | ||
paramIndex = 0; | ||
key = keys[i++]; | ||
name = key.name; | ||
paramVal = ctx.params[name]; | ||
paramCallbacks = params[name]; | ||
paramCalled = called[name]; | ||
paramIndex = 0 | ||
key = keys[i++] | ||
name = key.name | ||
paramVal = ctx.params[name] | ||
paramCallbacks = params[name] | ||
paramCalled = called[name] | ||
if (paramVal === undefined || !paramCallbacks) { | ||
return param(); | ||
return param() | ||
} | ||
@@ -387,7 +432,7 @@ | ||
// restore value | ||
ctx.params[name] = paramCalled.value; | ||
ctx.params[name] = paramCalled.value | ||
// process next param, don't re-process | ||
// unless not match with the last called | ||
return param(); | ||
return param() | ||
} | ||
@@ -397,11 +442,6 @@ | ||
match: paramVal, | ||
value: paramVal, | ||
}; | ||
value: paramVal | ||
} | ||
return paramCallback().catch(function(e) { | ||
// signal to exit this param | ||
if (e === 'param') return param(); | ||
throw e; | ||
}); | ||
return paramCallback() | ||
} | ||
@@ -411,22 +451,22 @@ | ||
async function paramCallback() { | ||
var fn = paramCallbacks[paramIndex++]; | ||
var fn = paramCallbacks[paramIndex++] | ||
// acquire the reference of the outer variable for | ||
// later use, otherwise we may alter it by mistake | ||
var pcalled = paramCalled; | ||
var k = key; | ||
var pcalled = paramCalled | ||
var k = key | ||
// store updated value | ||
pcalled.value = ctx.params[k.name]; | ||
pcalled.value = ctx.params[k.name] | ||
if (!fn) return param(); | ||
if (typeof fn !== 'function') return param() | ||
try { | ||
await fn(ctx, paramCallback, paramVal, k.name); | ||
await fn(ctx, paramCallback, paramVal, k.name) | ||
} finally { | ||
// store updated value | ||
pcalled.value = ctx.params[k.name]; | ||
pcalled.value = ctx.params[k.name] | ||
} | ||
} | ||
}; | ||
} | ||
@@ -448,13 +488,13 @@ /** | ||
proto.use = function use(fn) { | ||
var offset = 0; | ||
var path = '/'; | ||
Router.prototype.use = function use(handler) { | ||
var offset = 0 | ||
var path = '/' | ||
// default path to '/' | ||
// disambiguate router.use([fn]) | ||
if (typeof fn !== 'function') { | ||
var arg = fn; | ||
// disambiguate router.use([handler]) | ||
if (typeof handler !== 'function') { | ||
var arg = handler | ||
while (Array.isArray(arg) && arg.length !== 0) { | ||
arg = arg[0]; | ||
arg = arg[0] | ||
} | ||
@@ -464,8 +504,8 @@ | ||
if (typeof arg !== 'function') { | ||
offset = 1; | ||
path = fn; | ||
offset = 1 | ||
path = handler | ||
} | ||
} | ||
var callbacks = flatten(slice.call(arguments, offset)); | ||
var callbacks = flatten(slice.call(arguments, offset)) | ||
@@ -477,3 +517,3 @@ if (callbacks.length === 0) { | ||
for (var i = 0; i < callbacks.length; i++) { | ||
var fn = callbacks[i]; | ||
var fn = callbacks[i] | ||
@@ -490,12 +530,12 @@ if (typeof fn !== 'function') { | ||
strict: false, | ||
end: false, | ||
}, fn); | ||
end: false | ||
}, fn) | ||
layer.route = undefined; | ||
layer.route = undefined | ||
this.middleware.push(layer); | ||
this.stack.push(layer) | ||
} | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -515,4 +555,4 @@ /** | ||
proto.route = function route(path) { | ||
var route = new Route(path); | ||
Router.prototype.route = function route(path) { | ||
var route = new Route(path) | ||
@@ -522,24 +562,24 @@ var layer = new Layer(path, { | ||
strict: this.strict, | ||
end: true, | ||
}, route.dispatch.bind(route)); | ||
end: true | ||
}, route.handle.bind(route)) | ||
layer.route = route; | ||
layer.route = route | ||
this.middleware.push(layer); | ||
return route; | ||
}; | ||
this.stack.push(layer) | ||
return route | ||
} | ||
// create Router#VERB functions | ||
methods.concat('all').forEach(function(method) { | ||
proto[method] = function(path) { | ||
var offset = 1; | ||
Router.prototype[method] = function(path) { | ||
var offset = 1 | ||
if (typeof path === 'function') { | ||
path = '/'; | ||
offset = 0; | ||
path = '/' | ||
offset = 0 | ||
} | ||
var route = this.route(path); | ||
route[method].apply(route, slice.call(arguments, offset)); | ||
return this; | ||
}; | ||
}); | ||
var route = this.route(path) | ||
route[method].apply(route, slice.call(arguments, offset)) | ||
return this | ||
} | ||
}) | ||
@@ -549,21 +589,7 @@ /** | ||
*/ | ||
proto.del = proto.delete; | ||
Router.prototype.del = Router.prototype.delete | ||
// append methods to a list of methods | ||
function appendMethods(list, addition) { | ||
for (var i = 0; i < addition.length; i++) { | ||
var method = addition[i]; | ||
if (list.indexOf(method) === -1) { | ||
list.push(method); | ||
} | ||
} | ||
} | ||
// get pathname of request context | ||
function getPathname(ctx) { | ||
try { | ||
return parseUrl(ctx).pathname; | ||
} catch (err) { | ||
return undefined; | ||
} | ||
return parseUrl(ctx).pathname | ||
} | ||
@@ -574,14 +600,14 @@ | ||
if (typeof url !== 'string' || url.length === 0 || url[0] === '/') { | ||
return undefined; | ||
return undefined | ||
} | ||
var searchIndex = url.indexOf('?'); | ||
var searchIndex = url.indexOf('?') | ||
var pathLength = searchIndex !== -1 | ||
? searchIndex | ||
: url.length; | ||
var fqdnIndex = url.substr(0, pathLength).indexOf('://'); | ||
: url.length | ||
var fqdnIndex = url.substr(0, pathLength).indexOf('://') | ||
return fqdnIndex !== -1 | ||
? url.substr(0, url.indexOf('/', 3 + fqdnIndex)) | ||
: undefined; | ||
: undefined | ||
} | ||
@@ -591,53 +617,36 @@ | ||
function gettype(obj) { | ||
var type = typeof obj; | ||
var type = typeof obj | ||
if (type !== 'object') { | ||
return type; | ||
return type | ||
} | ||
// inspect [[Class]] for objects | ||
return toString.call(obj) | ||
.replace(objectRegExp, '$1'); | ||
return toString.call(obj).replace(objectRegExp, '$1') | ||
} | ||
/** | ||
* Match path to a layer. | ||
* | ||
* @param {Layer} layer | ||
* @param {string} path | ||
* @private | ||
*/ | ||
function matchLayer(layer, path) { | ||
try { | ||
return layer.match(path); | ||
} catch (err) { | ||
return { matched: err }; | ||
} | ||
} | ||
// merge params with parent params | ||
function mergeParams(params, parent) { | ||
if (typeof parent !== 'object' || !parent) { | ||
return params; | ||
return params | ||
} | ||
// make copy of parent for base | ||
var obj = mixin({}, parent); | ||
var obj = mixin({}, parent) | ||
// simple non-numeric merging | ||
if (!(0 in params) || !(0 in parent)) { | ||
return mixin(obj, params); | ||
return mixin(obj, params) | ||
} | ||
var i = 0; | ||
var o = 0; | ||
var i = 0 | ||
var o = 0 | ||
// determine numeric gaps | ||
while (i in params) { | ||
i++; | ||
i++ | ||
} | ||
while (o in parent) { | ||
o++; | ||
o++ | ||
} | ||
@@ -647,11 +656,11 @@ | ||
for (i--; i >= 0; i--) { | ||
params[i + o] = params[i]; | ||
params[i + o] = params[i] | ||
// create holes for the merge when necessary | ||
if (i < o) { | ||
delete params[i]; | ||
delete params[i] | ||
} | ||
} | ||
return mixin(obj, params); | ||
return mixin(obj, params) | ||
} | ||
@@ -661,8 +670,8 @@ | ||
function restore(obj) { | ||
var props = new Array(arguments.length - 1); | ||
var vals = new Array(arguments.length - 1); | ||
var props = new Array(arguments.length - 1) | ||
var vals = new Array(arguments.length - 1) | ||
for (var i = 0; i < props.length; i++) { | ||
props[i] = arguments[i + 1]; | ||
vals[i] = obj[props[i]]; | ||
props[i] = arguments[i + 1] | ||
vals[i] = obj[props[i]] | ||
} | ||
@@ -673,3 +682,3 @@ | ||
for (var i = 0; i < props.length; i++) { | ||
obj[props[i]] = vals[i]; | ||
obj[props[i]] = vals[i] | ||
} | ||
@@ -679,7 +688,24 @@ } | ||
// send an OPTIONS response | ||
function sendOptionsResponse(ctx, options) { | ||
var body = options.join(','); | ||
ctx.set('Allow', body); | ||
ctx.body = body; | ||
/** | ||
* Send an OPTIONS response. | ||
* | ||
* @private | ||
*/ | ||
function sendOptionsResponse(ctx, methods) { | ||
if (ctx.respond !== false) { | ||
var options = Object.create(null) | ||
// build unique method map | ||
for (var i = 0; i < methods.length; i++) { | ||
options[methods[i]] = true | ||
} | ||
// construct the allow list | ||
var body = Object.keys(options).sort().join(', ') | ||
ctx.set('Allow', body) | ||
ctx.set('X-Content-Type-Options', 'nosniff') | ||
ctx.body = body | ||
} | ||
} |
@@ -8,3 +8,3 @@ /*! | ||
'use strict'; | ||
'use strict' | ||
@@ -16,4 +16,4 @@ /** | ||
var pathRegexp = require('path-to-regexp'); | ||
var debug = require('debug')('koa2-router:layer'); | ||
var pathRegexp = require('path-to-regexp') | ||
var debug = require('debug')('koa2-router:layer') | ||
@@ -25,3 +25,3 @@ /** | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var hasOwnProperty = Object.prototype.hasOwnProperty | ||
@@ -33,19 +33,23 @@ /** | ||
module.exports = Layer; | ||
module.exports = Layer | ||
function Layer(path, options, fn) { | ||
if (!(this instanceof Layer)) { | ||
return new Layer(path, options, fn); | ||
return new Layer(path, options, fn) | ||
} | ||
debug('new %o', path) | ||
var opts = options || {}; | ||
var opts = options || {} | ||
this.handler = fn; | ||
this.name = fn._name || fn.name || '<anonymous>'; | ||
this.regexp = pathRegexp(path, this.keys = [], opts); | ||
this.handler = fn | ||
this.name = fn._name || fn.name || '<anonymous>' | ||
this.regexp = pathRegexp(path, this.keys = [], opts) | ||
// temperary matched result for faster matching | ||
this.path = undefined | ||
this.params = undefined | ||
// set fast path flags | ||
this.regexp.fast_star = path === '*'; | ||
this.regexp.fast_slash = path === '/' && opts.end === false; | ||
this.regexp.fast_star = path === '*' | ||
this.regexp.fast_slash = path === '/' && opts.end === false | ||
} | ||
@@ -62,12 +66,13 @@ | ||
Layer.prototype.handle = async function handle(ctx, next) { | ||
var fn = this.handler; | ||
await fn(ctx, next); | ||
}; | ||
var fn = this.handler | ||
var owner = this.owner | ||
await fn.call(owner, ctx, next) | ||
} | ||
/** | ||
* Check if this route matches `path`, if so | ||
* populate `.params` and `.path` for temporary usage. | ||
* populate `.params`, `.path` properties for temporary usage. | ||
* | ||
* @param {String} path | ||
* @return {Object} | ||
* @return {Boolean} | ||
* @api private | ||
@@ -77,3 +82,3 @@ */ | ||
Layer.prototype.match = function match(path) { | ||
var match; | ||
var match | ||
@@ -83,7 +88,5 @@ if (path != null) { | ||
if (this.regexp.fast_slash) { | ||
return { | ||
params: {}, | ||
path: '', | ||
matched: true, | ||
}; | ||
this.params = {} | ||
this.path = '' | ||
return true | ||
} | ||
@@ -93,39 +96,37 @@ | ||
if (this.regexp.fast_star) { | ||
return { | ||
params: {'0': decode_param(path)}, | ||
path: path, | ||
matched: true, | ||
}; | ||
this.params = {'0': decode_param(path)} | ||
this.path = path | ||
return true | ||
} | ||
// match the path | ||
match = this.regexp.exec(path); | ||
match = this.regexp.exec(path) | ||
} | ||
if (!match) { | ||
return { matched: false }; | ||
this.params = undefined | ||
this.path = undefined | ||
return false | ||
} | ||
var keys = this.keys; | ||
var params = {}; | ||
// store values | ||
var result = { | ||
params: params, | ||
path: match[0], | ||
matched: true, | ||
}; | ||
this.params = {} | ||
this.path = match[0] | ||
// iterate matches | ||
var keys = this.keys | ||
var params = this.params | ||
for (var i = 1; i < match.length; i++) { | ||
var key = keys[i - 1]; | ||
var prop = key.name; | ||
var val = decode_param(match[i]); | ||
var key = keys[i - 1] | ||
var prop = key.name | ||
var val = decode_param(match[i]) | ||
if (val !== undefined || !(hasOwnProperty.call(params, prop))) { | ||
params[prop] = val; | ||
params[prop] = val | ||
} | ||
} | ||
return result; | ||
}; | ||
return true | ||
} | ||
@@ -142,15 +143,15 @@ /** | ||
if (typeof val !== 'string' || val.length === 0) { | ||
return val; | ||
return val | ||
} | ||
try { | ||
return decodeURIComponent(val); | ||
return decodeURIComponent(val) | ||
} catch (err) { | ||
if (err instanceof URIError) { | ||
err.message = 'Failed to decode param \'' + val + '\''; | ||
err.status = err.statusCode = 400; | ||
err.message = 'Failed to decode param \'' + val + '\'' | ||
err.status = err.statusCode = 400 | ||
} | ||
throw err; | ||
throw err | ||
} | ||
} |
181
lib/route.js
@@ -8,3 +8,3 @@ /*! | ||
'use strict'; | ||
'use strict' | ||
@@ -16,6 +16,6 @@ /** | ||
var debug = require('debug')('koa2-router:route'); | ||
var flatten = require('array-flatten'); | ||
var methods = require('methods'); | ||
var Layer = require('./layer'); | ||
var debug = require('debug')('koa2-router:route') | ||
var flatten = require('array-flatten') | ||
var methods = require('methods') | ||
var Layer = require('./layer') | ||
@@ -27,4 +27,4 @@ /** | ||
var slice = Array.prototype.slice; | ||
var toString = Object.prototype.toString; | ||
var slice = Array.prototype.slice | ||
var toString = Object.prototype.toString | ||
@@ -36,3 +36,3 @@ /** | ||
module.exports = Route; | ||
module.exports = Route | ||
@@ -47,9 +47,13 @@ /** | ||
function Route(path) { | ||
this.path = path; | ||
this.middleware = []; | ||
if (!(this instanceof Route)) { | ||
return new Route(path) | ||
} | ||
debug('new %o', path) | ||
this.path = path | ||
this.stack = [] | ||
debug('new route %o', path) | ||
// route handlers for various http methods | ||
this.methods = {}; | ||
this.methods = {} | ||
} | ||
@@ -64,13 +68,13 @@ | ||
if (this.methods._all) { | ||
return true; | ||
return true | ||
} | ||
var name = method.toLowerCase(); | ||
var name = method.toLowerCase() | ||
if (name === 'head' && !this.methods['head']) { | ||
name = 'get'; | ||
name = 'get' | ||
} | ||
return Boolean(this.methods[name]); | ||
}; | ||
return Boolean(this.methods[name]) | ||
} | ||
@@ -82,8 +86,8 @@ /** | ||
Route.prototype._options = function _options() { | ||
var methods = Object.keys(this.methods); | ||
Route.prototype._methods = function _methods() { | ||
var methods = Object.keys(this.methods) | ||
// append automatic head | ||
if (this.methods.get && !this.methods.head) { | ||
methods.push('head'); | ||
methods.push('head') | ||
} | ||
@@ -93,7 +97,7 @@ | ||
// make upper case | ||
methods[i] = methods[i].toUpperCase(); | ||
methods[i] = methods[i].toUpperCase() | ||
} | ||
return methods; | ||
}; | ||
return methods | ||
} | ||
@@ -105,40 +109,59 @@ /** | ||
Route.prototype.dispatch = function dispatch(ctx, done) { | ||
var idx = 0; | ||
var middleware = this.middleware; | ||
if (middleware.length === 0) { | ||
return done(); | ||
Route.prototype.dispatch = function dispatch(ctx) { | ||
var idx = 0 | ||
var stack = this.stack | ||
if (stack.length === 0) { | ||
throw 'route' | ||
} | ||
var method = ctx.method.toLowerCase(); | ||
var method = ctx.method.toLowerCase() | ||
if (method === 'head' && !this.methods['head']) { | ||
method = 'get'; | ||
method = 'get' | ||
} | ||
ctx.route = this; | ||
ctx.route = this | ||
return next(); | ||
return next() | ||
function next() { | ||
var layer = middleware[idx++]; | ||
if (!layer) { | ||
return done(); | ||
var layer | ||
var match | ||
// find next matching layer | ||
while (match !== true && idx < stack.length) { | ||
layer = stack[idx++] | ||
match = !layer.method || layer.method === method | ||
} | ||
if (layer.method && layer.method !== method) { | ||
return next(); | ||
// no match | ||
if (match !== true) { | ||
throw 'route' | ||
} | ||
return layer.handle(ctx, next).catch(function(e) { | ||
// signal to exit route | ||
if (e === 'route') { | ||
return done(); | ||
} | ||
return layer.handle(ctx, next) | ||
} | ||
} | ||
// signal to exit router | ||
throw e; | ||
}); | ||
/** | ||
* handle route middleware | ||
* @private | ||
*/ | ||
Route.prototype.handle = async function handle(ctx, upstream) { | ||
if (typeof upstream !== 'function') { | ||
throw new TypeError('argument next(upstream) is required') | ||
} | ||
}; | ||
try { | ||
await this.dispatch(ctx) | ||
} catch(e) { | ||
if (e === 'route') { | ||
await upstream() | ||
} else { | ||
throw e | ||
} | ||
} | ||
} | ||
/** | ||
@@ -152,9 +175,9 @@ * Add a handler for all HTTP verbs to this route. | ||
* | ||
* function check_something(req, res, next){ | ||
* next(); | ||
* }; | ||
* function check_something(ctx, next) { | ||
* next() | ||
* } | ||
* | ||
* function validate_user(req, res, next){ | ||
* next(); | ||
* }; | ||
* function validate_user(ctx, next) { | ||
* next() | ||
* } | ||
* | ||
@@ -164,5 +187,5 @@ * route | ||
* .all(check_something) | ||
* .get(function(req, res, next){ | ||
* res.send('hello world'); | ||
* }); | ||
* .get(function(ctx, next) { | ||
* ctx.body = 'hello world' | ||
* }) | ||
* | ||
@@ -175,34 +198,38 @@ * @param {function} handler | ||
Route.prototype.all = function all() { | ||
var handlers = flatten(slice.call(arguments)); | ||
var handlers = flatten(slice.call(arguments)) | ||
if (handlers.length === 0) { | ||
throw new TypeError('argument handler is required') | ||
} | ||
for (var i = 0; i < handlers.length; i++) { | ||
var handler = handlers[i]; | ||
var handler = handlers[i] | ||
if (typeof handler !== 'function') { | ||
var type = toString.call(handler); | ||
var type = toString.call(handler) | ||
var msg = 'Route.all() requires a callback function but got a ' + type | ||
throw new TypeError(msg); | ||
throw new TypeError(msg) | ||
} | ||
var layer = new Layer('/', {}, handler); | ||
layer.method = undefined; | ||
var layer = new Layer('/', {}, handler) | ||
layer.method = undefined | ||
this.methods._all = true; | ||
this.middleware.push(layer); | ||
this.methods._all = true | ||
this.stack.push(layer) | ||
} | ||
return this; | ||
}; | ||
return this | ||
} | ||
methods.forEach(function(method) { | ||
Route.prototype[method] = function() { | ||
var handlers = flatten(slice.call(arguments)); | ||
var handlers = flatten(slice.call(arguments)) | ||
for (var i = 0; i < handlers.length; i++) { | ||
var handler = handlers[i]; | ||
var handler = handlers[i] | ||
if (typeof handler !== 'function') { | ||
var type = toString.call(handler); | ||
var type = toString.call(handler) | ||
var msg = 'Route.' + method + '() requires a callback function but got a ' + type | ||
throw new TypeError(msg); | ||
throw new TypeError(msg) | ||
} | ||
@@ -212,12 +239,12 @@ | ||
var layer = new Layer('/', {}, handler); | ||
layer.method = method; | ||
var layer = new Layer('/', {}, handler) | ||
layer.method = method | ||
this.methods[method] = true; | ||
this.middleware.push(layer); | ||
this.methods[method] = true | ||
this.stack.push(layer) | ||
} | ||
return this; | ||
}; | ||
}); | ||
return this | ||
} | ||
}) | ||
@@ -227,2 +254,2 @@ /** | ||
*/ | ||
Route.prototype.del = Route.prototype.delete; | ||
Route.prototype.del = Route.prototype.delete |
{ | ||
"name": "koa2-router", | ||
"version": "1.0.6", | ||
"version": "1.0.7", | ||
"description": "A express-liked router component for koa2", | ||
@@ -20,3 +20,3 @@ "main": "index.js", | ||
"array-flatten": "^2.1.2", | ||
"debug": "^4.1.0", | ||
"debug": "^4.1.1", | ||
"depd": "^2.0.0", | ||
@@ -26,3 +26,3 @@ "methods": "^1.1.2", | ||
"path-to-regexp": "^2.4.0", | ||
"setprototypeof": "^1.1.0", | ||
"setprototypeof": "^1.1.1", | ||
"utils-merge": "^1.0.1" | ||
@@ -32,4 +32,4 @@ }, | ||
"delay": "^4.1.0", | ||
"http-errors": "^1.7.1", | ||
"koa": "^2.6.2", | ||
"http-errors": "^1.7.2", | ||
"koa": "^2.7.0", | ||
"koa-morgan": "^1.0.1" | ||
@@ -36,0 +36,0 @@ }, |
@@ -89,2 +89,3 @@ # koa2-router | ||
* Thanks to the [koa-router](https://github.com/alexmingoia/koa-router) project | ||
* Thanks to the [router](https://github.com/pillarjs/router) project | ||
@@ -91,0 +92,0 @@ ## License |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27675
867
96
Updateddebug@^4.1.1
Updatedsetprototypeof@^1.1.1