195 lines
6.5 KiB
JavaScript
195 lines
6.5 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.writeError = exports.TwirpServer = void 0;
|
|
const hooks_1 = require("./hooks");
|
|
const request_1 = require("./request");
|
|
const errors_1 = require("./errors");
|
|
/**
|
|
* Runtime server implementation of a TwirpServer
|
|
*/
|
|
class TwirpServer {
|
|
constructor(options) {
|
|
this.pathPrefix = "/twirp";
|
|
this.hooks = [];
|
|
this.interceptors = [];
|
|
this.packageName = options.packageName;
|
|
this.serviceName = options.serviceName;
|
|
this.methodList = options.methodList;
|
|
this.matchRoute = options.matchRoute;
|
|
this.service = options.service;
|
|
}
|
|
/**
|
|
* Returns the prefix for this server
|
|
*/
|
|
get prefix() {
|
|
return this.pathPrefix;
|
|
}
|
|
/**
|
|
* The http handler for twirp complaint endpoints
|
|
* @param options
|
|
*/
|
|
httpHandler(options) {
|
|
return (req, resp) => {
|
|
// setup prefix
|
|
if ((options === null || options === void 0 ? void 0 : options.prefix) !== undefined) {
|
|
this.withPrefix(options.prefix);
|
|
}
|
|
return this._httpHandler(req, resp);
|
|
};
|
|
}
|
|
/**
|
|
* Adds interceptors or hooks to the request stack
|
|
* @param middlewares
|
|
*/
|
|
use(...middlewares) {
|
|
middlewares.forEach((middleware) => {
|
|
if (hooks_1.isHook(middleware)) {
|
|
this.hooks.push(middleware);
|
|
return this;
|
|
}
|
|
this.interceptors.push(middleware);
|
|
});
|
|
return this;
|
|
}
|
|
/**
|
|
* Adds a prefix to the service url path
|
|
* @param prefix
|
|
*/
|
|
withPrefix(prefix) {
|
|
if (prefix === false) {
|
|
this.pathPrefix = "";
|
|
}
|
|
else {
|
|
this.pathPrefix = prefix;
|
|
}
|
|
return this;
|
|
}
|
|
/**
|
|
* Returns the regex matching path for this twirp server
|
|
*/
|
|
matchingPath() {
|
|
const baseRegex = this.baseURI().replace(/\./g, "\\.");
|
|
return new RegExp(`${baseRegex}\/(${this.methodList.join("|")})`);
|
|
}
|
|
/**
|
|
* Returns the base URI for this twirp server
|
|
*/
|
|
baseURI() {
|
|
return `${this.pathPrefix}/${this.packageName ? this.packageName + "." : ""}${this.serviceName}`;
|
|
}
|
|
/**
|
|
* Create a twirp context
|
|
* @param req
|
|
* @param res
|
|
* @private
|
|
*/
|
|
createContext(req, res) {
|
|
return {
|
|
packageName: this.packageName,
|
|
serviceName: this.serviceName,
|
|
methodName: "",
|
|
contentType: request_1.getContentType(req.headers["content-type"]),
|
|
req: req,
|
|
res: res,
|
|
};
|
|
}
|
|
/**
|
|
* Twrip server http handler implementation
|
|
* @param req
|
|
* @param resp
|
|
* @private
|
|
*/
|
|
_httpHandler(req, resp) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
const ctx = this.createContext(req, resp);
|
|
try {
|
|
yield this.invokeHook("requestReceived", ctx);
|
|
const { method, mimeContentType } = request_1.validateRequest(ctx, req, this.pathPrefix || "");
|
|
const handler = this.matchRoute(method, {
|
|
onMatch: (ctx) => {
|
|
return this.invokeHook("requestRouted", ctx);
|
|
},
|
|
onNotFound: () => {
|
|
const msg = `no handler for path ${req.url}`;
|
|
throw new errors_1.BadRouteError(msg, req.method || "", req.url || "");
|
|
},
|
|
});
|
|
const body = yield request_1.getRequestData(req);
|
|
const response = yield handler(ctx, this.service, body, this.interceptors);
|
|
yield Promise.all([
|
|
this.invokeHook("responsePrepared", ctx),
|
|
// keep backwards compatibility till next release
|
|
this.invokeHook("requestPrepared", ctx),
|
|
]);
|
|
resp.statusCode = 200;
|
|
resp.setHeader("Content-Type", mimeContentType);
|
|
resp.end(response);
|
|
}
|
|
catch (e) {
|
|
yield this.invokeHook("error", ctx, mustBeTwirpError(e));
|
|
if (!resp.headersSent) {
|
|
writeError(resp, e);
|
|
}
|
|
}
|
|
finally {
|
|
yield Promise.all([
|
|
this.invokeHook("responseSent", ctx),
|
|
// keep backwards compatibility till next release
|
|
this.invokeHook("requestSent", ctx),
|
|
]);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Invoke a hook
|
|
* @param hookName
|
|
* @param ctx
|
|
* @param err
|
|
* @protected
|
|
*/
|
|
invokeHook(hookName, ctx, err) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (this.hooks.length === 0) {
|
|
return;
|
|
}
|
|
const chainedHooks = hooks_1.chainHooks(...this.hooks);
|
|
const hook = chainedHooks === null || chainedHooks === void 0 ? void 0 : chainedHooks[hookName];
|
|
if (hook) {
|
|
yield hook(ctx, err || new errors_1.InternalServerError("internal server error"));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
exports.TwirpServer = TwirpServer;
|
|
/**
|
|
* Write http error response
|
|
* @param res
|
|
* @param error
|
|
*/
|
|
function writeError(res, error) {
|
|
const twirpError = mustBeTwirpError(error);
|
|
res.setHeader("Content-Type", "application/json");
|
|
res.statusCode = errors_1.httpStatusFromErrorCode(twirpError.code);
|
|
res.end(twirpError.toJSON());
|
|
}
|
|
exports.writeError = writeError;
|
|
/**
|
|
* Make sure that the error passed is a TwirpError
|
|
* otherwise it will wrap it into an InternalError
|
|
* @param err
|
|
*/
|
|
function mustBeTwirpError(err) {
|
|
if (err instanceof errors_1.TwirpError) {
|
|
return err;
|
|
}
|
|
return new errors_1.InternalServerErrorWith(err);
|
|
}
|