Https Server
An HTTPS server with a built-in router.
This module can be used to create an HTTPS server that can route requests to a pre-defined set of handlers. It also provides an interface that makes it trivial to run such a server from the command line.
Each request received is assigned a unique id that is included in all associated log entries and is returned to the requester in the Request-Id
header of the response.
If a request is received for an unhandled pathname or method the server will log the occurrence and respond with the proper HTTP response codes.
Similarly, if an error is encountered while handling a defined route, the occurrence will be logged and the proper HTTP response will be sent to the requester.
Installation
npm install @panosoft/https-server
Usage
The cli
method can be used to quickly create a server that can be run from the command line:
#! /usr/bin/env node
const HttpsServer = require('@panosoft/https-server');
const packageFilename = '/path/to/top-level/pacakge.json';
const routes = {
'/': {
GET: (request, response, log) => response.end('Hello World')
}
};
HttpsServer.cli(packageFilename, routes);
Or, the API can be used directly:
const bunyan = require('bunyan');
const fs = require('fs');
const HttpsServer = require('@panosoft/https-server');
const options = {
key: fs.readFileSync('path/to/key.pem'),
cert: fs.readFileSync('path/to/cert.pem'),
logger: bunyan.createLogger({name: 'server'})
};
const routes = {
'/': {
GET: (request, response, log) => response.end('Hello World')
}
};
const server = HttpsServer.create(options, routes);
yield server.listen(8443, '127.0.0.1');
server.address();
yield server.connections();
yield server.close();
Routes
An object that determines how requests will be routed and handled. It has the following structure:
{
<pathname> : {
<method>: <handler>,
...
},
...
}
Where:
-
pathname
- {String} Any valid path. -
method
- {String} Any valid HTTP method. -
handler
- {Function}A function to be called when the specified pathname and method are requested.
This function will be called with the
request
andresponse
streams generated by Node'shttps.Server
along with a Bunyan logger that includes the serialized request with every log entry.This function should handle the entire request, write any necessary log entries, and call
response.end()
when it has completed.This function will be run asynchronously if it returns a
Promise
. Otherwise, it will be run synchronously.In the case of synchronous handlers, when an error is encountered, it should be thrown.
In the case of asynchronous handlers, when an error is encountered the returned
Promise
should be rejected with the error.All handler errors will be automatically be logged by the server and an HTTP error response will automatically be sent to the requester.
Example
const routes = {
'/': {
GET: (request, response, log) => {}
},
'/user': {
GET:(request, response, log) => {},
POST: (request, response, log) => {},
DELETE: (request, response, log) => {}
}
};
Error Responses
Path Not Found
When a requested path is not defined in Routes
, the following error response is sent to the requester:
- Status Code:
404
- Headers:
-
Request-Id
- {String} The unique id assigned to the associated request. -
Content-Type
-'application/json'
-
- Body:
-
error
-'${pathname} not found'
-
Method Not Defined
When an undefined method for a path defined in Routes
is requested, the following error response is sent to the requester:
- Status Code:
405
- Headers:
-
Request-Id
- {String} The unique id assigned to the associated request. -
Content-Type
-'application/json'
-
Allow
- {String} A comma separated list of valid methods for the requested path.
-
- Body:
-
error
-'${method} not allowed for ${pathname}'
-
Internal Server error
When an error is thrown or a returned promise is rejected by a route handler, the following error response is sent to the requester:
- Status Code:
500
- Headers:
-
Request-Id
- {String} The unique id assigned to the associated request. -
Content-Type
-'application/json'
-
- Body:
-
error
- {String} The associated error message.
-
API
cli ( packageFilename , routes )
Run the server as a node command line program.
Help and version commands are automatically generated using the contents of the package.json
file found at the provided path.
Log entries are written to stdout
and stderr
as appropriate.
Also, the following process events are also handled gracefully and appropriately (i.e. open connections are closed and appropriate exit codes are returned by the process):
-
SIGINT
-
SIGTERM
-
unhandledRejection
-
uncaughtException
When run as a command the following interface is available:
Usage: command-name --key <path> --cert <path> [options]
Description from package.json ...
Options:
-h, --help output usage information
-V, --version output the version number
-k, --key <path> Path to the private key of the server in PEM format.
-c, --cert <path> Path to the certificate key of the server in PEM format.
-p, --port <port> The port to accept connections on. Default: 8443.
-i, --interface <interface> The interface to accept connections on. Default: 0.0.0.0.
Arguments
-
packageFilename
- {String} A path to thepackage.json
for the module calling this function. -
routes
- {Routes} A routes object.
Example
#! /usr/bin/env node
const HttpsServer = require('@panosoft/https-server');
const packageFilename = '/path/to/top-level/pacakge.json';
const routes = {
'/': { GET: (request, response, log) => response.end('Hello World') }
};
HttpsServer.cli(packageFilename, routes);
create ( options , routes )
Create a new instance of HttpsServer.
Arguments
-
options
-
key
- (Required) {String} The private key of the server in PEM format. -
cert
- (Required) {String} The certificate key of the server in PEM format. -
logger
- {Bunyan Logger} An instance of a Bunyan logger. -
... any other supported Node https.Server options
-
-
routes
- {Routes} A routes object.
Example
const options = {
key: fs.readFileSync('path/to/key.pem'),
cert: fs.readFileSync('path/to/cert.pem'),
logger: bunyan.createLogger({name: 'server'})
};
const routes = {
'/': { GET: (request, response, log) => response.end('Hello World') }
};
const server = HttpsServer.create(options, routes);
listen ( ... )
A thin wrapper around Node's server.listen method that returns a Promise
instead of taking a callback.
address ( )
Equivalent to Node's server.address method.
connections ( )
A thin wrapper around Node's server.getConnections method that returns a Promise
instead of taking a callback.
close ( )
A thin wrapper around Node's server.close method that returns a Promise
instead of taking a callback.
Also, this method differs from Node's server.close in that it will not return an error if it is called while the server is not listening.
test.startServer ( filename [, options] )
A helper function that makes it simple to test cli servers that use the cli
method.
This function spawns the cli server in a child process. It returns a Promise
that is fullfilled with a reference to the server process once the server has started successfully. If an error is encountered, the promise will be rejected with the error.
Arguments
-
filename
- {String} File path to an executable file that calls thecli
method. -
options
- {Object} An object of server options.-
port
- {Number} The port to start the server on. -
interface
- {String} The interface to start the server on.
-
Example
Assuming we have and executable file at path/to/executable.js
with the following contents:
#! /usr/bin/env node
const HttpsServer = require('@panosoft/https-server');
const path = require('path');
const packageFilename = path.resolve(__dirname, '../package.json');
const routes = { '/': { 'POST': (req, res, log) => res.end('root') }};
HttpsServer.cli(packageFilename, routes);
Then we can use the test.startServer
method to run this executable file and know when the server has started:
const co = require('co');
const HttpsServer = require('@panosoft/https-server');
co(function * () {
const filename = `path/to/executable.js`;
const server = yield HttpsServer.test.startServer(filename);
// make requests ...
server.kill();
})
test.request ( pathname , method , headers , data )
A helper function that makes it simple to make requests to a test instance of https-server being run with test.startServer
. This method handles making requests with the self-signed TLS certificate used by test.startServer
.
This method makes a request that will recognize the the self-signed TLS certificate used by test.startServer
as valid. It returns a Promise
that is fulfilled with the completed response that includes the response body at response.body
.
Arguments
-
pathname
- {String} The pathname to request. -
method
- {String} The HTTP method to use. Defaults toGET
. -
headers
- {Object} The headers to include with the request. Defaults to{}
. -
data
- {*} The data to send along with the request. Defaults tonull
.
Example
Continuing the test.startServer
example above ...
const co = require('co');
const HttpsServer = require('@panosoft/https-server');
co(function * () {
const filename = `path/to/executable.js`;
const server = yield HttpsServer.test.startServer(filename);
const pathname = '/';
const method = 'POST';
const headers = { 'Content-Type': 'application/json' };
const data = JSON.stringify({ a: 1 });
const response = yield request( pathname, method, headers, data);
console.log(response.body.toString('utf8')); //=> 'root'
server.kill();
})