koa2-router
Advanced tools
Comparing version 1.0.9 to 1.1.0
101
lib/index.js
@@ -35,2 +35,3 @@ /*! | ||
var toString = Object.prototype.toString | ||
var featureSymbol = Symbol('koa2-router') | ||
@@ -50,3 +51,3 @@ /** | ||
/** | ||
* Add baseUrl/params to the | ||
* Add baseUrl/params/matched to the | ||
* prototype of request and context in app | ||
@@ -64,3 +65,3 @@ * | ||
const request = app.request | ||
if (!request.hasOwnProperty('baseUrl')) { | ||
if (!request.hasOwnProperty(featureSymbol)) { | ||
Object.defineProperties(request, { | ||
@@ -78,2 +79,6 @@ baseUrl: { | ||
set: function(matched) { return this.req.matched = matched } | ||
}, | ||
[featureSymbol]: { | ||
value: 'koa2-router', | ||
writable: false, | ||
} | ||
@@ -85,2 +90,3 @@ }) | ||
.access('matched') | ||
.getter(featureSymbol) | ||
} | ||
@@ -90,2 +96,3 @@ } | ||
/** | ||
* @alias module:koa2-router | ||
* Initialize a new `Router` with the given `options`. | ||
@@ -108,2 +115,3 @@ * | ||
patchPrototype(ctx.app) | ||
// initialize ctx.req with `originalUrl`, `baseUrl`, `params` | ||
@@ -227,34 +235,12 @@ const req = ctx.req | ||
// store options for OPTIONS request | ||
// only used if OPTIONS request | ||
var matched = [] | ||
// save point | ||
var restoreCtx = restore(undefined, ctx, 'baseUrl', 'params', 'url') | ||
// save point 1 | ||
var restore1 = restore(ctx, 'baseUrl', 'params', 'url') | ||
// detect router match | ||
var notMatched = false | ||
try { | ||
// dispatch into the current router | ||
await this.dispatch(ctx, matched, function() { | ||
notMatched = true | ||
}) | ||
await this.dispatch(ctx, upstream) | ||
} finally { | ||
// restore point 1 | ||
restore1() | ||
// restore point | ||
restoreCtx() | ||
} | ||
if (notMatched) { | ||
if (matched.length) ctx.matched = matched | ||
// save point 2 | ||
var restore2 = restore(ctx, 'baseUrl', 'params', 'url') | ||
try { | ||
// go upstream | ||
await upstream() | ||
} finally { | ||
// restore point 2 | ||
restore2() | ||
} | ||
} | ||
} | ||
@@ -325,5 +311,16 @@ | ||
Router.prototype.dispatch = function dispatch(ctx, matched, done) { | ||
Router.prototype.dispatch = function dispatch(ctx, done) { | ||
var self = this | ||
var matched = [] | ||
// collect methods of which route matches | ||
// the path but not method | ||
done = wrap(done, function(fn) { | ||
if (matched.length) ctx.matched = matched | ||
return fn() | ||
}) | ||
// restore properties in context | ||
done = restore(done, ctx, 'baseUrl', 'params', 'url') | ||
debug('dispatching %s %s%s', ctx.method, ctx.baseUrl, ctx.url) | ||
@@ -426,4 +423,23 @@ | ||
if (route) { | ||
return layer.handle(ctx, next) | ||
// .all(.get/.post/...) middlewares | ||
// call next() after layer.handle | ||
// finishes its route's dispatching | ||
var isNextCalled = false | ||
function onNextCalled() { | ||
isNextCalled = true | ||
} | ||
function tryNext() { | ||
// only call next if next() is called | ||
// within route middlewares, and ctx | ||
// is not responded | ||
if (!isNextCalled || isResponded(ctx)) return | ||
return next() | ||
} | ||
return layer.handle(ctx, onNextCalled) | ||
.then(tryNext) | ||
} | ||
// .use middlewares | ||
return trim_prefix(layer, layerPath, path, next) | ||
@@ -742,3 +758,3 @@ }).catch(function(e) { | ||
// save obj props for restoring after a while | ||
function restore(obj) { | ||
function restore(fn, obj) { | ||
var props = new Array(arguments.length - 1) | ||
@@ -757,2 +773,4 @@ var vals = new Array(arguments.length - 1) | ||
} | ||
return fn && fn.apply(this, arguments) | ||
} | ||
@@ -791,1 +809,20 @@ } | ||
} | ||
/** | ||
* Wrap a function | ||
* | ||
* @private | ||
*/ | ||
function wrap(old, fn) { | ||
return function proxy() { | ||
var args = new Array(arguments.length + 1) | ||
args[0] = old | ||
for (var i = 0, len = arguments.length; i < len; i++) { | ||
args[i + 1] = arguments[i] | ||
} | ||
return fn.apply(this, args) | ||
} | ||
} |
@@ -106,12 +106,3 @@ /*! | ||
// detect route match | ||
var notMatched = false | ||
await this.dispatch(ctx, function() { | ||
notMatched = true | ||
}) | ||
if (notMatched) { | ||
await upstream() | ||
} | ||
await this.dispatch(ctx, upstream) | ||
} | ||
@@ -118,0 +109,0 @@ |
{ | ||
"name": "koa2-router", | ||
"version": "1.0.9", | ||
"version": "1.1.0", | ||
"description": "A express-liked router component for koa2", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
153
README.md
@@ -6,2 +6,4 @@ # koa2-router | ||
## Features | ||
## Getting Started | ||
@@ -22,3 +24,3 @@ You can follow the instructions below to setup a router component in koa@2 environment. | ||
* Import component | ||
``` | ||
```javascript | ||
const Router = require('koa2-router'); | ||
@@ -28,8 +30,8 @@ ``` | ||
* Create a router | ||
```javascript | ||
const router = new Router(opts); | ||
``` | ||
const router = Router(opts); | ||
``` | ||
* Mount a router to a koa application | ||
``` | ||
```javascript | ||
const app = new Koa(); | ||
@@ -40,10 +42,10 @@ app.use(router); | ||
* Mount a router to another router | ||
``` | ||
const router2 = Router(); | ||
```javascript | ||
const router2 = new Router(); | ||
router2.use(...); | ||
router.use('/users', router2); | ||
router.all('/users', router2); | ||
``` | ||
* Use http method to handle request | ||
``` | ||
```javascript | ||
router2.get('/:userId', ctx => ctx.body = `hello user ${ctx.params.userId}`); | ||
@@ -53,11 +55,11 @@ ``` | ||
* Use params middleware like express | ||
``` | ||
```javascript | ||
const router3 = Router(); | ||
router3.params('userName', (ctx, next, userName, key) => (ctx[key] = userName, next())) | ||
.get('/:userName', async ctx => ctx.body = await ctx.db.getStaffFromName(ctx.userName)); | ||
router.use('/staff', router3); | ||
router.all('/staff', router3); | ||
``` | ||
* Use route to make a rest api | ||
``` | ||
```javascript | ||
const route = router3.route('/:id'); | ||
@@ -69,3 +71,3 @@ route | ||
* exit route or router without exception | ||
``` | ||
```javascript | ||
route | ||
@@ -79,9 +81,127 @@ .all(async (ctx, next) => { | ||
route3.use('/admin', (ctx, next) => { | ||
if (ctx.authenticate.userRoles.includes('admin')) return next(); | ||
else throw 'router'; // exit this router3 without any exception | ||
}) | ||
route3.all('/admin', (ctx, next) => { | ||
if (ctx.authenticate.userRoles.includes('admin')) return next(); | ||
else throw 'router'; // exit this router3 without any exception | ||
}) | ||
.post('/posts', async ctx => ctx.body = await ctx.db.createPost(ctx.request.body, ctx.authenticate.userId)); | ||
``` | ||
* implement Method Not Allowed and Not Implemented | ||
```javascript | ||
router.all('/api', router3, router3.allowMethods(opts)) | ||
``` | ||
`opts` the allowMethods options | ||
`opts.throw` [boolean] default false, set to true to throw errors | ||
`opts.methodNotAllowed` [function(ctx, methods)] set if throw a custom 405 error | ||
`opts.notImplemented` [function(ctx)] set if throw a custom 501 error | ||
## Nested Router Spec | ||
In this module, router is a specific **function** instance which can be constructed via `router = new Router(opts)` or `router = Router(opts)`, and can be directly used as a `Koa.Middleware` function - `app.use(router)`. | ||
We create a router model called **Express Liked Router Model**. The router constructed via this mechanism, implements everything that `express.Router` also dose, like `Router.use()`, `Router[method]()` `Router.params()` `Router.route()`. | ||
But there is an issue about that mode, how nested router stack being built for an asynchronized middleware system. | ||
Nested routers are supported, but in a different way. Considering the entering and exiting order of the stack, we have consulted and borrowed the middlewares in Golang: within that a new `Group` midleware is presented, and it can make a branching stack. So we borrowed this design and setup new rules in nested routers in order to constraint excuting orders: | ||
1. Middleares using `.use` only insert pure `middlewares` to the original stack | ||
> in `Router.use(path, middlewares)`, `middlewares` are inserted into | ||
> the parent's middlewares thus when the last one invokes `next`, it | ||
> will continue `enter` the next one of the parent router, until all | ||
> things done, then it will `leave` from the bottom to the top of the | ||
> parent router's stack | ||
Let's see an example | ||
```js | ||
var router = new Router('A') | ||
var nested = new Router('B') | ||
router.use(async (ctx, next) => { | ||
console.log('enter parent') | ||
await next() | ||
console.log('leave parent') | ||
}) | ||
// use `.use so nested mw is bundled together with the parent` | ||
router.use('/stuff', nested) | ||
router.use(async (ctx, next) => { | ||
console.log('prepare') | ||
await next() | ||
console.log('post') | ||
}) | ||
router.use(ctx => { | ||
console.log('output body') | ||
ctx.body = 'success' | ||
}) | ||
nested.use(async (ctx, next) => { | ||
console.log('enter nested') | ||
await next() | ||
console.log('leave nested') | ||
}) | ||
``` | ||
**GET /stuff** and watch the console | ||
```bash | ||
> enter parent | ||
> enter nested | ||
> prepare | ||
> output body | ||
> post | ||
> leave nested | ||
> leave parent | ||
> HTTP/1.1 200 OK | ||
> success | ||
``` | ||
2. Middlewares using `[method]` `.all` or `.route` makes a branching stack of route nested in the parent stack | ||
> in this situation, middlewares are bundled into another one, and | ||
> if the one is matched both in method & route, calling `next` in the | ||
> last middlewares of the nested router will `leave` the nested router > stack from bottom to the top first, and then if nothing is responded > before that, it enters the next middleware of the parent stack | ||
Let's see another example, almost the same as above one | ||
```js | ||
var router = new Router('A') | ||
var nested = new Router('B') | ||
router.use(async (ctx, next) => { | ||
console.log('enter parent') | ||
await next() | ||
console.log('leave parent') | ||
}) | ||
// use `.all` instead of `.use` | ||
router.all('/stuff', nested) | ||
router.use(async (ctx, next) => { | ||
console.log('prepare') | ||
await next() | ||
console.log('post') | ||
}) | ||
router.use(ctx => { | ||
console.log('output body') | ||
ctx.body = 'success' | ||
}) | ||
nested.use(async (ctx, next) => { | ||
console.log('enter nested') | ||
await next() | ||
console.log('leave nested') | ||
}) | ||
``` | ||
**GET /stuff** and watch the console | ||
```bash | ||
> enter parent | ||
> enter nested | ||
> leave nested | ||
> prepare | ||
> output body | ||
> post | ||
> leave parent | ||
> HTTP/1.1 200 OK | ||
> success | ||
``` | ||
The order of entering/leaving differs from the above example, because we make a branching stack nested in the router, and it will leave the branching stack before go over the next. It is just like the `Group` in the project [gobwas/glob](https://github.com/gobwas/glob) powered by golang | ||
## Running tests | ||
@@ -97,2 +217,3 @@ You should clone thie repository down to your file system, and execute | ||
* Thanks to the [router](https://github.com/pillarjs/router) project | ||
* Thanks to the [gobwas/glob](https://github.com/gobwas/glob) project | ||
@@ -99,0 +220,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
35154
985
217