Comparing version 1.4.1 to 2.0.0
{ | ||
"name": "take-five", | ||
"version": "1.4.1", | ||
"version": "2.0.0", | ||
"description": "Very minimal JSON-REST server", | ||
@@ -30,10 +30,13 @@ "main": "take-five.js", | ||
"dependencies": { | ||
"fast-safe-stringify": "^1.0.9", | ||
"concat-stream": "^1.6.2", | ||
"fast-safe-stringify": "^2.0.3", | ||
"wayfarer": "^6.1.5" | ||
}, | ||
"devDependencies": { | ||
"dependency-check": "^2.6.0", | ||
"standard": "^10.0.2", | ||
"tap": "^10.5.1" | ||
"dependency-check": "^3.1.0", | ||
"nanorequest": "^1.1.0", | ||
"selfsigned": "^1.10.2", | ||
"standard": "^11.0.1", | ||
"tap": "^11.1.3" | ||
} | ||
} |
237
README.md
# take-five | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/scriptoLLC/take-five.svg)](https://greenkeeper.io/) | ||
[![Build Status](https://travis-ci.org/scriptoLLC/take-five.svg?branch=master)](https://travis-ci.org/scriptoLLC/take-five) [![NSP Status](https://nodesecurity.io/orgs/scriptollc/projects/24857fc4-2472-446e-ac2d-5a0f5913503d/badge)](https://nodesecurity.io/orgs/scriptollc/projects/24857fc4-2472-446e-ac2d-5a0f5913503d) [![Coverage Status](https://coveralls.io/repos/github/scriptoLLC/take-five/badge.svg?branch=master)](https://coveralls.io/github/scriptoLLC/take-five?branch=master) | ||
@@ -18,7 +17,7 @@ | ||
```js | ||
const five = require('take-five') | ||
const server = five() | ||
server.get('/', (req, res) => res.send('Hello, world')) | ||
server.post('/', (req, res) => res.send(req.body)) | ||
server.listen(3000) | ||
const Five = require('take-five') | ||
const five = new Five() | ||
five.get('/', async (req, res, ctx) => ctx.send('Hello, world')) | ||
five.post('/', async (req, res, ctx) => ctx.send(req.body)) | ||
five.listen(3000) | ||
``` | ||
@@ -33,30 +32,131 @@ | ||
## Routing and route-handlers | ||
In lieu of pre-set middleware, routes handlers can be arrays of functions that will | ||
be iterated over asynchronously. To simplify handling of these handlers, | ||
it is expected that the handlers will return [thenables](https://promisesaplus.com/), or terminate the response | ||
stream. This means any promises library should work (including the built in one), | ||
as well as using the `async` function keyword. | ||
**If you do not return a [thenable](https://promisesaplus.com/), handler processing will stop in that function** | ||
e.g.: | ||
```js | ||
function badSetContentHeader (req, res, ctx) { | ||
res.setHeader('x-no-way', 'this is gonna do nothing') | ||
} | ||
function goodSetContentHeader (req, res, ctx) { | ||
return new Promise((resolve) => { | ||
res.setHeader('x-yes-way', 'this is gonna do everything!') | ||
resolve() | ||
}) | ||
} | ||
function sendReply (req, res, ctx) { | ||
ctx.send('beep!') | ||
} | ||
five.get('/nope', [badSetContentHeader, sendReply]) | ||
five.get('/yup', [goodSetContentHeader, sendReply) | ||
``` | ||
since `badSetContentHeader` doesn't return a [`thenable`](https://promisesaplus.com/), take-five will not | ||
know that it needs to call the `sendReply` function in the handler list for the `/nope` | ||
route. | ||
If you have either closed the response stream, or `reject`ed the [thenable](https://promisesaplus.com/) returned | ||
from your route handler, the next route will not be called. In the case that you have | ||
`reject`ed the [thenable](https://promisesaplus.com/), the error handler will be invoked as well. If you have | ||
closed the response stream, the server assumes you were done processing the request | ||
and will just ignore the remaning functions in the queue. | ||
By default, `get`, `post`, `put`, `delete`, `options` and `patch` will be available | ||
for routing, but this can be changed by providing an array of methods on the options | ||
hash when instatiating a new TakeFive prototype. | ||
### Examples | ||
#### Using async/await | ||
```js | ||
five.handleError = (err, req, res, ctx) => { | ||
ctx.err(err.statusCode, err.message) | ||
} | ||
five.get('/:secretdata', [ | ||
async (req, res, ctx) => { | ||
try { | ||
const session = await isAuthorized(req.headers.Authorization) | ||
ctx.session = session | ||
} catch (err) { | ||
err.statusCode = 401 | ||
throw err | ||
} | ||
}, | ||
async (res, res, ctx) => { | ||
try { | ||
const data = await getResource(ctx.params.secretdata, ctx.session) | ||
ctx.send(data) | ||
} catch (err) { | ||
err.statusCode = 500 | ||
reject(err) | ||
} | ||
} | ||
]) | ||
``` | ||
#### Using a "then"-able | ||
```js | ||
five.get('/:secretdata', [ | ||
(req, res, ctx) => { | ||
return new Promise((resolve, reject) => { | ||
isAuthorized(req.headers.Authorization, (err, session) => { | ||
if (err) { | ||
ctx.err(401, 'Not Authorized') | ||
return reject(new Error('Not authorized')) | ||
} | ||
ctx.session = session | ||
resolve() | ||
}) | ||
}) | ||
}, | ||
(res, res, ctx) => { | ||
return new Promise((resolve, reject) => { | ||
getResource(ctx.params.secretdata, ctx.session, (err, data) => { | ||
if (err) { | ||
ctx.err(500, 'Server error') | ||
return reject(new Error('server error')) | ||
} | ||
ctx.send(data) | ||
resolve() | ||
}) | ||
}) | ||
} | ||
]) | ||
``` | ||
## API | ||
### `five(opts?:object):object` | ||
### `Five(opts?:object):object` | ||
Create and return a new HTTP server object. | ||
* `opts.maxPost?:number`: the max size for a payload. Default: 512kb | ||
* `opts.cors?:object` | ||
* `opts.cors.headers?:array(string)`: an array of headers to accept besides the default. Default: `Content-Type`, `Accept`, `X-Requested-With` | ||
* `opts.cors.origin?:string`: What origin(s) are accepted. Deafult: `*` | ||
* `opts.cors.credentials?:boolean`: Allow or deny credentials. Default: `true` | ||
* `opts.cores.methods?array(string)`: an array of methods to accept besides the default. Default: `PUT`, `POST`, `DELETE`, `GET`, `OPTIONS` | ||
* `opts.allowHeaders?:array(string)`: an array of headers to accept besides the default. Default: `Content-Type`, `Accept`, `X-Requested-With` | ||
* `opts.allowOrigin?:string`: What origin(s) are accepted. Deafult: `*` | ||
* `opts.allowCredentials?:boolean`: Allow or deny credentials. Default: `true` | ||
* `opts.allowContentTypes?:string|string[]`: What content types are allowed to be used when sending data to the server. Default: `['application/json']`. Note: This is additive, so `application/json` will ALWAYS be allowed. | ||
* `opts.allowMethods?array(string)`: an array of methods to accept besides the default. Default: `PUT`, `POST`, `DELETE`, `GET`, `OPTIONS`, `PATCH` | ||
* `opts.methods?array(string)`: An array of methods to create route handlers for. Default: `PUT`, `POST`, `DELETE`, `GET`, `OPTIONS`, `PATCH` | ||
* `opts.http?object`: options for `http(s).createServer`. If you supply `key`, | ||
`cert` and `ca` as options, it will assume you are trying to create an https server` | ||
### `Five#use(middleware:function)` | ||
Add a new middleware to the stack. Middleware will be processed in the order in | ||
which they are added, which means they will be run after the built-in middleware. | ||
`Access-Control-Allow-Headers` and `Access-Control-Allow-Methods` can also be changed during runtime | ||
by setting `allowHeaders` and `allowMethods` respectively. | ||
* `middleware(request:object, response:object, next:function):function` -You must either call `next` or send data to the client when you are finshed. | ||
Since the middleware signature is the same express/restify, you *might* be able to use existing middleware with take-five, but ymmv. | ||
### `Five#router(namespace:string):object` | ||
Namespace routes. All routes defined off this router will be prefixed with the supplied | ||
namespace. The methods have the same signature as the router provided. | ||
### `Five#get(route:string, handler:(function|array(function)))` | ||
### `Five#post(route:string, handler:(function|array(function)))` | ||
### `Five#put(route:string, handler:(function|array(function)))` | ||
### `Five#delete(route:string, handler:(function|array(function)))` | ||
#### `Five#get(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
#### `Five#post(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
#### `Five#put(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
#### `Five#patch(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
#### `Five#delete(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
#### `Five#options(route:string, handler:(function|array(function)), routeOpts?:object)` | ||
Add a new route to the server. Routes may be added after the server has been | ||
@@ -67,35 +167,68 @@ started. You can supply either a single function or an array of functions to call. | ||
* `route:string` A [wayfarer](https://github.com/yoshuawuyts/wayfarer) route definition. | ||
* `handler(request:object, response:object, next:function):function`: The handler for this route. | ||
* `handler(request:object, response:object, ctx:object):function`: The handler for this route. | ||
* `routeOpts?:object` - overrides for this specific chain of handlers | ||
* `maxPost:number` - set the maximum size of a payload for this set of handlers | ||
* `allowedContentTypes:string|string[]` - add new allowable content-types for this set of handlers | ||
Since this is an augmented instance of [http.Server](https://nodejs.org/api/http.html#http_class_http_server) | ||
all methods and properties are available on this as well. | ||
#### `ctx:object` | ||
* `query?:object`: query parameters from the URL (if any) | ||
* `params?:object`: Named parameters from the route definition as provided by wayfarer | ||
* `body?:object`: The parsed JSON body available on POST requests | ||
* `send(statusCode?:number, body?:(string|object)):function`: Send a response. | ||
* `err(statusCode?:number, message?:string):function`: Send an error. If no status code is provided it will default to a 500 error. If no message is provided, it will use the default message for that status code. The message will be wrapped in a JSON object under the key `message` | ||
* `next():function`: Go to the next handler | ||
### `request:object` | ||
The [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) | ||
object extended with: | ||
The `ctx` object can also be extended to contain user defined objects, through | ||
setting `this.ctx` to an object. The object will be copied over using `Object.assign`. | ||
* `params?:object`: query parameters from the URL (if any) | ||
* `urlParams?:object`: Named parameters from the route definition as provided by wayfarer | ||
* `body?:object`: The parsed JSON body available on POST requests | ||
The keys from above will overwrite any keys you provide named the same. | ||
### `response:object` | ||
The [`http.ServerResponse`](https://nodejs.org/api/http.html#http_class_http_serverresponse) | ||
object augmented with two additional methods. The defaults for sending messages are | ||
`content-type: application/json` and `statusCode: 200`. The header may be overridden by | ||
calling `res.header`. The statusCode can be provided when calling the `send` method. | ||
#### `Five#handleError(err:Error, req:Request, res:Response, ctx:Context)` | ||
This function is invoked when either an error object is passed to the `ctx.next` | ||
method, or the `then`-able function's `reject` handler is invoked. | ||
* `send(statusCode?:number, body?:(string|object)):function`: Send a response. | ||
* `err(statusCode?:number, message?:string):function`: Send an error. If no status code is provided it will default to a 500 error. If no message is provided, it will use the default message for that status code. The message will be wrapped in a JSON object under the key `message` | ||
This is a no-op by default, allowing you to customize it's behavior. | ||
### `next:function` | ||
If you are done processing the request, but you want a later handler to be able to modify the response, call next. This will invoke the next handler in the stack. If there are no more handlers left, it will call `res.end()` and send the response as is. If you want to immediately send the response, you can call `res.end`, `res.send` or `res.err` directly. | ||
#### `Five#listen(port:number, handle?:function)` | ||
Start listening for requests and call the optional callback when you've started | ||
listening | ||
#### `Five.addParser(type:string, parser:functon):void` | ||
Add a new content parser to the parsers list. By default there is only a single | ||
parser (`application/json`: JSON.parser). This can be overridden with a custom | ||
JSON parser if you'd like. | ||
#### `Five#close()` | ||
Shutdown the underlying server | ||
### Getters/Setters | ||
#### `Five.server` | ||
The underlying http(s) server from node can be accessed directly. This is non-writeable | ||
#### `Five.maxPost` | ||
Globally control the maximum payload size after creation | ||
#### `Five.allowContentTypes` | ||
Add new allowable content types for clients to send data with. You can use either | ||
an array of strings or a string | ||
#### `Five.allowHeaders` | ||
Set a new allowable header or headers for CORS requests. You can use either an | ||
array of strings or a string. | ||
#### `Five.allowMethods` | ||
Set a new allowable method for CORS requests. | ||
#### `Five.ctx` | ||
Add new keys to the ctx objects | ||
## Do we need another REST server? | ||
Probably not, but [`restify`](http://restify.com), [`hapi`](http://hapijs.com) and [`express`](http://expressjs.com) are all over-kill on the types of services I'm building for the most part. | ||
* Setting up CORS is difficult or laborious: most REST services need to support CORS, this should be enabled by default (and easily configurable) | ||
* It has no need to accept anything other than `application/json` payloads, and doesn't need the cruft associated with other payload types. | ||
* By default it woill respond with `application/json` as well, but allow it be override-able if needed | ||
* It has no need to accept anything other than `application/json` payloads, but you can easily extend it to | ||
* By default it will respond with `application/json` as well, but allow it be override-able if needed | ||
* Should be trivial to reject large payloads to prevent DOS attacks | ||
* Each route should have the ability to have multiple placeholders, regardless of the payload type | ||
* It shouldn't mutate the built-in request and response objects | ||
* It should be as fast as possible | ||
@@ -106,11 +239,3 @@ | ||
There are some scripts used for the (extremely reductive) benchmarking in `/benchmark`. Using my Core-i7, I get the following data using `wrk -t12 -c400 -d30s http://localhost:3000/test`. You might see different results. As with all benchmarks, these are likely indicative of nothing! | ||
``` | ||
take-five: Requests/sec: 20296.63 | ||
express: Requests/sec: 10974.18 | ||
restify: Requests/sec: 9201.09 | ||
``` | ||
## License | ||
Copyright © 2016 Scripto LLC, Todd Kennedy. Reuse permitted under the Apache-2.0 license | ||
Copyright © 2018 Scripto LLC, Todd Kennedy. Reuse permitted under the Apache-2.0 license |
321
take-five.js
@@ -1,64 +0,301 @@ | ||
const path = require('path') | ||
const http = require('http') | ||
const https = require('https') | ||
const querystring = require('querystring') | ||
const Buffer = require('buffer').Buffer | ||
const stringify = require('fast-safe-stringify') | ||
const wayfarer = require('wayfarer') | ||
const concat = require('concat-stream') | ||
const handleRequest = require('./lib/handle-request') | ||
const cors = require('./lib/cors') | ||
const parseBody = require('./lib/parse-body') | ||
const restrictPost = require('./lib/restrict-post') | ||
const makeRes = require('./lib/make-res') | ||
const methods = ['get', 'put', 'post', 'delete'] | ||
const methods = ['get', 'put', 'post', 'delete', 'patch'] | ||
const dataMethods = ['put', 'post', 'patch'] | ||
module.exports = function (opts) { | ||
opts = opts || {} | ||
opts.maxPost = opts.maxPost || 512 * 1024 | ||
const routers = new Map() | ||
const middleware = [] | ||
const server = http.createServer() | ||
methods.forEach(m => { | ||
server[m] = (matcher, func) => addRoute(m, matcher, func) | ||
}) | ||
const MAX_POST = 512 * 1024 | ||
const ORIGIN = '*' | ||
const CREDENTIALS = true | ||
const ALLOWED_TYPES = ['application/json'] | ||
const HEADERS = ['Content-Type', 'Accept', 'X-Requested-With'] | ||
server.use = (funcs) => { | ||
if (!Array.isArray(funcs)) { | ||
funcs = [funcs] | ||
class TakeFive { | ||
constructor (opts) { | ||
this._allowMethods = ['options'].concat(methods) | ||
this._allowHeaders = HEADERS.slice(0) | ||
this._allowContentTypes = ALLOWED_TYPES.slice(0) | ||
this._parsers = { | ||
'application/json': JSON.parse | ||
} | ||
middleware.push.apply(middleware, funcs) | ||
opts = opts || {} | ||
this.maxPost = opts.maxPost || MAX_POST | ||
this.allowedContentTypes = opts.allowContentTypes | ||
this.allowOrigin = opts.allowOrigin || ORIGIN | ||
this.allowCredentials = CREDENTIALS | ||
if (typeof opts.allowCredentials === 'boolean') { | ||
this.allowCredentials = opts.allowCredentials | ||
} | ||
if (opts.allowHeaders) { | ||
this.allowHeaders = opts.allowHeaders | ||
} | ||
if (opts.allowMethods) { | ||
this.allowMethods = opts.allowMethods | ||
} | ||
this._httpLib = http | ||
this._httpOpts = opts.http || {} | ||
this._ctx = {} | ||
if (this._httpOpts.key && this._httpOpts.cert) { | ||
this._httpLib = https | ||
} | ||
this.routers = new Map() | ||
const args = [] | ||
if (Object.keys(this._httpOpts).length > 0) { | ||
args.push(this._httpOpts) | ||
} | ||
args.push(this._onRequest.bind(this)) | ||
this.server = this._httpLib.createServer.apply(this._httpLib, args) | ||
this.methods = methods.concat(opts.methods || []) | ||
this._addRouters() | ||
} | ||
server.router = (ns) => { | ||
const router = {} | ||
methods.forEach(m => { | ||
router[m] = (matcher, func) => { | ||
const nsMatcher = path.posix.join(ns, matcher) | ||
addRoute(m, nsMatcher, func) | ||
set allowContentTypes (types) { | ||
if (!Array.isArray(types)) { | ||
types = [types] | ||
} | ||
this._allowContentTypes = this._allowContentTypes.concat(types) | ||
} | ||
get allowContentTypes () { | ||
return this._allowContentTypes | ||
} | ||
addParser (type, func) { | ||
if (typeof type === 'string' && typeof func === 'function') { | ||
this._parsers[type] = func | ||
} | ||
} | ||
set allowHeaders (headers) { | ||
headers = Array.isArray(headers) ? headers : [headers] | ||
this._allowHeaders = this._allowHeaders.concat(headers) | ||
} | ||
get allowHeaders () { | ||
return this._allowHeaders | ||
} | ||
set allowMethods (methods) { | ||
methods = Array.isArray(methods) ? methods : [methods] | ||
this._allowMethods = this._allowMethods.concat(methods) | ||
} | ||
get allowMethods () { | ||
return this._allowMethods | ||
} | ||
set ctx (ctx) { | ||
const ctxType = Object.prototype.toString.call(ctx) | ||
if (ctxType !== '[object Object]') { | ||
throw new Error(`ctx must be an object, was ${ctxType}`) | ||
} | ||
this._ctx = Object.assign({}, ctx) | ||
} | ||
get ctx () { | ||
return this._ctx | ||
} | ||
parseBody (data, type) { | ||
const parser = this._parsers[type] | ||
if (typeof parser === 'function') { | ||
return parser(data) | ||
} | ||
return data | ||
} | ||
makeCtx (res) { | ||
function send (code, content) { | ||
if (typeof content === 'undefined') { | ||
content = code | ||
code = 200 | ||
} | ||
if (typeof content !== 'string') { | ||
content = stringify(content) | ||
} | ||
res.statusCode = code | ||
res.setHeader('content-type', 'application/json') | ||
res.end(content, 'utf8') | ||
} | ||
function err (code, content) { | ||
if (typeof content === 'undefined') { | ||
if (parseInt(code, 10)) { | ||
content = http.STATUS_CODES[code] | ||
} else { | ||
content = code | ||
code = 500 | ||
} | ||
} | ||
res.statusCode = code | ||
res.statusMessage = content | ||
res.setHeader('content-type', 'application/json') | ||
res.end(stringify({message: content})) | ||
} | ||
return Object.assign({}, this.ctx, {send, err}) | ||
} | ||
_handleError (err, req, res, ctx) { | ||
if (typeof this.handleError === 'function') { | ||
this.handleError(err, req, res, ctx) | ||
} | ||
if (!res.finished) { | ||
ctx.err('Internal server error') | ||
} | ||
} | ||
cors (res) { | ||
res.setHeader('Access-Control-Allow-Origin', this.allowOrigin) | ||
res.setHeader('Access-Control-Allow-Headers', this.allowHeaders.join(',')) | ||
res.setHeader('Access-Control-Allow-Credentials', this.allowCredentials) | ||
res.setHeader('Access-Control-Allow-Methods', this.allowMethods.join(',').toUpperCase()) | ||
} | ||
listen (port, func) { | ||
this.server.listen(port, func) | ||
} | ||
close () { | ||
this.server.close() | ||
} | ||
_verifyBody (req, res, ctx) { | ||
return new Promise((resolve) => { | ||
const type = req.headers['content-type'] | ||
const size = req.headers['content-length'] | ||
const _ctxMax = parseInt(ctx.maxPost, 10) | ||
const maxPost = Number.isNaN(_ctxMax) ? this.maxPost : _ctxMax | ||
let allowContentTypes = this.allowContentTypes.slice(0) | ||
if (ctx.allowContentTypes) { | ||
allowContentTypes = allowContentTypes.concat(ctx.allowContentTypes) | ||
} | ||
if (size > maxPost) { | ||
return ctx.err(413, `Payload size exceeds maximum size for requests`) | ||
} | ||
if (!allowContentTypes.includes(type)) { | ||
return ctx.err(415, `Expected data to be of ${allowContentTypes.join(', ')} not ${type}`) | ||
} else { | ||
const parser = concat((data) => { | ||
try { | ||
ctx.body = this.parseBody(data.toString('utf8'), type) | ||
} catch (err) { | ||
return ctx.err(400, `Payload is not valid ${type}`) | ||
} | ||
resolve() | ||
}) | ||
req.pipe(parser) | ||
const body = [] | ||
req.on('data', (chunk) => { | ||
body.push(chunk.toString('utf8')) | ||
if (chunk.length > this.maxPost || Buffer.byteLength(body.join(''), 'utf8') > this.maxPost) { | ||
req.pause() | ||
return ctx.err(413, 'Payload size exceeds maximum body length') | ||
} | ||
}) | ||
} | ||
}) | ||
return router | ||
} | ||
server.use(cors(opts.cors || {})) | ||
server.use(restrictPost(opts.maxPost)) | ||
server.use(parseBody(opts.maxPost)) | ||
server.on('request', (req, res) => handleRequest(req, makeRes(res), middleware, routers)) | ||
_onRequest (req, res) { | ||
this.cors(res) | ||
return server | ||
function addRoute (method, matcher, funcs) { | ||
if (!routers.has(method)) { | ||
routers.set(method, wayfarer('/_')) | ||
if (req.method === 'OPTIONS') { | ||
res.statusCode = 204 | ||
return res.end() | ||
} | ||
if (!Array.isArray(funcs)) { | ||
funcs = [funcs] | ||
const ctx = this.makeCtx(res) | ||
try { | ||
const method = req.method.toLowerCase() | ||
const url = req.url.split('?')[0] | ||
const router = this.routers.get(method) | ||
router(url, req, res, ctx) | ||
} catch (err) { | ||
if (res.finished) { | ||
throw err | ||
} | ||
return ctx.err(404, 'Not found') | ||
} | ||
} | ||
routers.get(method).on(matcher, (params, req, res) => { | ||
req.params = querystring.parse(req.url.split('?')[1]) | ||
req.urlParams = params | ||
handleRequest(req, res, funcs) | ||
_addRouters () { | ||
this.methods.forEach((method) => { | ||
Object.defineProperty(this, method, {value: generateRouter(method)}) | ||
}) | ||
function generateRouter (method) { | ||
return function (matcher, handler, ctxOpts) { | ||
let router = this.routers.get(method) | ||
if (!router) { | ||
router = wayfarer('/_') | ||
this.routers.set(method, router) | ||
} | ||
const handlers = Array.isArray(handler) ? handler : [handler] | ||
if (handlers.some((f) => typeof f !== 'function')) { | ||
throw new Error('handlers must be functions') | ||
} | ||
router.on(matcher, (params, req, res, ctx) => { | ||
const routeHandlers = handlers.slice(0) | ||
const conlen = parseInt(req.headers['content-length'], 10) || 0 | ||
if (conlen !== 0 && dataMethods.includes(req.method.toLowerCase())) { | ||
if (ctxOpts) ctx = Object.assign({}, ctx, ctxOpts) | ||
routeHandlers.unshift(this._verifyBody.bind(this)) | ||
} | ||
ctx.query = querystring.parse(req.url.split('?')[1]) | ||
ctx.params = params | ||
this._resolveHandlers(req, res, ctx, routeHandlers) | ||
}) | ||
} | ||
} | ||
} | ||
_resolveHandlers (req, res, ctx, handlers) { | ||
const iterate = (handler) => { | ||
const p = handler(req, res, ctx) | ||
if (p && typeof p.then === 'function') { | ||
p.then(() => { | ||
if (!res.finished && handlers.length > 0) { | ||
const next = handlers.shift() | ||
iterate(next) | ||
} | ||
}) | ||
.catch((err) => { | ||
this._handleError(err, req, res, ctx) | ||
}) | ||
} | ||
} | ||
const next = handlers.shift() | ||
iterate(next) | ||
} | ||
} | ||
module.exports = TakeFive |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
119794
16
728
238
3
5
3
+ Addedconcat-stream@^1.6.2
+ Addedbuffer-from@1.1.2(transitive)
+ Addedconcat-stream@1.6.2(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedfast-safe-stringify@2.1.1(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedreadable-stream@2.3.8(transitive)
+ Addedsafe-buffer@5.1.2(transitive)
+ Addedstring_decoder@1.1.1(transitive)
+ Addedtypedarray@0.0.6(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedfast-safe-stringify@1.2.3(transitive)
Updatedfast-safe-stringify@^2.0.3