moleculer-web
Advanced tools
Comparing version 0.4.0 to 0.4.1
@@ -0,1 +1,41 @@ | ||
<a name="0.4.1"></a> | ||
# 0.4.1 (2017-07-24) | ||
## New | ||
### Prohibited action with `publish: false` action properties | ||
```js | ||
module.exports = { | ||
name: "test", | ||
actions: { | ||
dangerZone: { | ||
publish: false, | ||
handler(ctx) { | ||
return "You cannot call this action via API Gateway!"; | ||
} | ||
} | ||
} | ||
}; | ||
``` | ||
### Calling options in routes | ||
The `route` has a `callOptions` property which is passed to `broker.call`. So you can set `timeout`, `retryCount` or `fallbackResponse` options for routes. | ||
```js | ||
broker.createService(ApiGatewayService, { | ||
settings: { | ||
routes: [{ | ||
callOptions: { | ||
timeout: 1000, // 1 sec | ||
retryCount: 0 | ||
} | ||
}] | ||
} | ||
}); | ||
``` | ||
----------------------------- | ||
<a name="0.4.0"></a> | ||
@@ -2,0 +42,0 @@ # 0.4.0 (2017-07-07) |
@@ -189,2 +189,7 @@ "use strict"; | ||
callOptions: { | ||
timeout: 3000, | ||
//fallbackResponse: "Fallback response via callOptions" | ||
}, | ||
onBeforeCall(ctx, route, req, res) { | ||
@@ -191,0 +196,0 @@ return new this.Promise(resolve => { |
"use strict"; | ||
const _ = require("lodash"); | ||
const { MoleculerError } = require("moleculer").Errors; | ||
@@ -36,4 +37,20 @@ module.exports = { | ||
return "Hi!"; | ||
}, | ||
dangerZone: { | ||
publish: false, | ||
handler(ctx) { | ||
return "You cannot call this action via API Gateway!"; | ||
} | ||
}, | ||
slow(ctx) { | ||
let time = ctx.params.delay || 5000; | ||
return this.Promise.resolve().delay(time).then(() => `Done after ${time / 1000} sec!`); | ||
}, | ||
wrong(ctx) { | ||
throw new MoleculerError("It is a wrong action! I always throw error!"); | ||
} | ||
} | ||
}; |
{ | ||
"name": "moleculer-web", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "Official API Gateway service for Moleculer framework", | ||
@@ -32,3 +32,3 @@ "main": "index.js", | ||
"coveralls": "2.13.1", | ||
"eslint": "4.1.1", | ||
"eslint": "4.3.0", | ||
"express": "4.15.3", | ||
@@ -39,5 +39,5 @@ "fakerator": "0.3.0", | ||
"jsonwebtoken": "7.4.1", | ||
"lolex": "1.6.0", | ||
"lolex": "2.1.1", | ||
"mkdirp": "0.5.1", | ||
"moleculer": "0.8.2", | ||
"moleculer": "0.8.3", | ||
"multer": "1.3.0", | ||
@@ -44,0 +44,0 @@ "nats": "0.7.20", |
262
src/index.js
@@ -127,2 +127,10 @@ /* | ||
// Call options | ||
route.callOptions = opts.callOptions; | ||
// Fallback response handler | ||
/*if (opts.fallbackResponse) | ||
route.fallbackResponse = this.Promise.method(opts.fallbackResponse); | ||
*/ | ||
// Handle whitelist | ||
@@ -240,5 +248,6 @@ route.whitelist = opts.whitelist; | ||
* @param {String} url | ||
* @param {Number} status code | ||
*/ | ||
sendRedirect(res, url) { | ||
res.writeHead(302, { | ||
sendRedirect(res, url, code = 302) { | ||
res.writeHead(code, { | ||
"Location": url | ||
@@ -384,141 +393,168 @@ }); | ||
// Whitelist check | ||
.then(() => { | ||
if (route.hasWhitelist) { | ||
if (!this.checkWhitelist(route, actionName)) { | ||
this.logger.debug(` The '${actionName}' action is not in the whitelist!`); | ||
return this.Promise.reject(new ServiceNotFoundError(actionName)); | ||
.then(() => { | ||
if (route.hasWhitelist) { | ||
if (!this.checkWhitelist(route, actionName)) { | ||
this.logger.debug(` The '${actionName}' action is not in the whitelist!`); | ||
return this.Promise.reject(new ServiceNotFoundError(actionName)); | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
// Parse body | ||
.then(() => { | ||
if (["POST", "PUT", "PATCH"].indexOf(req.method) !== -1 && route.parsers && route.parsers.length > 0) { | ||
return this.Promise.mapSeries(route.parsers, parser => { | ||
return new this.Promise((resolve, reject) => { | ||
parser(req, res, err => { | ||
if (err) { | ||
return reject(new InvalidRequestBodyError(err.body, err.message)); | ||
} | ||
// Parse body | ||
.then(() => { | ||
if (["POST", "PUT", "PATCH"].indexOf(req.method) !== -1 && route.parsers && route.parsers.length > 0) { | ||
return this.Promise.mapSeries(route.parsers, parser => { | ||
return new this.Promise((resolve, reject) => { | ||
parser(req, res, err => { | ||
if (err) { | ||
return reject(new InvalidRequestBodyError(err.body, err.message)); | ||
} | ||
resolve(); | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
}) | ||
} | ||
}) | ||
// Merge params | ||
.then(() => { | ||
const body = _.isObject(req.body) ? req.body : {}; | ||
params = Object.assign({}, body, params); | ||
}) | ||
// Merge params | ||
.then(() => { | ||
const body = _.isObject(req.body) ? req.body : {}; | ||
params = Object.assign({}, body, params); | ||
}) | ||
// Resolve action by name | ||
.then(() => { | ||
endpoint = this.broker.getAction(actionName); | ||
if (endpoint) { | ||
// Resolve action by name | ||
.then(() => { | ||
endpoint = this.broker.getAction(actionName); | ||
if (!endpoint) { | ||
// Action is not available | ||
return this.Promise.reject(new ServiceNotFoundError(actionName)); | ||
} | ||
if (endpoint.action.publish === false) { | ||
// Action is not publishable | ||
return this.Promise.reject(new ServiceNotFoundError(actionName)); | ||
} | ||
// Validate params | ||
if (this.broker.validator && endpoint.action.params) | ||
this.broker.validator.validate(params, endpoint.action.params); | ||
} else { | ||
// Action is not available | ||
return this.Promise.reject(new ServiceNotFoundError(actionName)); | ||
} | ||
this.broker.validator.validate(params, endpoint.action.params); | ||
return endpoint; | ||
}) | ||
return endpoint; | ||
}) | ||
// Create a new context for request | ||
.then(() => { | ||
this.logger.info(` Call '${actionName}' action with params:`, params); | ||
// Create a new context for request | ||
.then(() => { | ||
this.logger.info(` Call '${actionName}' action with params:`, params); | ||
const restAction = { | ||
name: this.name + ".rest" | ||
}; | ||
const restAction = { | ||
name: this.name + ".rest" | ||
}; | ||
// Create a new context to wrap the request | ||
const ctx = this.broker.createNewContext(restAction, null, params, { | ||
//timeout: 5 * 1000 | ||
}); | ||
ctx.requestID = ctx.id; | ||
ctx._metricStart(ctx.metrics); | ||
//ctx.endpoint = endpoint; | ||
// Create a new context to wrap the request | ||
const ctx = this.broker.createNewContext(restAction, null, params, route.callOptions || {}); | ||
return ctx; | ||
}) | ||
ctx.requestID = ctx.id; | ||
ctx._metricStart(ctx.metrics); | ||
//ctx.endpoint = endpoint; | ||
// onBeforeCall handling | ||
.then(ctx => { | ||
if (route.onBeforeCall) { | ||
return route.onBeforeCall.call(this, ctx, route, req, res).then(() => { | ||
return ctx; | ||
}); | ||
} | ||
return ctx; | ||
}) | ||
return ctx; | ||
}) | ||
// Authorization | ||
.then(ctx => { | ||
if (route.authorization) { | ||
return this.authorize(ctx, route, req, res).then(() => { | ||
return ctx; | ||
}); | ||
} | ||
return ctx; | ||
}) | ||
// onBeforeCall handling | ||
.then(ctx => { | ||
if (route.onBeforeCall) { | ||
return route.onBeforeCall.call(this, ctx, route, req, res).then(() => { | ||
return ctx; | ||
}); | ||
} | ||
return ctx; | ||
}) | ||
// Call the action | ||
.then(ctx => { | ||
return ctx.call(endpoint, params) | ||
.then(data => { | ||
res.statusCode = 200; | ||
// Authorization | ||
.then(ctx => { | ||
if (route.authorization) { | ||
return this.authorize(ctx, route, req, res).then(() => { | ||
return ctx; | ||
}); | ||
} | ||
return ctx; | ||
}) | ||
// Override responseType by action | ||
const responseType = endpoint.action.responseType; | ||
// Call the action | ||
.then(ctx => { | ||
return ctx.call(endpoint, params, route.callOptions || {}) | ||
.then(data => { | ||
res.statusCode = 200; | ||
// Return with the response | ||
if (ctx.requestID) | ||
res.setHeader("Request-Id", ctx.requestID); | ||
// Override responseType by action | ||
const responseType = endpoint.action.responseType; | ||
return Promise.resolve() | ||
// Return with the response | ||
if (ctx.requestID) | ||
res.setHeader("Request-Id", ctx.requestID); | ||
return Promise.resolve() | ||
// onAfterCall handling | ||
.then(() => { | ||
if (route.onAfterCall) | ||
return route.onAfterCall.call(this, ctx, route, req, res, data); | ||
}) | ||
.then(() => { | ||
.then(() => { | ||
if (route.onAfterCall) | ||
return route.onAfterCall.call(this, ctx, route, req, res, data); | ||
}) | ||
.then(() => { | ||
//try { | ||
this.sendResponse(ctx, route, req, res, data, responseType); | ||
//} catch(err) { | ||
this.sendResponse(ctx, route, req, res, data, responseType); | ||
//} catch(err) { | ||
/* istanbul ignore next */ | ||
// return this.Promise.reject(new InvalidResponseTypeError(typeof(data))); | ||
//} | ||
// return this.Promise.reject(new InvalidResponseTypeError(typeof(data))); | ||
//} | ||
ctx._metricFinish(null, ctx.metrics); | ||
}); | ||
}); | ||
}) | ||
ctx._metricFinish(null, ctx.metrics); | ||
}); | ||
}); | ||
}) | ||
// Error handling | ||
.catch(err => { | ||
this.logger.error(" Request error!", err.name, ":", err.message, "\n", err.stack, "\nData:", err.data); | ||
const headers = { | ||
"Content-type": "application/json" | ||
}; | ||
if (err.ctx) { | ||
headers["Request-Id"] = err.ctx.id; | ||
} | ||
// Error handling | ||
.catch(err => { | ||
return Promise.resolve(err) | ||
/* Deprecated. Use `route.callOptions.fallbackResponse` instead. | ||
.then(err => { | ||
let ctx = err.ctx; | ||
if (_.isFunction(route.fallbackResponse)) { | ||
return route.fallbackResponse.call(this, err, route, err.ctx, req, res) | ||
.then(data => { | ||
if (data !== undefined) { | ||
this.sendResponse(ctx, route, req, res, data); | ||
return null; | ||
} | ||
return null; | ||
}).catch(err => err); // Throw further the new Error | ||
} | ||
return err; | ||
}) | ||
*/ | ||
.then(err => { | ||
/* istanbul ignore next */ | ||
if (!err) | ||
return; | ||
// Return with the error | ||
const code = _.isNumber(err.code) ? err.code : 500; | ||
res.writeHead(code, headers); | ||
const errObj = _.pick(err, ["name", "message", "code", "type", "data"]); | ||
res.end(JSON.stringify(errObj, null, 2)); | ||
this.logger.error(" Request error!", err.name, ":", err.message, "\n", err.stack, "\nData:", err.data); | ||
const headers = { | ||
"Content-type": "application/json" | ||
}; | ||
if (err.ctx) { | ||
headers["Request-Id"] = err.ctx.id; | ||
} | ||
if (err.ctx) | ||
err.ctx._metricFinish(null, err.ctx.metrics); | ||
}); | ||
// Return with the error | ||
const code = _.isNumber(err.code) ? err.code : 500; | ||
res.writeHead(code, headers); | ||
const errObj = _.pick(err, ["name", "message", "code", "type", "data"]); | ||
res.end(JSON.stringify(errObj, null, 2)); | ||
if (err.ctx) | ||
err.ctx._metricFinish(null, err.ctx.metrics); | ||
}); | ||
}); | ||
return p; | ||
@@ -525,0 +561,0 @@ }, |
@@ -37,2 +37,9 @@ "use strict"; | ||
dangerZone: { | ||
publish: false, | ||
handler(ctx) { | ||
return "Danger zone!"; | ||
} | ||
}, | ||
text(ctx) { | ||
@@ -39,0 +46,0 @@ return "String text"; |
@@ -114,2 +114,20 @@ "use strict"; | ||
}); | ||
it("GET /test/dangerZone", () => { | ||
return request(server) | ||
.get("/test/dangerZone") | ||
.expect(404) | ||
.expect("Content-Type", "application/json") | ||
.then(res => { | ||
expect(res.body).toEqual({ | ||
"code": 404, | ||
"message": "Service 'test.dangerZone' is not available!", | ||
"name": "ServiceNotFoundError", | ||
"type": null, | ||
"data": { | ||
action: "test.dangerZone" | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -544,2 +562,6 @@ | ||
let customAlias = jest.fn((route, req, res) => { | ||
res.end("Custom Alias"); | ||
}); | ||
beforeAll(() => { | ||
@@ -556,3 +578,4 @@ [ broker, service, server] = setup({ | ||
"/repeat-test/:args*": "test.echo", | ||
"GET /": "test.hello" | ||
"GET /": "test.hello", | ||
"GET custom": customAlias | ||
} | ||
@@ -700,2 +723,13 @@ }] | ||
}); | ||
it("GET /api/custom", () => { | ||
return request(server) | ||
.get("/api/custom") | ||
.expect(200) | ||
.then(res => { | ||
expect(res.text).toBe("Custom Alias"); | ||
expect(customAlias).toHaveBeenCalledTimes(1); | ||
expect(customAlias).toHaveBeenCalledWith(service.routes[0], jasmine.any(http.IncomingMessage), jasmine.any(http.ServerResponse)); | ||
}); | ||
}); | ||
}); | ||
@@ -702,0 +736,0 @@ |
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
758069
3080