UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

206 lines 9.55 kB
"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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalServer = void 0; const common_1 = require("@bitblit/ratchet/common"); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const luxon_1 = require("luxon"); const event_util_1 = require("./http/event-util"); const local_server_cert_1 = require("./local-server-cert"); /** * A simplistic server for testing your lambdas locally */ class LocalServer { constructor(globalHandler, port = 8888, https = false) { this.globalHandler = globalHandler; this.port = port; this.https = https; } runServer() { return __awaiter(this, void 0, void 0, function* () { return new Promise((res, rej) => { try { common_1.Logger.info('Starting Epsilon server on port %d', this.port); if (this.https) { const options = { key: local_server_cert_1.LocalServerCert.CLIENT_KEY_PEM, cert: local_server_cert_1.LocalServerCert.CLIENT_CERT_PEM, }; common_1.Logger.info('Starting https server - THIS SERVER IS NOT SECURE! The KEYS are in the code! Testing Server Only - Use at your own risk!'); this.server = https_1.default.createServer(options, this.requestHandler.bind(this)).listen(this.port); } else { this.server = http_1.default.createServer(this.requestHandler.bind(this)).listen(this.port); } common_1.Logger.info('Epsilon server is listening'); // Also listen for SIGINT process.on('SIGINT', () => { common_1.Logger.info('Caught SIGINT - shutting down test server...'); this.server.close(); res(true); }); } catch (err) { common_1.Logger.error('Local server failed : %s', err, err); rej(err); } }); }); } requestHandler(request, response) { return __awaiter(this, void 0, void 0, function* () { const context = { awsRequestId: 'LOCAL-' + common_1.StringRatchet.createType4Guid(), getRemainingTimeInMillis() { return 300000; }, }; //TBD const evt = yield LocalServer.messageToApiGatewayEvent(request, context); const logEventLevel = event_util_1.EventUtil.eventIsAGraphQLIntrospection(evt) ? common_1.LoggerLevelName.silly : common_1.LoggerLevelName.info; if (evt.path == '/epsilon-poison-pill') { this.server.close(); return true; } else { const result = yield this.globalHandler.lambdaHandler(evt, context); const written = yield LocalServer.writeProxyResultToServerResponse(result, response, logEventLevel); return written; } }); } static bodyAsBase64String(request) { return __awaiter(this, void 0, void 0, function* () { return new Promise((res, rej) => { const body = []; request.on('data', (chunk) => { body.push(chunk); }); request.on('end', () => { const rval = Buffer.concat(body).toString('base64'); res(rval); }); }); }); } static messageToApiGatewayEvent(request, context) { return __awaiter(this, void 0, void 0, function* () { const bodyString = yield LocalServer.bodyAsBase64String(request); const stageIdx = request.url.indexOf('/', 1); const stage = request.url.substring(1, stageIdx); const path = request.url.substring(stageIdx + 1); const reqTime = new Date().getTime(); const formattedTime = luxon_1.DateTime.utc().toFormat('dd/MMM/yyyy:hh:mm:ss ZZ'); const queryStringParams = LocalServer.parseQueryParamsFromUrlString(path); const headers = Object.assign({}, request.headers); headers['X-Forwarded-Proto'] = 'http'; // This server is always unencrypted const rval = { body: bodyString, multiValueHeaders: {}, multiValueQueryStringParameters: {}, resource: '/{proxy+}', path: request.url, httpMethod: request.method.toLowerCase(), isBase64Encoded: true, queryStringParameters: queryStringParams, pathParameters: { proxy: path, }, stageVariables: { baz: 'qux', }, headers: headers, requestContext: { accountId: '123456789012', resourceId: '123456', stage: stage, requestId: context.awsRequestId, requestTime: formattedTime, requestTimeEpoch: reqTime, identity: null, /* identity: { apiKey: null, cognitoIdentityPoolId: null, accountId: null, cognitoIdentityId: null, caller: null, accessKey: null, sourceIp: '127.0.0.1', cognitoAuthenticationType: null, cognitoAuthenticationProvider: null, userArn: null, userAgent: 'Custom User Agent String', user: null }, */ path: request.url, domainName: request.headers['host'], resourcePath: '/{proxy+}', httpMethod: request.method.toLowerCase(), apiId: '1234567890', protocol: 'HTTP/1.1', authorizer: null, }, }; return rval; }); } static writeProxyResultToServerResponse(proxyResult, response, logLevel) { return __awaiter(this, void 0, void 0, function* () { const isGraphQLSchemaResponse = !!proxyResult && !!proxyResult.body && proxyResult.body.indexOf('{"data":{"__schema"') > -1; if (!isGraphQLSchemaResponse) { common_1.Logger.logByLevel(logLevel, 'Result: %j', proxyResult); } response.statusCode = proxyResult.statusCode; if (proxyResult.headers) { Object.keys(proxyResult.headers).forEach((hk) => { response.setHeader(hk, String(proxyResult.headers[hk])); }); } if (proxyResult.multiValueHeaders) { Object.keys(proxyResult.multiValueHeaders).forEach((hk) => { response.setHeader(hk, proxyResult.multiValueHeaders[hk].join(',')); }); } const toWrite = proxyResult.isBase64Encoded ? Buffer.from(proxyResult.body, 'base64') : Buffer.from(proxyResult.body); response.end(toWrite); return !!proxyResult.body; }); } /** * Takes in a URL string and returns the parsed URL query params in the way the ALB / Lambda * integration does. * Note that it does not URL decode the values. */ static parseQueryParamsFromUrlString(urlString) { const rval = {}; const searchStringParts = urlString.split('?'); if (searchStringParts.length < 2) { // No query string. return rval; } const searchString = searchStringParts.slice(1).join('?'); const searchParts = searchString.split('&'); for (const eachKeyValueString of searchParts) { const eachKeyValueStringParts = eachKeyValueString.split('='); const eachKey = eachKeyValueStringParts[0]; const eachValue = eachKeyValueStringParts.slice(1).join('='); rval[eachKey] = eachValue; } return rval; } } exports.LocalServer = LocalServer; //# sourceMappingURL=local-server.js.map