urest
Version:
The rest frame work from the future.
154 lines (122 loc) • 4.39 kB
JavaScript
const Router = require("./Router");
const Log = require("./Log");
const UReq = require("./UReq");
const URes = require("./URes/URes");
const UResLambda = require("./URes/UResLambda");
const UResNative = require("./URes/UResNative");
const UResGCF = require("./URes/UResGCF");
const { UInternalServerError } = require("./UErrors");
const defaultName = process.env.FUNCTION_NAME || process.env.AWS_LAMBDA_FUNCTION_NAME;
const logRequest = Symbol("logRequest");
const logResponse = Symbol("logResponse");
const handleCors = Symbol("handleCors");
class Rest extends Router {
constructor({ name = defaultName, log, logRequests = true } = {}) {
super();
this.name = name;
this.log = log;
this.logRequests = logRequests;
}
[logRequest](req) {
req.log.info({ event: "Request", method: req.method, path: req.path || req.url, body: req.body });
}
[logResponse](req, res) {
//TODO: some clever stuff to handle other body types than json here
const diff = process.hrtime(res.zeroTime);
const duration = (diff[0] * 1e9 + diff[1]) / 1e6;
req.log.info({ event: "Response", status: res.statusCode, body: res.body, duration });
}
[handleCors](req, res) { // TODO: have this handle the res.send and return true if handled
if (!this.corsConfig) return;
const origin = req.headers.origin;
const { origins, methods, headers } = this.corsConfig;
if (origins.includes(origin)) {
res.headers["Access-Control-Allow-Origin"] = origin;
res.headers["Access-Control-Allow-Methods"] = methods ? methods.join(", ") : "*";
res.headers["Access-Control-Allow-Headers"] = headers ? headers.join(", ") : "*";
}
}
query(req, res, log) {
req.log = this.log || log;
if (this.logRequests) {
this[logRequest](req);
res.on("response", () => this[logResponse](req, res));
}
if (this[handleCors](req, res)) return;
const { method, path = req.url } = req; // TODO: normalise req.url/req.path before Rest.query
const matched = this.routes
.filter(({ method: candidateMethod }) => candidateMethod === method.toLowerCase())
.map(route => Object.assign(route, { match: route.regex.exec(path) }))
.filter(({ match }) => match !== null)[0];
if (!matched) {
if (method.toLowerCase() === "options" && this.corsConfig) return res.status(200).send();
return res.status(404).send(); //TODO: make this customizable
}
const handlers = this.middleware.concat(matched.handlers);
const intercept = this.intercept.concat(matched.intercept);
req.params = this.getParams(matched.match, matched.tokens);
res.setIntercept(intercept);
this.runHandlers(req, res, handlers);
}
getParams(match, tokens) {
return tokens
.filter(token => typeof token === "object")
.map(({ param }) => param)
.reduce((acc, param, index) => {
if (param === "*") acc[param] = (acc[param] || []).concat([match[index + 1]]); // for wildcards we use an array
else acc[param] = match[index + 1];
return acc;
}, {});
}
runHandlers(req, res, handlers) { // This is special, lol
const runHandler = (next, i) => {
const handler = handlers[i];
try {
if (handler) handler(req, res, next);
}
catch (e) {
URes.returnError(new UInternalServerError(e), req, res);
}
};
let i = 0;
const next = (err) => {
if (err) return URes.returnError(err, req, res);
i++;
runHandler(next, i);
};
runHandler(next, i);
}
cors(corsConfig) {
this.corsConfig = corsConfig;
}
gcf() {
return {
urest: (req, res) => {
const log = new Log({ headers: req.headers });
const ureq = new UReq({ req, res });
const ures = new UResGCF({ req, res, log });
this.query(ureq, ures, new Log({ headers: req.headers }));
}
};
}
lambda(e) {
return new Promise(callback => {
const log = new Log({ headers: e.headers });
const req = new UReq({ e, log });
const res = new UResLambda({ e, callback, log, req });
this.query(req, res, new Log({ headers: req.headers }));
});
}
native() {
const http = require("http");
const server = http.createServer((req, res) => {
const log = new Log({ headers: req.headers });
const ureq = new UReq({ req, res });
const ures = new UResNative({ req, res, log });
this.query(ureq, ures, log);
});
server.on("clientError", (err, socket) => socket.end("HTTP/1.1 400 Bad Request\r\n\r\n"));
return server;
}
}
module.exports = Rest;