Skip to content

Commit

Permalink
allow listening on multiple endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Qix- authored and leo committed Apr 24, 2018
1 parent 801fdb1 commit 2aed2f7
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 83 deletions.
157 changes: 111 additions & 46 deletions bin/micro.js
Expand Up @@ -5,80 +5,147 @@ const path = require('path');
const {existsSync} = require('fs');

// Packages
const parseArgs = require('mri');
const arg = require('arg');
const chalk = require('chalk');

// Utilities
const serve = require('../lib');
const handle = require('../lib/handler');
const generateHelp = require('../lib/help');
const {version} = require('../package');
const logError = require('../lib/error');
const parseEndpoint = require('../lib/parse-endpoint.js');

// Check if the user defined any options
const flags = parseArgs(process.argv.slice(2), {
alias: {
p: 'port',
H: 'host',
s: 'unix-socket',
h: 'help',
v: 'version'
},
unknown(flag) {
console.log(`The option "${flag}" is unknown. Use one of these:`);
console.log(generateHelp());
process.exit(1);
}
const args = arg({
'--listen': [parseEndpoint],
'-l': '--listen',

'--help': Boolean,

'--version': Boolean,
'-v': '--version',

// Deprecated options
'--port': Number,
'-p': '--port',
'--host': String,
'-h': '--host',
'--unix-socket': String,
'-s': '--unix-socket'
});

// When `-h` or `--help` are used, print out
// the usage information
if (flags.help) {
console.log(generateHelp());
process.exit();
if (args['--help']) {
console.error(chalk`
{bold.cyan micro} - Asynchronous HTTP microservices
{bold USAGE}
{bold $} {cyan micro} --help
{bold $} {cyan micro} --version
{bold $} {cyan micro} [-l {underline listen_uri} [-l ...]] [{underline entry_point.js}]
By default {cyan micro} will listen on {bold 0.0.0.0:3000} and will look first
for the {bold "main"} property in package.json and subsequently for {bold index.js}
as the default {underline entry_point}.
Specifying a single {bold --listen} argument will overwrite the default, not supplement it.
{bold OPTIONS}
--help shows this help message
-v, --version displays the current version of micro
-l, --listen {underline listen_uri} specify a URI endpoint on which to listen (see below) -
more than one may be specified to listen in multiple places
{bold ENDPOINTS}
Listen endpoints (specified by the {bold --listen} or {bold -l} options above) instruct {cyan micro}
to listen on one or more interfaces/ports, UNIX domain sockets, or Windows named pipes.
For TCP (traditional host/port) endpoints:
{bold $} {cyan micro} -l tcp://{underline hostname}:{underline 1234}
For UNIX domain socket endpoints:
{bold $} {cyan micro} -l unix:{underline /path/to/socket.sock}
For Windows named pipe endpoints:
{bold $} {cyan micro} -l pipe:\\\\.\\pipe\\{underline PipeName}
`);
process.exit(2);
}

// Print out the package's version when
// `--version` or `-v` are used
if (flags.version) {
if (args['--version']) {
console.log(version);
process.exit();
}

if (flags.port && flags['unix-socket']) {
if ((args['--port'] || args['--host']) && args['--unix-socket']) {
logError(
`Both port and socket provided. You can only use one.`,
`Both host/port and socket provided. You can only use one.`,
'invalid-port-socket'
);
process.exit(1);
}

let listenTo = 3000;
let deprecatedEndpoint = null;

if (flags.port) {
args['--listen'] = args['--listen'] || [];

if (args['--port']) {
const {isNaN} = Number;
const port = Number(flags.port);
const port = Number(args['--port']);
if (isNaN(port) || (!isNaN(port) && (port < 1 || port >= Math.pow(2, 16)))) {
logError(
`Port option must be a number. Supplied: ${flags.port}`,
`Port option must be a number. Supplied: ${args['--port']}`,
'invalid-server-port'
);
process.exit(1);
}

listenTo = flags.port;
deprecatedEndpoint = [args['--port']];
}

if (args['--host']) {
deprecatedEndpoint = deprecatedEndpoint || [];
deprecatedEndpoint.push(args['--host']);
}

if (deprecatedEndpoint) {
args['--listen'].push(deprecatedEndpoint);
}

if (flags['unix-socket']) {
if (typeof flags['unix-socket'] === 'boolean') {
if (args['--unix-socket']) {
if (typeof args['--unix-socket'] === 'boolean') {
logError(
`Socket must be a string. A boolean was provided.`,
'invalid-socket'
);
}
listenTo = flags['unix-socket'];
args['--listen'].push(args['--unix-socket']);
}

let file = flags._[0];
if (args['--port'] || args['--host'] || args['--unix-socket']) {
logError(
'--port, --host, and --unix-socket are deprecated - see --help for information on the --listen flag',
'deprecated-endpoint-flags'
);
}

if (args['--listen'].length === 0) {
// default endpoint
args['--listen'].push([3000]);
}

let file = args._[0];

if (!file) {
try {
Expand Down Expand Up @@ -112,21 +179,15 @@ if (!existsSync(file)) {
process.exit(1);
}

async function start() {
const loadedModule = await handle(file);
const server = serve(loadedModule);
function startEndpoint(module, endpoint) {
const server = serve(module);

server.on('error', err => {
console.error('micro:', err.stack);
process.exit(1);
});

const listenArgs = [listenTo];
if (flags.host) {
listenArgs.push(flags.host);
}

server.listen(...listenArgs, () => {
server.listen(...endpoint, () => {
const details = server.address();

const shutdown = () => {
Expand All @@ -136,21 +197,25 @@ async function start() {

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
process.on('exit', shutdown);

// `micro` is designed to run only in production, so
// this message is perfectly for prod
if (typeof details === 'string') {
console.log(`micro: Accepting connections on ${details}`);
return;
}

if (typeof details === 'object' && details.port) {
} else if (typeof details === 'object' && details.port) {
console.log(`micro: Accepting connections on port ${details.port}`);
return;
} else {
console.log('micro: Accepting connections');
}

console.log('micro: Accepting connections');
});
}

async function start() {
const loadedModule = await handle(file);
for (const endpoint of args['--listen']) {
startEndpoint(loadedModule, endpoint);
}
}

start();
15 changes: 0 additions & 15 deletions lib/help.js

This file was deleted.

26 changes: 26 additions & 0 deletions lib/parse-endpoint.js
@@ -0,0 +1,26 @@
const {URL} = require('url');

module.exports = function parseEndpoint(str) {
const url = new URL(str);

switch (url.protocol) {
case 'pipe:': {
// some special handling
const cutStr = str.replace(/^pipe:/, '');
if (cutStr.slice(0, 4) !== '\\\\.\\') {
throw new Error(`Invalid Windows named pipe endpoint: ${str}`);
}
return [cutStr];
}
case 'unix:':
if (!url.pathname) {
throw new Error(`Invalid UNIX domain socket endpoint: ${str}`);
}
return [url.pathname];
case 'tcp:':
url.port = url.port || '3000';
return [parseInt(url.port, 10), url.hostname];
default:
throw new Error(`Unknown --listen endpoint scheme (protocol): ${url.protocol}`);
}
};
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -40,9 +40,10 @@
"then-sleep": "1.0.1"
},
"dependencies": {
"arg": "2.0.0",
"chalk": "2.4.0",
"content-type": "1.0.4",
"is-stream": "1.1.0",
"mri": "1.1.0",
"raw-body": "2.3.2"
},
"eslintConfig": {
Expand Down
9 changes: 0 additions & 9 deletions test/help.js

This file was deleted.

38 changes: 38 additions & 0 deletions test/parse-endpoint.js
@@ -0,0 +1,38 @@
const test = require('ava');

const parseEndpoint = require('../lib/parse-endpoint');

test('parses TCP URI', async t => {
t.deepEqual(parseEndpoint('tcp://my-host-name.foo.bar:12345'), [12345, 'my-host-name.foo.bar']);
t.deepEqual(parseEndpoint('tcp://0.0.0.0:8080'), [8080, '0.0.0.0']);

// with the default
t.deepEqual(parseEndpoint('tcp://1.2.3.4'), [3000, '1.2.3.4']);
});

test('parses UNIX domain socket URI', async t => {
t.deepEqual(parseEndpoint('unix:/foo/bar.sock'), ['/foo/bar.sock']);
t.deepEqual(parseEndpoint('unix:///foo/bar.sock'), ['/foo/bar.sock']);
});

test('parses Windows named pipe URI', async t => {
t.deepEqual(parseEndpoint('pipe:\\\\.\\pipe\\some-name'), ['\\\\.\\pipe\\some-name']);
});

test('throws on invalid URI', async t => {
t.throws(() => parseEndpoint('qwertyuiop'), 'Invalid URL: qwertyuiop');
t.throws(() => parseEndpoint('tcp://:8080'), 'Invalid URL: tcp://:8080');
});

test('throws on invalid scheme (protocol)', async t => {
t.throws(() => parseEndpoint('foobar://blah'), 'Unknown --listen endpoint scheme (protocol): foobar:');
});

test('throws on invalid Windows named pipe', async t => {
t.throws(() => parseEndpoint('pipe:lolsickbro'), 'Invalid Windows named pipe endpoint: pipe:lolsickbro');
t.throws(() => parseEndpoint('pipe://./pipe/lol'), 'Invalid Windows named pipe endpoint: pipe://./pipe/lol');
});

test('throws on invalid UNIX domain socket', async t => {
t.throws(() => parseEndpoint('unix:'), 'Invalid UNIX domain socket endpoint: unix:');
});
24 changes: 12 additions & 12 deletions yarn.lock
Expand Up @@ -185,6 +185,10 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"

arg@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"

arg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arg/-/arg-1.0.1.tgz#892a26d841bd5a64880bbc8f73dd64a705910ca3"
Expand Down Expand Up @@ -883,6 +887,14 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"

chalk@2.4.0, chalk@^2.0.0, chalk@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52"
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"

chalk@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
Expand All @@ -901,14 +913,6 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"

chalk@^2.0.0, chalk@^2.3.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52"
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"

chalk@^2.0.1, chalk@^2.1.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
Expand Down Expand Up @@ -2785,10 +2789,6 @@ mixin-deep@^1.2.0:
dependencies:
minimist "0.0.8"

mri@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.0.tgz#5c0a3f29c8ccffbbb1ec941dcec09d71fa32f36a"

ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
Expand Down

0 comments on commit 2aed2f7

Please sign in to comment.