Skip to content

Commit

Permalink
Allow for async init and shutdown functions (#272)
Browse files Browse the repository at this point in the history
* Allow for async init and shutdown functions

* Add init and shutdown lifecycle tests

* Fix lint

* Fix failing tests
  • Loading branch information
jonirap authored Sep 5, 2023
1 parent d297261 commit d409c1f
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 101 deletions.
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Server } from 'http';
import { CloudEventFunction, HTTPFunction, InvokerOptions } from './lib/types';
import { CloudEventFunction, HTTPFunction, InvokerOptions, Function } from './lib/types';
import { LogLevel } from 'fastify';

// Invokable describes the function signature for a function that can be invoked by the server.
Expand All @@ -21,7 +21,7 @@ export declare const defaults: {
LOG_LEVEL: LogLevel,
PORT: number,
INCLUDE_RAW: boolean,
}
};

// re-export
export * from './lib/types';
60 changes: 38 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const eventHandler = require('./lib/event-handler');
const Context = require('./lib/context');
const shutdown = require('death')({ uncaughtException: true });
const fastifyRawBody = require('fastify-raw-body');
const { isPromise } = require('./lib/utils');

// HTTP framework
const fastify = require('fastify');
Expand Down Expand Up @@ -41,7 +42,10 @@ async function start(func, options) {
throw new TypeError('Function must export a handle function');
}
if (typeof func.init === 'function') {
func.init();
const initRet = func.init();
if (isPromise(initRet)) {
await initRet;
}
}
if (typeof func.shutdown === 'function') {
options.shutdown = func.shutdown;
Expand Down Expand Up @@ -79,10 +83,10 @@ async function __start(func, options) {
try {
await server.listen({
port: config.port,
host: '::'
host: '::',
});
return server.server;
} catch(err) {
} catch (err) {
console.error('Error starting server', err);
process.exit(1);
}
Expand All @@ -100,12 +104,12 @@ function initializeServer(config) {
level: config.logLevel,
formatters: {
bindings: bindings => ({
pid: bindings.pid,
hostname: bindings.hostname,
node_version: process.version
})
}
}
pid: bindings.pid,
hostname: bindings.hostname,
node_version: process.version,
}),
},
},
});

if (config.includeRaw) {
Expand All @@ -118,16 +122,20 @@ function initializeServer(config) {
}

// Give the Function an opportunity to clean up before the process exits
shutdown(_ => {
shutdown(async _ => {
if (typeof config.shutdown === 'function') {
config.shutdown();
const shutdownRet = config.shutdown();
if (isPromise(shutdownRet)) {
await shutdownRet;
}
}
server.close();
process.exit(0);
});

// Add a parser for application/x-www-form-urlencoded
server.addContentTypeParser('application/x-www-form-urlencoded',
server.addContentTypeParser(
'application/x-www-form-urlencoded',
function(_, payload, done) {
var body = '';
payload.on('data', data => (body += data));
Expand All @@ -140,18 +148,23 @@ function initializeServer(config) {
}
});
payload.on('error', done);
});
}
);

// Add a parser for everything else - parse it as a buffer and
// let this framework's router handle it
server.addContentTypeParser('*', { parseAs: 'buffer' }, function(req, body, done) {
try {
done(null, body);
} catch (err) {
err.statusCode = 500;
done(err, undefined);
server.addContentTypeParser(
'*',
{ parseAs: 'buffer' },
function(req, body, done) {
try {
done(null, body);
} catch (err) {
err.statusCode = 500;
done(err, undefined);
}
}
});
);

// Initialize the invocation context
// This is passed as a parameter to the function when it's invoked
Expand Down Expand Up @@ -210,11 +223,14 @@ function readFuncYaml(fileOrDirPath) {
if (!!maybeYaml && maybeYaml.isFile()) {
try {
return yaml.load(fs.readFileSync(yamlFile, 'utf8'));
} catch(err) {
} catch (err) {
console.warn(err);
}
}
}
}

module.exports = exports = { start, defaults: { LOG_LEVEL, PORT, INCLUDE_RAW } };
module.exports = exports = {
start,
defaults: { LOG_LEVEL, PORT, INCLUDE_RAW },
};
8 changes: 4 additions & 4 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { Http2ServerRequest, Http2ServerResponse } from 'http2';
*/
export interface Function {
// The initialization function, called before the server is started
// This function is optional and should be synchronous.
init?: () => any;
// This function is optional.
init?: () => (any | Promise<any>);

// The shutdown function, called after the server is stopped
// This function is optional and should be synchronous.
shutdown?: () => any;
// This function is optional.
shutdown?: () => (any | Promise<any>);

// The liveness function, called to check if the server is alive
// This function is optional and should return 200/OK if the server is alive.
Expand Down
9 changes: 9 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function isPromise(p) {
if (typeof p === 'object' && typeof p.then === 'function') {
return true;
}

return false;
}

module.exports = exports = { isPromise };
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"type": "commonjs",
"scripts": {
"lint": "eslint --ignore-path .gitignore .",
"fix-lint": "eslint --fix --ignore-path .gitignore .",
"test": "npm run test:source && npm run test:types",
"test:source": "nyc --reporter=lcovonly tape test/test*.js | colortape",
"test:types": "tsd",
Expand Down
Loading

0 comments on commit d409c1f

Please sign in to comment.