From 04501349b714a50ea0011aa76d5e2a46d1b22642 Mon Sep 17 00:00:00 2001 From: ben fleis Date: Tue, 11 Aug 2015 16:27:36 +0200 Subject: [PATCH 1/2] add 'tcat' utility (a la netcat) for quick cli tchannel listening. addresses https://github.com/uber/tcurl/issues/30 --- package.json | 3 ++- tcat.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100755 tcat.js diff --git a/package.json b/package.json index 0cfc6c9..84e0bfd 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "main": "index.js", "homepage": "https://github.com/uber/tcurl", "bin": { - "tcurl": "index.js" + "tcurl": "index.js", + "tcat": "tcat.js" }, "bugs": { "url": "https://github.com/uber/tcurl/issues", diff --git a/tcat.js b/tcat.js new file mode 100755 index 0000000..d6f53ef --- /dev/null +++ b/tcat.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node + +// Copyright (c) 2015 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +'use strict'; + +// trivial tchannel listener interface, a la netcat + +var console = require('console'); +var process = require('process'); + +var minimist = require('minimist'); +var TChannel = require('tchannel'); + +function main(args) { + /*eslint no-console: 0 */ + var opts = minimist(args, {alias: {peer: ['p'], help: ['h']}}); + if (opts.help || opts.h) { + displayHelp(0); + } + + args = opts._.slice(2); + if (args.length !== 2 || !opts.peer) { + console.error('Must specify full endpoint.\n'); + displayHelp(1); + } + + var host = opts.peer.split(':')[0]; + var port = opts.peer.split(':')[1]; + var service = args[0]; + var endpoint = args[1]; + + if (!host || !port) { + console.error('Invalid HostPort.\n'); + displayHelp(1); + } + + var server = new TChannel(); + server + .makeSubChannel({serviceName: service}) + .register(endpoint, onRequest); + server.listen(+port, host); + + function onRequest(req, res, arg2, arg3) { + res.headers.as = 'raw'; + console.log(arg2.toString()); + res.sendOk(); + } +} + +function displayHelp(exitValue) { + /*eslint no-process-exit: 0*/ + console.log('tcat -p '); + process.exit(exitValue); +} + +if (require.main === module) { + main(process.argv); +} From b68bc1875283bc18cd1ab0d49e2820a225c588ef Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Thu, 12 Nov 2015 11:28:39 -0800 Subject: [PATCH 2/2] Add support for tcat subcommands --- package.json | 3 +- tcat.js | 119 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 84e0bfd..6860d4d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "safe-json-parse": "^4.0.0", "tchannel": "^3.5.19", "traverse": "^0.6.6", - "xtend": "^4.0.0" + "xtend": "^4.0.0", + "shon": "^1.1.0" }, "devDependencies": { "coveralls": "^2.10.0", diff --git a/tcat.js b/tcat.js index d6f53ef..0eb7ffb 100755 --- a/tcat.js +++ b/tcat.js @@ -21,54 +21,113 @@ // THE SOFTWARE. 'use strict'; -// trivial tchannel listener interface, a la netcat +/*eslint no-console: 0 */ +// tchannel listener interface, a la netcat var console = require('console'); var process = require('process'); +var ChildProcess = require('child_process'); -var minimist = require('minimist'); var TChannel = require('tchannel'); -function main(args) { - /*eslint no-console: 0 */ - var opts = minimist(args, {alias: {peer: ['p'], help: ['h']}}); - if (opts.help || opts.h) { - displayHelp(0); - } +var Command = require('shon/command'); +var command = new Command('tcat', { + peer: '[-p|--peer] The host:port pair to listen on', + // hyperbahn: '[-H|--hyperbahn ] Hyperbahn host list to advertise', + // thrift: '[-t|--thrift] File or directory with Thrift IDL files', + // interactive: '[-i|--interactive] Use interactive stream mode', + service: '', + endpoint: '', + command: '[]', + args: '....', + help: '[-h|--help]*' +}); - args = opts._.slice(2); - if (args.length !== 2 || !opts.peer) { - console.error('Must specify full endpoint.\n'); - displayHelp(1); +command.peer.converter = function convertAddress(peer, logger) { + var index = peer.lastIndexOf(':'); + if (index < 0) { + logger.error('Expected colon in host:port pair for peer'); + return null; } + var host = peer.slice(0, index); + var port = +peer.slice(index + 1); + if (port !== port) { + logger.error('Expected a numeric port in host:port for peer'); + return null; + } + return {host: host, port: port}; +}; - var host = opts.peer.split(':')[0]; - var port = opts.peer.split(':')[1]; - var service = args[0]; - var endpoint = args[1]; - - if (!host || !port) { - console.error('Invalid HostPort.\n'); - displayHelp(1); +function main(args) { + var config = command.exec(); + if (config === 'help') { + command._logUsage(); + return; } var server = new TChannel(); server - .makeSubChannel({serviceName: service}) - .register(endpoint, onRequest); - server.listen(+port, host); + .makeSubChannel({serviceName: config.service}) + .register(config.endpoint, onRequest); + server.listen(config.peer.port, config.peer.host, onListening); + + function onListening() { + console.log(JSON.stringify({message: 'listening', address: server.hostPort})); + } function onRequest(req, res, arg2, arg3) { - res.headers.as = 'raw'; - console.log(arg2.toString()); - res.sendOk(); + console.log(JSON.stringify(req.extendLogInfo({message: 'request'}))); + // TODO env inferred from arg2, argscheme dependent + var child = ChildProcess.spawn(config.command || 'cat', config.args, { + stdio: ['pipe', 'pipe', process.stderr] + }); + child.stdio[0].end(arg3); + readString(child.stdio[1], onOutput); + + function onOutput(err, arg3Res) { + if (err) { + res.sendError(err); + return; + } + console.log(JSON.stringify(req.extendLogInfo({message: 'response', arg2: arg2.toString('utf-8'), arg3: arg3Res}))); + res.headers.as = req.headers.as; + res.sendOk(arg2, arg3Res); + } } } -function displayHelp(exitValue) { - /*eslint no-process-exit: 0*/ - console.log('tcat -p '); - process.exit(exitValue); +function readString(stream, callback) { + stream.setEncoding('utf-8'); + stream.on('data', onData); + stream.on('end', onEnd); + stream.on('eerror', onError); + var done = false; + var all = ''; + function onData(data) { + all += data; + } + function onEnd() { + if (done) { + return; + } + done = true; + callback(null, all); + } + function onError(err) { + if (done) { + return; + } + done = true; + callback(err); + } + function cancel() { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onError); + done = true; + stream = null; + } + return {cancel: cancel}; } if (require.main === module) {