#!/usr/bin/env node /* vim: set ft=javascript: */ /* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Copyright 2020 Joyent, Inc. */ /* * fastcall: command-line tool for making a node-fast RPC method call. */ var VError = require('verror'); var mod_assertplus = require('assert-plus'); var mod_bunyan = require('bunyan'); var mod_cmdutil = require('cmdutil'); var mod_fast = require('../lib/fast'); var mod_getopt = require('posix-getopt'); var mod_net = require('net'); var OPTS = { 'a': 'abandon the RPC request after issuing it ' + 'via FastClientRequest#abandon', 'c': 'do not close the socket used to talk ' + 'to the FastServer until SIGINT' }; function main() { var argv, host, port, rpcmethod, rpcargs; var doabandon = false; var leaveconnopen = false; var timeout = null; mod_cmdutil.configure({ 'synopses': [ '[OPTIONS] HOST PORT METHOD ARGS' ], 'usageMessage': [ ' OPTIONS', ' -a,--abandon-immediately ' + OPTS['a'], ' -c,--leave-conn-open ' + OPTS['c'], ' HOST DNS name or IP address for remote server', ' PORT TCP port for remote server', ' METHOD Name of remote RPC method call', ' ARGS JSON-encoded arguments for RPC method call' ].join('\n') }); mod_cmdutil.exitOnEpipe(); var option; var parser = new mod_getopt.BasicParser('a(abandon-immediately)' + 'c(leave-conn-open)', process.argv); while ((option = parser.getopt()) !== undefined) { switch (option.option) { case 'c': leaveconnopen = true; break; case 'a': doabandon = true; break; default: mod_assertplus.equal('?', option.option); mod_cmdutil.usage(); break; } } argv = process.argv.slice(parser.optind()); if (argv.length != 4) { mod_cmdutil.usage('expected four non-option arguments'); } host = argv[0]; port = parseInt(argv[1], 10); if (isNaN(port) || port < 1 || port > 65535) { mod_cmdutil.usage('invalid TCP port: %s\n', argv[1]); } rpcmethod = argv[2]; try { rpcargs = JSON.parse(argv[3]); } catch (ex) { mod_cmdutil.usage(new VError(ex, 'parsing RPC arguments')); } if (!Array.isArray(rpcargs)) { mod_cmdutil.usage(new Error('RPC arguments: expected array')); } fastcall({ 'host': host, 'port': port, 'rpcmethod': rpcmethod, 'rpcargs': rpcargs, 'timeout': timeout, 'abandonImmediately': doabandon, 'leaveConnOpen': leaveconnopen }, function (err, result) { if (err) { mod_cmdutil.warn(err); } }); } function fastcall(args, callback) { var log, conn; var rpcmethod, rpcargs, timeout, doabandon, leaveconnopen; mod_assertplus.object(args, 'args'); mod_assertplus.string(args.host, 'args.host'); mod_assertplus.number(args.port, 'args.port'); mod_assertplus.optionalNumber(args.timeout, 'args.timeout'); mod_assertplus.string(args.rpcmethod, 'args.rpcmethod'); mod_assertplus.array(args.rpcargs, 'args.rpcargs'); mod_assertplus.bool(args.abandonImmediately, 'args.abandonImmediately'); mod_assertplus.bool(args.leaveConnOpen, 'args.leaveConnOpen'); rpcmethod = args.rpcmethod; rpcargs = args.rpcargs; timeout = args.timeout; doabandon = args.abandonImmediately; leaveconnopen = args.leaveConnOpen; log = new mod_bunyan({ 'name': 'fastcall', 'level': process.env['LOG_LEVEL'] || 'fatal' }); log.info(args, 'fastcall start'); conn = mod_net.createConnection(args.port, args.host); conn.on('connect', function onConnect() { /* * If the '--leave-conn-open' option was specified, then we * leave the connection open until the user sends a SIGINT to * the process. The purpose of this option is to demonstrate the * function of FastServer#onConnsDestroyed. See documentation * on the '--quiesce' option to fastserve for more information. */ if (leaveconnopen) { process.on('SIGINT', function () { conn.destroy(); }); } var fastconn, req; fastconn = new mod_fast.FastClient({ 'log': log, 'transport': conn, 'nRecentRequests': 10 }); fastconn.on('error', function (err) { if (!leaveconnopen) { conn.destroy(); } callback(new VError(err, 'fast connection')); }); req = fastconn.rpc({ 'rpcmethod': rpcmethod, 'rpcargs': rpcargs, 'timeout': timeout }); if (doabandon) { req.abandon(); } req.on('error', function (err) { if (!leaveconnopen) { conn.destroy(); } callback(new VError(err, 'fast request')); }); req.on('data', function (message) { console.log(JSON.stringify(message)); }); req.on('end', function () { if (!leaveconnopen) { conn.destroy(); } callback(); }); }); } main();