Comparing version 0.11.13 to 0.12.0-rc1
@@ -5,4 +5,4 @@ const assert = require('assert'); | ||
const resMethods = require('./response'); | ||
const { handleError } = require('./error'); | ||
const Response = require('./response'); | ||
const RequestBody = require('./body'); | ||
@@ -86,38 +86,5 @@ | ||
handler(input).catch(err => handleError(err, input)); | ||
handler(input).catch(err => handleError(err, input.res)); | ||
} | ||
function generateResponseObject(method, url, req){ | ||
return { | ||
headers: {}, | ||
req, | ||
statusCode: 200, | ||
end(body){ | ||
let output = { status: this.statusCode, headers: this.headers }; | ||
if(body) | ||
output.body = body; | ||
this.input.log.debug({ res: this }); | ||
this.finished = true; | ||
return this.routeEnded(output); | ||
}, | ||
getHeader(key){ | ||
return this.headers[key] | ||
}, | ||
setHeader(key, value){ | ||
this.headers[key] = value; | ||
} | ||
}; | ||
} | ||
function generateRequestObject(method, path, input){ | ||
let obj = { method, path }; | ||
input.headers && Object.assign(obj, input.headers); | ||
return obj; | ||
} | ||
function parseSignedCookies(cconf, input){ | ||
@@ -209,21 +176,17 @@ for(let key in input.cookies) | ||
const req = input.req = input.req || generateRequestObject(method, path, input); | ||
const res = input.res = input.res || generateResponseObject(method, path, req); | ||
res.req = req; | ||
res.input = input; | ||
Object.assign(res, resMethods); | ||
let origReq = input.req || {}; | ||
input.req = { method, path, host: origReq.host, 'user-agent': origReq['user-agent'] }; | ||
app.log.debug({ req: input.req }); | ||
app.log.debug({ req }); | ||
let res = input.res = new Response(input); | ||
const handler = matchRoute.call(this, method, path, params); | ||
const masterPromise = new Promise(done => { res.routeEnded = done }); | ||
if(!handler){ | ||
res.status(404).end(); | ||
return masterPromise; | ||
return res.ended; | ||
} | ||
req.body = input.body = new RequestBody(input); | ||
if(app._shouldParseBody) | ||
input.body = new RequestBody(input, origReq); | ||
if(app._autoParseBody) | ||
try{ | ||
@@ -233,8 +196,7 @@ input.body = await input.body.parse(); | ||
catch(err){ | ||
!req.aborted && res.status(400).end(); | ||
return app.log.warn({ req, err, type: 'request' }); | ||
res.status(400).end(); | ||
app.log.warn({ req: res.req, err, type: 'request' }); | ||
return res.ended; | ||
} | ||
if(app.conf.cookie) | ||
res.cookieSecret = app.conf.cookie.secret; | ||
parseSignedCookies(app.conf.cookie, input); | ||
@@ -250,5 +212,5 @@ | ||
return masterPromise; | ||
return res.ended; | ||
} | ||
} |
@@ -5,4 +5,2 @@ | ||
const formdata = require('./form-data'); | ||
const FALLBACK_CONTENT_TYPE = { type: 'text/plain', parameters: { charset: 'utf-8' } }; | ||
@@ -25,3 +23,3 @@ | ||
function readStream(){ | ||
var buffer = []; | ||
var complete, buffer = []; | ||
@@ -45,9 +43,9 @@ this.req.on('data', chunk => buffer.push(chunk)); | ||
this.req.on('end', () => { | ||
!this.complete && resolve(Buffer.concat(buffer)); | ||
this.complete = true; | ||
!complete && resolve(Buffer.concat(buffer)); | ||
complete = true; | ||
}); | ||
this.req.on('error', err => { | ||
!this.complete && reject(err); | ||
this.complete = true; | ||
!complete && reject(err); | ||
complete = true; | ||
}); | ||
@@ -59,3 +57,3 @@ }); | ||
if(!this.isStream) | ||
if(!this.res.res) | ||
return this.origBody; | ||
@@ -73,10 +71,8 @@ | ||
constructor({ req, res, headers, body }){ | ||
constructor({ res, headers, body }, stream){ | ||
Object.assign(this, parseContentType(headers)); | ||
this.req = req; | ||
this.req = stream; | ||
this.res = res; | ||
this.origBody = body; | ||
this.complete = false; | ||
this.length = headers['content-length']; | ||
this.isStream = req.readable; | ||
} | ||
@@ -101,3 +97,2 @@ | ||
this.res.badType(this.type.slice(-4) != 'json'); | ||
return await read.call(this, raw => | ||
@@ -109,5 +104,2 @@ JSON.parse(raw.toString(this.textCharset))); | ||
if(this.type == 'multipart/form-data') | ||
return formdata(this.req); | ||
if(this.type.slice(-4) == 'json') | ||
@@ -114,0 +106,0 @@ return this.json(); |
@@ -25,10 +25,8 @@ | ||
// and from res.error() when calling it with an error | ||
function handleError(err, input){ | ||
input.res.failed = true; | ||
function handleError(err, res){ | ||
res.failed = true; | ||
// Need to keep the original error stack and message for logging. | ||
input.res.err = err; | ||
res.err = err; | ||
const { log, res } = input; | ||
err = anythingToError(err); | ||
@@ -39,4 +37,4 @@ | ||
log.error({ method: input.res.req.method, path: input.res.req.path, | ||
headers: input.res.req.headers, type: 'route', err: input.res.err }); | ||
res.log.error({ method: res.req.method, path: res.req.path, | ||
headers: res.req.headers, type: 'route', err: res.err }); | ||
@@ -43,0 +41,0 @@ if(!res.finished) |
@@ -18,4 +18,2 @@ const cookie = require('cookie'); | ||
res.on('finish', () => this.log.debug({ res })); | ||
await this._api.trigger(req.method, req.path, { | ||
@@ -22,0 +20,0 @@ query: req.query, req, res, headers: req.headers, cookies: req.cookies |
@@ -11,8 +11,2 @@ const | ||
const SHORT_TYPES = { | ||
form: 'multipart/form-data', | ||
urlencoded: 'application/x-www-form-urlencoded', | ||
json: 'application/json' | ||
}; | ||
const noop = function(){}; | ||
@@ -68,3 +62,3 @@ noop.noop = true; | ||
this._shouldParseBody = opts.shouldParseBody || typeof opts.shouldParseBody == 'undefined'; | ||
this._autoParseBody = opts.autoParseBody || false; | ||
this._alwaysRebuildAPI = opts.alwaysRebuildAPI || false; | ||
@@ -92,11 +86,2 @@ | ||
accept(types){ | ||
types = [].concat(types).map(t => SHORT_TYPES[t] || t); | ||
return ({ body, req, res, next }) => { | ||
if(body !== '' && !types.includes(req.body.type)) | ||
return res.status(415).end(); | ||
next(); | ||
} | ||
} | ||
async start(){ | ||
@@ -103,0 +88,0 @@ |
const { sign } = require('cookie-signature'); | ||
const cookie = require('cookie'); | ||
const { format } = require('util'); | ||
const { HTTPError, handleError } = require('./error'); | ||
@@ -20,51 +21,38 @@ | ||
badType: 415 | ||
} | ||
}; | ||
function assert(status, cond, message, ...args){ | ||
if(!cond) | ||
return true; | ||
throw this.error(status, message, ...args); | ||
} | ||
module.exports = class Response { | ||
module.exports = { | ||
constructor({ res, req, log, conf }){ | ||
this.conf = conf; | ||
this.res = res; | ||
this.log = log; | ||
this.req = req; | ||
this.ended = new Promise((resolve, reject) => { | ||
this.resolve = resolve; | ||
this.reject = reject; | ||
}); | ||
this.headers = {}; | ||
this.statusCode = 200; | ||
for(let name in ASSERTS) | ||
this[name] = this.assert.bind(this, ASSERTS[name]); | ||
} | ||
get(k){ | ||
return this.getHeader(k); | ||
}, | ||
write(chunk){ | ||
this.res && this.res.write(chunk); | ||
this.body ? this.body.concat(chunk) : this.body = chunk; | ||
} | ||
set(k, v){ | ||
this.setHeader(k, v); | ||
return this; | ||
}, | ||
end(body){ | ||
this.body ? this.body.concat(body) : this.body = body; | ||
this.res && this.res.end(body); | ||
this.log.debug({ res: this }); | ||
this.finished = true; | ||
return this.resolve({ | ||
status: this.statusCode, | ||
headers: this.headers, | ||
body: this.body | ||
}); | ||
} | ||
append(k, v){ | ||
let prev = this.get(k); | ||
prev && (v = Array.isArray(prev) | ||
? prev.concat(v) | ||
: [ prev, v ]); | ||
return this.set(k, v); | ||
}, | ||
status(s){ | ||
this.statusCode = s; | ||
return this; | ||
}, | ||
type(ct){ | ||
this.set('Content-Type', SHORT_CONTENT_TYPES[ct] || ct); | ||
return this; | ||
}, | ||
json(data){ | ||
this.type('json'); | ||
this.end(JSON.stringify(data)); | ||
return this; | ||
}, | ||
text(data){ | ||
this.type('text'); | ||
this.end(String(data)); | ||
return this; | ||
}, | ||
// Not necessarily an APP error, but a client Error | ||
@@ -75,3 +63,3 @@ error(status, message, ...args){ | ||
if(!Number.isInteger(status)) | ||
return handleError(status, this.input); | ||
return handleError(status, this); | ||
@@ -99,4 +87,47 @@ this.status(status); | ||
return new HTTPError(status, message, type); | ||
}, | ||
} | ||
assert(status, cond, message, ...args){ | ||
if(!cond) | ||
return true; | ||
throw this.error(status, message, ...args); | ||
} | ||
set(k, v){ | ||
this.res && this.res.setHeader(k, v); | ||
this.headers[k] = v; | ||
return this; | ||
} | ||
append(k, v){ | ||
let prev = this.headers[k]; | ||
prev && (v = Array.isArray(prev) | ||
? prev.concat(v) | ||
: [ prev, v ]); | ||
return this.set(k, v); | ||
} | ||
status(s){ | ||
this.res && (this.res.statusCode = s); | ||
this.statusCode = s; | ||
return this; | ||
} | ||
type(ct){ | ||
this.set('Content-Type', SHORT_CONTENT_TYPES[ct] || ct); | ||
return this; | ||
} | ||
json(data){ | ||
this.type('json'); | ||
this.end(JSON.stringify(data)); | ||
return this; | ||
} | ||
text(data){ | ||
this.type('text'); | ||
this.end(String(data)); | ||
return this; | ||
} | ||
clearCookie(name, opts) { | ||
@@ -106,3 +137,3 @@ opts = { path: '/', ...opts, expires: new Date(1) }; | ||
return this.cookie(name, '', opts); | ||
}, | ||
} | ||
@@ -113,3 +144,3 @@ cookie(name, value, opts = {}) { | ||
if(opts.signed && !this.cookieSecret) | ||
if(opts.signed && !this.conf.cookie?.secret) | ||
throw new Error('Trying to sign cookies when secret is not defined'); | ||
@@ -120,3 +151,3 @@ | ||
if(opts.signed) | ||
value = 's:' + sign(value, this.cookieSecret); | ||
value = 's:' + sign(value, this.conf.cookie.secret); | ||
@@ -133,9 +164,4 @@ if('maxAge' in opts) { | ||
}; | ||
} | ||
for(let name in ASSERTS) | ||
module.exports[name] = function(...args){ | ||
assert.call(this, ASSERTS[name], ...args); | ||
} | ||
// TODO 406 notAcceptable: | ||
@@ -142,0 +168,0 @@ // TODO 405 methodNotAllowed |
{ | ||
"name": "nodecaf", | ||
"version": "0.11.13", | ||
"version": "0.12.0-rc1", | ||
"description": "Nodecaf is a light framework for developing RESTful Apps in a quick and convenient manner.", | ||
@@ -40,3 +40,2 @@ "main": "lib/main.js", | ||
"dependencies": { | ||
"busboy": "^0.3.1", | ||
"confort": "^0.2.0", | ||
@@ -50,3 +49,2 @@ "content-type": "^1.0.4", | ||
"devDependencies": { | ||
"form-data": "^3.0.1", | ||
"muhb": "^3.0.4", | ||
@@ -53,0 +51,0 @@ "toml": "^3.0.0" |
@@ -20,3 +20,2 @@ # [Nodecaf](https://gitlab.com/GCSBOSS/nodecaf) | ||
source of truth. | ||
- Functions to [filter request bodies](#filter-requests-by-mime-type) by mime-type. | ||
- Helpful [command line interface](https://gitlab.com/GCSBOSS/nodecaf-cli). | ||
@@ -419,38 +418,2 @@ | ||
### Filter Requests by Mime-type | ||
Nodecaf allow you to reject request bodies whose mime-type is not in a defined | ||
white-list. Denied requests will receive a 400 response with the apporpriate | ||
message. | ||
Define a filter for the entire app on your `api.js`: | ||
```js | ||
module.exports = function({ }){ | ||
this.accept(['json', 'text/html']); | ||
} | ||
``` | ||
Override the global accept per route on your `api.js`: | ||
```js | ||
const { accept } = require('nodecaf'); | ||
module.exports = function({ post, put }){ | ||
// Define global accept rules | ||
this.accept(['json', 'text/html']); | ||
// Obtain accepts settings | ||
let json = accept('json'); | ||
let img = accept([ 'png', 'jpg', 'svg', 'image/*' ]); | ||
// Prepend accept definition in each route chain | ||
post('/my/json/thing', json, myJSONHandler); | ||
post('/my/img/thing', img, myImageHandler); | ||
} | ||
``` | ||
### API Description | ||
@@ -507,3 +470,2 @@ | ||
| `app.conf.port` | Integer | Port for the web server to listen (also exposed as user conf) | `80` or `443` | | ||
| `app.conf.formFileDir` | Path | Where to store files uploaded as form-data | OS default temp dir | | ||
| `app.conf.cookie.secret` | String | A secure random string to be used for signing cookies | none | | ||
@@ -510,0 +472,0 @@ | `opts.name` | String | Manually set application name used in various places | `package.json`s | |
Sorry, the diff of this file is not supported yet
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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
55631
6
2
1
564
473
- Removedbusboy@^0.3.1
- Removedbusboy@0.3.1(transitive)
- Removeddicer@0.3.0(transitive)
- Removedstreamsearch@0.1.2(transitive)