--- /dev/null
+#!/usr/bin/env node
+
+var fs = require('fs'),
+ tty = require('tty'),
+ statik = require('./../lib/node-static');
+
+ var argv = require('optimist')
+ .usage([
+ 'USAGE: $0 [-p <port>] [<directory>]',
+ 'simple, rfc 2616 compliant file streaming module for node']
+ .join('\n\n'))
+ .option('port', {
+ alias: 'p',
+ 'default': 8080,
+ description: 'TCP port at which the files will be served'
+ })
+ .option('host-address', {
+ alias: 'a',
+ 'default': '127.0.0.1',
+ description: 'the local network interface at which to listen'
+ })
+ .option('cache', {
+ alias: 'c',
+ description: '"Cache-Control" header setting, defaults to 3600'
+ })
+ .option('version', {
+ alias: 'v',
+ description: 'node-static version'
+ })
+ .option('headers', {
+ alias: 'H',
+ description: 'additional headers (in JSON format)'
+ })
+ .option('header-file', {
+ alias: 'f',
+ description: 'JSON file of additional headers'
+ })
+ .option('gzip', {
+ alias: 'z',
+ description: 'enable compression (tries to serve file of same name plus \'.gz\')'
+ })
+ .option('spa', {
+ description: 'serve the content as a single page app by redirecting all non-file requests to the index html file'
+ })
+ .option('indexFile', {
+ alias: 'i',
+ 'default': 'index.html',
+ description: 'specify a custom index file when serving up directories'
+ })
+ .option('help', {
+ alias: 'h',
+ description: 'display this help message'
+ })
+ .argv;
+
+ var dir = argv._[0] || '.';
+
+ var colors = require('colors');
+
+ var log = function(request, response, statusCode) {
+ var d = new Date();
+ var seconds = d.getSeconds() < 10? '0'+d.getSeconds() : d.getSeconds(),
+ datestr = d.getHours() + ':' + d.getMinutes() + ':' + seconds,
+ line = datestr + ' [' + response.statusCode + ']: ' + request.url,
+ colorized = line;
+ if (tty.isatty(process.stdout.fd))
+ colorized = (response.statusCode >= 500) ? line.red.bold :
+ (response.statusCode >= 400) ? line.red :
+ line;
+ console.log(colorized);
+ };
+
+ var file, options;
+
+if (argv.help) {
+ require('optimist').showHelp(console.log);
+ process.exit(0);
+}
+
+if (argv.version) {
+ console.log('node-static', statik.version.join('.'));
+ process.exit(0);
+}
+
+if (argv.cache) {
+ (options = options || {}).cache = argv.cache;
+}
+
+if (argv.headers) {
+ (options = options || {}).headers = JSON.parse(argv.headers);
+}
+
+if (argv['header-file']) {
+ (options = options || {}).headers =
+ JSON.parse(fs.readFileSync(argv['header-file']));
+}
+
+if (argv.gzip) {
+ (options = options || {}).gzip = true;
+}
+
+if (argv.indexFile) {
+ (options = options || {}).indexFile = argv['indexFile'];
+}
+
+file = new(statik.Server)(dir, options);
+
+require('http').createServer(function (request, response) {
+ request.addListener('end', function () {
+ var callback = function(e, rsp) {
+ if (e && e.status === 404) {
+ response.writeHead(e.status, e.headers);
+ response.end("Not Found");
+ log(request, response);
+ } else {
+ log(request, response);
+ }
+ };
+
+ if (argv['spa'] && request.url.indexOf(".") == -1) {
+ file.serveFile(argv['indexFile'], 200, {}, request, response);
+ } else {
+ file.serve(request, response, callback);
+ }
+ }).resume();
+}).listen(+argv.port, argv['host-address']);
+
+console.log('serving "' + dir + '" at http://' + argv['host-address'] + ':' + argv.port);
+if (argv.spa) {
+ console.log('serving as a single page app (all non-file requests redirect to ' + argv['indexFile'] +')');
+}
--- /dev/null
+#!/usr/bin/env node
+
+/*!
+ * ws: a node.js websocket client
+ * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
+ * MIT Licensed
+ */
+
+const EventEmitter = require('events');
+const fs = require('fs');
+const program = require('commander');
+const read = require('read');
+const readline = require('readline');
+const tty = require('tty');
+const WebSocket = require('ws');
+
+/**
+ * InputReader - processes console input.
+ *
+ * @extends EventEmitter
+ */
+class Console extends EventEmitter {
+ constructor() {
+ super();
+
+ this.stdin = process.stdin;
+ this.stdout = process.stdout;
+
+ this.readlineInterface = readline.createInterface(this.stdin, this.stdout);
+
+ this.readlineInterface
+ .on('line', data => {
+ this.emit('line', data);
+ })
+ .on('close', () => {
+ this.emit('close');
+ });
+
+ this._resetInput = () => {
+ this.clear();
+ };
+ }
+
+ static get Colors() {
+ return {
+ Red: '\u001b[31m',
+ Green: '\u001b[32m',
+ Yellow: '\u001b[33m',
+ Blue: '\u001b[34m',
+ Default: '\u001b[39m'
+ };
+ }
+
+ static get Types() {
+ return {
+ Incoming: '< ',
+ Control: '',
+ Error: 'error: '
+ };
+ }
+
+ prompt() {
+ this.readlineInterface.prompt();
+ }
+
+ print(type, msg, color) {
+ if (tty.isatty(1)) {
+ this.clear();
+
+ if (program.execute) color = type = '';
+ else if (!program.color) color = '';
+
+ this.stdout.write(color + type + msg + Console.Colors.Default + '\n');
+ this.prompt();
+ } else if (type === Console.Types.Incoming) {
+ this.stdout.write(msg + '\n');
+ } else {
+ // is a control message and we're not in a tty... drop it.
+ }
+ }
+
+ clear() {
+ if (tty.isatty(1)) {
+ this.stdout.write('\u001b[2K\u001b[3D');
+ }
+ }
+
+ pause() {
+ this.stdin.on('keypress', this._resetInput);
+ }
+
+ resume() {
+ this.stdin.removeListener('keypress', this._resetInput);
+ }
+}
+
+function collect(val, memo) {
+ memo.push(val);
+ return memo;
+}
+
+function noop() {}
+
+/**
+ * The actual application
+ */
+const version = require('../package.json').version;
+
+program
+ .version(version)
+ .usage('[options] (--listen <port> | --connect <url>)')
+ .option('-l, --listen <port>', 'listen on port')
+ .option('-c, --connect <url>', 'connect to a websocket server')
+ .option('-p, --protocol <version>', 'optional protocol version')
+ .option('-o, --origin <origin>', 'optional origin')
+ .option('-x, --execute <command>', 'execute command after connecting')
+ .option('-w, --wait <seconds>', 'wait given seconds after executing command')
+ .option('--host <host>', 'optional host')
+ .option('-s, --subprotocol <protocol>', 'optional subprotocol', collect, [])
+ .option('-n, --no-check', 'Do not check for unauthorized certificates')
+ .option(
+ '-H, --header <header:value>',
+ 'Set an HTTP header. Repeat to set multiple. (--connect only)',
+ collect,
+ []
+ )
+ .option(
+ '--auth <username:password>',
+ 'Add basic HTTP authentication header. (--connect only)'
+ )
+ .option('--ca <ca>', 'Specify a Certificate Authority (--connect only)')
+ .option('--cert <cert>', 'Specify a Client SSL Certificate (--connect only)')
+ .option(
+ '--key <key>',
+ "Specify a Client SSL Certificate's key (--connect only)"
+ )
+ .option(
+ '--passphrase [passphrase]',
+ "Specify a Client SSL Certificate Key's passphrase (--connect only). " +
+ "If you don't provide a value, it will be prompted for."
+ )
+ .option('--no-color', 'Run without color')
+ .option(
+ '--slash',
+ 'Enable slash commands for control frames ' +
+ '(/ping, /pong, /close [code [, reason]])'
+ )
+ .parse(process.argv);
+
+if (program.listen && program.connect) {
+ console.error('\u001b[33merror: use either --listen or --connect\u001b[39m');
+ process.exit(-1);
+}
+
+if (program.listen) {
+ const wsConsole = new Console();
+ wsConsole.pause();
+
+ let ws = null;
+ const wss = new WebSocket.Server({ port: program.listen }, () => {
+ wsConsole.print(
+ Console.Types.Control,
+ `listening on port ${program.listen} (press CTRL+C to quit)`,
+ Console.Colors.Green
+ );
+ wsConsole.clear();
+ });
+
+ wsConsole.on('close', () => {
+ if (ws) ws.close();
+ process.exit(0);
+ });
+
+ wsConsole.on('line', data => {
+ if (ws) {
+ ws.send(data);
+ wsConsole.prompt();
+ }
+ });
+
+ wss.on('connection', newClient => {
+ if (ws) return newClient.terminate();
+
+ ws = newClient;
+ wsConsole.resume();
+ wsConsole.prompt();
+ wsConsole.print(
+ Console.Types.Control,
+ 'client connected',
+ Console.Colors.Green
+ );
+
+ ws.on('close', code => {
+ wsConsole.print(
+ Console.Types.Control,
+ `disconnected (code: ${code})`,
+ Console.Colors.Green
+ );
+ wsConsole.clear();
+ wsConsole.pause();
+ ws = null;
+ });
+
+ ws.on('error', err => {
+ wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
+ });
+
+ ws.on('message', data => {
+ wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
+ });
+ });
+
+ wss.on('error', err => {
+ wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
+ process.exit(-1);
+ });
+} else if (program.connect) {
+ const options = {};
+ const cont = () => {
+ const wsConsole = new Console();
+
+ const headers = program.header.reduce((acc, cur) => {
+ const i = cur.indexOf(':');
+ const key = cur.slice(0, i);
+ const value = cur.slice(i + 1);
+ acc[key] = value;
+ return acc;
+ }, {});
+
+ if (program.auth) {
+ headers.Authorization =
+ 'Basic ' + Buffer.from(program.auth).toString('base64');
+ }
+ if (program.host) headers.Host = program.host;
+ if (program.protocol) options.protocolVersion = +program.protocol;
+ if (program.origin) options.origin = program.origin;
+ if (!program.check) options.rejectUnauthorized = program.check;
+ if (program.ca) options.ca = fs.readFileSync(program.ca);
+ if (program.cert) options.cert = fs.readFileSync(program.cert);
+ if (program.key) options.key = fs.readFileSync(program.key);
+
+ let connectUrl = program.connect;
+ if (!connectUrl.match(/\w+:\/\/.*$/i)) {
+ connectUrl = `ws://${connectUrl}`;
+ }
+
+ options.headers = headers;
+ const ws = new WebSocket(connectUrl, program.subprotocol, options);
+
+ ws.on('open', () => {
+ if (program.execute) {
+ ws.send(program.execute);
+ setTimeout(() => {
+ ws.close();
+ }, program.wait ? program.wait * 1000 : 2000);
+ } else {
+ wsConsole.print(
+ Console.Types.Control,
+ 'connected (press CTRL+C to quit)',
+ Console.Colors.Green
+ );
+
+ wsConsole.on('line', data => {
+ if (program.slash && data[0] === '/') {
+ const toks = data.split(/\s+/);
+ switch (toks[0].substr(1)) {
+ case 'ping':
+ ws.ping(noop);
+ break;
+ case 'pong':
+ ws.pong(noop);
+ break;
+ case 'close': {
+ let closeStatusCode = 1000;
+ let closeReason = '';
+ if (toks.length >= 2) {
+ closeStatusCode = parseInt(toks[1]);
+ }
+ if (toks.length >= 3) {
+ closeReason = toks.slice(2).join(' ');
+ }
+ if (closeReason.length > 0) {
+ ws.close(closeStatusCode, closeReason);
+ } else {
+ ws.close(closeStatusCode);
+ }
+ break;
+ }
+ default:
+ wsConsole.print(
+ Console.Types.Error,
+ 'Unrecognized slash command.',
+ Console.Colors.Yellow
+ );
+ }
+ } else {
+ ws.send(data);
+ }
+ wsConsole.prompt();
+ });
+ }
+ });
+
+ ws.on('close', code => {
+ if (!program.execute) {
+ wsConsole.print(
+ Console.Types.Control,
+ `disconnected (code: ${code})`,
+ Console.Colors.Green
+ );
+ }
+ wsConsole.clear();
+ process.exit();
+ });
+
+ ws.on('error', err => {
+ wsConsole.print(Console.Types.Error, err.message, Console.Colors.Yellow);
+ process.exit(-1);
+ });
+
+ ws.on('message', data => {
+ wsConsole.print(Console.Types.Incoming, data, Console.Colors.Blue);
+ });
+
+ ws.on('ping', () => {
+ wsConsole.print(
+ Console.Types.Incoming,
+ 'Received ping',
+ Console.Colors.Blue
+ );
+ });
+
+ ws.on('pong', () => {
+ wsConsole.print(
+ Console.Types.Incoming,
+ 'Received pong',
+ Console.Colors.Blue
+ );
+ });
+
+ wsConsole.on('close', () => {
+ ws.close();
+ process.exit();
+ });
+ };
+
+ if (program.passphrase === true) {
+ read(
+ {
+ prompt: 'Passphrase: ',
+ silent: true,
+ replace: '*'
+ },
+ (err, passphrase) => {
+ options.passphrase = passphrase;
+ cont();
+ }
+ );
+ } else if (typeof program.passphrase === 'string') {
+ options.passphrase = program.passphrase;
+ cont();
+ } else {
+ cont();
+ }
+} else {
+ program.help();
+}