@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
206 lines • 9.55 kB
JavaScript
;
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