@stringstack/express
v0.2.1
Published
StringStack/express is a component container for ExpressJS that allows you to easily include the ExpressJS framework in your StringStack application.
Downloads
7
Readme
StringStack Express
StringStack/express is a component container for ExpressJS that allows you to easily include the ExpressJS framework in your StringStack application. The goal of this component is as follows:
- Map StringStack init/dinit patterns to ExpressJS.
- Provide graceful startup/shutdown via Stoppable.
- Have ZERO interference between your routes and ExpressJS.
Migrating from 0.1.x to 0.2.x
Although the APIs for 0.1.x and 0.2.x are compatible, logging mentality has changed. In 0.1.x all HTTP/S requests were passed to the StringStack logger component. In order to do this requests and responses had to be tracked with a middleware. This was contrary to our goal of having zero interference between your routes and express. Even if you didn't enable the debug logging level, the effort was still made behind the scenes to create the log entry even if your log facility ignored the entry.
If you would like to recreate this logging in your code, simply add this middleware in one of your route components, before all other middleware or route handlers:
this._logger = deps.get( 'logger' );
this._app = deps.get( '@stringstack/express' ).getApp();
let requestId = 0;
this._app.use( ( req, res, next ) => {
requestId++;
// reset counter at a billion. if your process handles this many requests without a restart, you don't release
// often enough.
if ( requestId > 1000000000 ) {
requestId = 1;
}
let entry = {
requestId: requestId,
request: {
protocol: typeof req.protocol === 'string' ? req.protocol.toLowerCase() : null,
hostname: typeof req.hostname === 'string' ? req.hostname.toLowerCase() : null,
path: typeof req.path === 'string' ? req.path : null,
method: typeof req.method === 'string' ? req.method.toLowerCase() : null,
secure: req.secure,
headers: req.headers,
ip: req.ip,
ips: req.ips,
query: req.query || {},
cookies: null
},
response: {
headers: {},
statusCode: null
}
};
let ips = entry.request.ips.length > 0 ? entry.request.ips : [ entry.request.ip ];
let message = `START [${entry.requestId}] ${entry.request.protocol} ${entry.request.method}: ${entry.request.hostname}: ${entry.request.path}: ${JSON.stringify(
ips )}`;
this._logger( 'debug', message, entry );
res.once( 'finish', () => {
setImmediate( () => {
// if a user supplied middleware parses body or cookies, add it to the log
entry.request.cookies = req.cookies || null;
entry.request.body = req.body || null;
// manually pull headers because getHeaders() does not return a plain Object instance
entry.response.headers = {};
res.getHeaderNames().forEach( ( header ) => {
entry.response.headers[ header ] = res.getHeader( header );
} );
entry.response.statusCode = res.statusCode || null;
let message = `FINISH [${entry.requestId}] ${entry.request.protocol} ${entry.request.method}: ${entry.request.hostname}: ${entry.request.path}: ${JSON.stringify(
ips )}`;
this._logger( 'debug', message, entry );
} );
} );
next();
} );
New logging was also added for confirming what ports are being opened, when the ports are open, which servers are closing, and finally confirming when each server has been closed.
Installation
npm install @stringstack/express --save
This will also install ExpressJS for you. See the version log at the end of this document to see which version of ExpressJS is provided with each version of StringStack/express.
Configuration
StringStack/express looks for configuration in the nconf container provided by
StringStack. Store the configuration in nconf at the path
stringstack:express
. The configuration is an object of the following schema.
{
"http": {
"enabled": true,
"port": 8080
},
"https": {
"enabled": false,
"port": 8443,
"options": {
"key": "",
"cert": ""
}
}
}
http.enabled: <boolean> indicates whether to create an HTTP listener. Default: false
http.port: <integer> Ignored if http.enabled is false. The port to listen on for HTTP. Default: 8080
https.enabled: <boolean> indicates whether to create an HTTPS listener. Default: false
https.port: <integer> Ignored if https.enabled is false. The port to listen on for HTTPS. Default: 8443
https.options: <object> This field is passed directly to the NodeJS https.createServer() method. See
https://nodejs.org/en/docs/ for configuration options. The default config has self-signed certs so you can get going
quickly with TLS, but these should NOT be used in production.
The best way to set the config would be to create a config setup component and pass it to rootComponents when creating the App instance. See StringStack documentation for examples of setting rootComponents.
You would use the setup component to set all config values for your entire StringStack app.
An example setup component.
class SetupComponent {
constructor( deps ) {
this._nconf = deps.get( 'config' );
}
init( done ) {
asynchronousLoadExpressConfigFromFileServerWhatever( ( err, config ) => {
if (err) {
return done( err );
}
this._nconf.defaults({
stringstack: {
express: config
}
});
} );
}
dinit( done ) {
done();
}
}
module.exports = SetupComponent;
Usage
The StringStack/express component is a minimal wrapper around ExpressJS. The component simply adds the hooks to start and stop the listeners in a sane manner. It also utilizes https://www.npmjs.com/package/stoppable to ensure web services start and stop in a predictable manner. See the documentation on stoppable for details.
In order to access ExpressJS and add your custom configuration, you will need to create your own component that configures ExpressJS. Here is an example component that will configure ExpressJS to echo back any request made.
class MyExpressEchoRouteSetupComponent {
constructor( deps ) {
// use inject instead of get to ensure that this component inits before stringstack/express opens ports.
this._express = deps.inject( '@stringstack/express' );
}
init( done ) {
// getApp() returns the result of express(). However, getApp() always returns the same instance of express().
let app = this._express.getApp();
// setup a route to echo all requests
app.use( ( req, res ) => {
let response = {};
response.method = req.method;
response.url = req.url;
response.headers = req.headers;
response.httpVersionMajor = req.httpVersionMajor;
response.httpVersionMinor = req.httpVersionMinor;
response.httpVersion = req.httpVersion;
response.body = req.body;
response.query = req.query;
response.cookies = req.cookies;
response.remoteAddress = req.connection.remoteAddress;
response.remotePort = req.connection.remotePort;
res.json( response );
} );
done();
}
dinit( done ) {
done();
}
}
module.exports = MyExpressEchoRouteSetupComponent;
Here we see that the ExpressJS component has a method getApp(). This allows the component MyExpressEchoRouteSetupComponent to register all of its configuration and routes before the ExpressJS component starts listening.
ExpressJS won't start listening for requests until MyExpressEchoRouteSetupComponent.init() is called and calls done().
Setup as many route components as needed, or one giant route component. Multiple components helps organize code, but it may not be clear which routes create in what order. If your API is
Logging
These are the events that are logged, including the log level used.
- info:
opening http/s on port <port number>
# logged when attempting to open the specified port for specified server - info:
http/s listening on port <port number>
# logged when server is accepting requests on specified port - info:
stopping http/s
# logged when stopping specified server, inflight requests may be in progress buy no new requests can be accepted. Any reverse proxies will see the inbound port as closed and may start draining traffic. - info:
stopping http/s
# logged when the server has no more connections and is completely shutdown.
Security
This component is made to be VERY lean. Web servers are very dynamic tools and the valid use cases for how to use them are across the board. Web server security is also one of those things that has numerous valid use cases. As such this component does not attempt to enforce any security semantics beyond those built in to NodeJS itself and ExpressJS.
Any security flags you wish to set or unset is up to you. The only explicit configuration exposed for security is the options field for an HTTPS listener. Other than that, security is on you, just like it is with raw ExpressJS.
Version Log
This is a log of which version of ExpressJS is provided by each version of StringStack/express.
- @stringstack/[email protected] => [email protected]
- @stringstack/[email protected] => [email protected]
- @stringstack/[email protected] => [email protected]