Skip to content
This repository has been archived by the owner on Sep 25, 2020. It is now read-only.

Get Thrift IDL from Meta::thriftIDL #61

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 115 additions & 60 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ var TChannel = require('tchannel');
var TChannelAsThrift = require('tchannel/as/thrift');
var TChannelAsJSON = require('tchannel/as/json');
var minimist = require('minimist');
var myLocalIp = require('my-local-ip');
var DebugLogtron = require('debug-logtron');

var fmt = require('util').format;
var console = require('console');
var process = require('process');
var fs = require('fs');
var path = require('path');
var url = require('url');
var assert = require('assert');

var safeJsonParse = require('safe-json-parse/tuple');
Expand All @@ -51,12 +49,13 @@ var packageJson = require('./package.json');
module.exports = main;

var minimistArgs = {
boolean: ['raw', 'strict'],
boolean: ['raw', 'json', 'strict'],
alias: {
h: 'help',
p: 'peer',
H: 'hostlist',
t: 'thrift',
j: 'json',
2: ['arg2', 'head'],
3: ['arg3', 'body']
},
Expand Down Expand Up @@ -94,16 +93,21 @@ main.exec = function execMain(str, delegate) {
function help() {
var helpMessage = [
'tcurl [-H <hostlist> | -p host:port] <service> <endpoint> [options]',
' ',
'',
' Version: ' + packageJson.version,
'',
' Options: ',
// TODO @file; @- stdin.
' -2 [data] send an arg2 blob',
' -3 [data] send an arg3 blob',
' --head (-2) [data] JSON or raw',
' --body (-3) [data] JSON or raw',
' (JSON promoted to Thrift via IDL when applicable)',
' --shardKey send ringpop shardKey transport header',
' --depth=n configure inspect printing depth',
' -t [dir] directory containing Thrift files',
' --thrift (-t) [dir] directory containing Thrift files',
' (obtained from Meta::thriftIDL endpoint if omitted)',
' --no-strict parse Thrift loosely',
' --json (-j) Use JSON argument scheme',
' (default unless endpoint has ::)',
' --http method',
' --raw encode arg2 & arg3 raw',
' --health',
Expand All @@ -118,32 +122,36 @@ function parseArgs(argv) {
var endpoint = argv._[1];
var health = argv.health;

var uri = argv.hostlist ?
JSON.parse(fs.readFileSync(argv.hostlist))[0] : argv.peer;
var peers = argv.hostlist ?
JSON.parse(fs.readFileSync(argv.hostlist)) : [argv.peer];

var parsedUri = url.parse('tchannel://' + uri);

if (parsedUri.hostname === 'localhost') {
parsedUri.hostname = myLocalIp();
}

assert(parsedUri.hostname, 'host required');
assert(parsedUri.port, 'port required');
assert(health || endpoint, 'endpoint required');
assert(service, 'service required');

var argScheme;
if (argv.raw) {
argScheme = 'raw';
} else if (argv.http) {
argScheme = 'http';
} else if (argv.json) {
argScheme = 'json';
} else if (argv.thrift || health || endpoint.indexOf('::') >= 0) {
argScheme = 'thrift';
} else {
argScheme = 'json';
}

return {
head: argv.head,
body: argv.body,
shardKey: argv.shardKey,
service: service,
endpoint: endpoint,
hostname: parsedUri.hostname,
port: parsedUri.port,
peers: peers,
argScheme: argScheme,
thrift: argv.thrift,
strict: argv.strict,
http: argv.http,
json: argv.json,
raw: argv.raw,
timeout: argv.timeout,
depth: argv.depth,
Expand Down Expand Up @@ -182,6 +190,50 @@ TCurl.prototype.parseJsonArgs = function parseJsonArgs(opts, delegate) {
return null;
};

TCurl.prototype.getThriftSource = function getThriftSource(opts, channel, delegate, callback) {
var self = this;
if (opts.thrift) {
var source = self.readThrift(opts, delegate);
if (source === null) {
return callback(new Error('Unabled to find thrift source'));
} else {
return callback(null, source);
}
} else {
return self.requestThriftSource(opts, channel, delegate, callback);
}
};

TCurl.prototype.requestThriftSource = function requestThriftSource(opts, channel, delegate, callback) {
var source = fs.readFileSync(path.join(__dirname, 'meta.thrift'), 'ascii');
var sender = new TChannelAsThrift({source: source});

var request = channel.request({
timeout: opts.timeout || 100,
hasNoParent: true,
serviceName: opts.service,
headers: {}
});

sender.send(request, 'Meta::thriftIDL', null, null, onResponse);

function onResponse(err, res) {
if (err) {
delegate.error('Can\'t infer Thrift IDL from Meta::thriftIDL endpoint');
delegate.error(err);
delegate.error('Consider passing --thrift [dir/file] or --json');
return callback(err);
} else if (!res.ok) {
delegate.error('Can\'t infer Thrift IDL from Meta::thriftIDL endpoint');
delegate.error('Service returned unexpected Thrift exception');
delegate.error(res.body);
delegate.error('Consider passing --thrift [dir/file] or --json');
return callback(new Error('Unexpected Thrift exception'));
}
return callback(null, res.body);
}
};

TCurl.prototype.readThrift = function readThrift(opts, delegate) {
var self = this;
try {
Expand Down Expand Up @@ -233,7 +285,7 @@ TCurl.prototype.request = function tcurlRequest(opts, delegate) {

var subChan = client.makeSubChannel({
serviceName: opts.service,
peers: [opts.hostname + ':' + opts.port],
peers: opts.peers,
requestDefaults: {
serviceName: opts.service,
headers: {
Expand All @@ -242,9 +294,10 @@ TCurl.prototype.request = function tcurlRequest(opts, delegate) {
}
});

client.waitForIdentified({
host: opts.hostname + ':' + opts.port
}, onIdentified);
var peer = subChan.peers.choosePeer();
assert(peer, 'peer required');
// TODO: the host option should be called peer, hostPort, or address
client.waitForIdentified({host: peer.hostPort}, onIdentified);

function onIdentified(err) {
if (err) {
Expand All @@ -261,15 +314,15 @@ TCurl.prototype.request = function tcurlRequest(opts, delegate) {
headers: headers
});

if (opts.thrift) {
if (opts.argScheme === 'thrift') {
self.asThrift(opts, request, delegate, done);
} else if (opts.http) {
} else if (opts.argScheme === 'http') {
self.asHTTP(opts, client, subChan, delegate, done);
} else if (opts.raw) {
self.asRaw(opts, request, delegate, done);
} else {
} else if (opts.argScheme === 'json') {
self.asJSON(opts, request, delegate, done);
// TODO fix argument order for each of these
} else {
self.asRaw(opts, request, delegate, done);
}
}

Expand All @@ -281,44 +334,46 @@ TCurl.prototype.request = function tcurlRequest(opts, delegate) {
TCurl.prototype.asThrift = function asThrift(opts, request, delegate, done) {
var self = this;

var source = self.readThrift(opts, delegate);

if (source === null) {
done();
return delegate.exit();
}

var sender;
try {
sender = new TChannelAsThrift({source: source, strict: opts.strict});
} catch (err) {
delegate.error('Error parsing Thrift IDL');
delegate.error(err);
delegate.error('Consider using --no-strict to bypass mandatory optional/required fields');
done();
return delegate.exit();
}
self.getThriftSource(opts, request.channel, delegate, onThriftSource);

// The following is a hack to produce a nice error message when
// the endpoint does not exist. It is a temporary solution based
// on the thriftify interface. How the existence of this endpoint
// is checked and this error thrown will change when we move to
// the thriftrw rewrite.
try {
sender.send(request, opts.endpoint, opts.head,
opts.body, onResponse);
} catch (err) {
// TODO untangle this mess
if (err.message === fmt('type %s_args not found', opts.endpoint)) {
delegate.error(fmt('%s endpoint does not exist', opts.endpoint));
function onThriftSource(err, source) {
if (err) {
done();
return delegate.exit();
} else {
delegate.error('Error response received for the as-thrift request.');
}

var sender;
try {
sender = new TChannelAsThrift({source: source, strict: opts.strict});
} catch (err) {
delegate.error('Error parsing Thrift IDL');
delegate.error(err);
delegate.error('Consider using --no-strict to bypass mandatory optional/required fields');
done();
return delegate.exit();
}

// The following is a hack to produce a nice error message when
// the endpoint does not exist. It is a temporary solution based
// on the thriftify interface. How the existence of this endpoint
// is checked and this error thrown will change when we move to
// the thriftrw rewrite.
try {
sender.send(request, opts.endpoint, opts.head,
opts.body, onResponse);
} catch (err) {
// TODO untangle this mess
if (err.message === fmt('type %s_args not found', opts.endpoint)) {
delegate.error(fmt('%s endpoint does not exist', opts.endpoint));
done();
return delegate.exit();
} else {
delegate.error('Error response received for the as-thrift request.');
delegate.error(err);
done();
return delegate.exit();
}
}
}

function onResponse(err, res, arg2, arg3) {
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@
"dependencies": {
"debug-logtron": "^3.2.0",
"minimist": "^1.1.1",
"my-local-ip": "^1.0.0",
"process": "^0.11.0",
"readable-stream": "^1.0.33",
"safe-json-parse": "^4.0.0",
"tchannel": "2.10.1"
"tchannel": "3.3.0"
},
"devDependencies": {
"coveralls": "^2.10.0",
Expand Down
50 changes: 50 additions & 0 deletions test/as-thrift.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,56 @@ test('getting an ok response', function t(assert) {

});

test.only('getting an ok response using remote IDL', function t(assert) {
var serviceName = 'meta';
var server = new TChannel({
serviceName: serviceName
});

var hostname = '127.0.0.1';
var port = 4040;

// Register Meta::health and Meta::thriftIDL endpoints
/* eslint no-unused-vars: [0] */
var tchannelAsThrift = new TChannelAsThrift({
source: meta,
isHealthy: isHealthy,
channel: server
});

server.listen(port, hostname, onListening);
function onListening() {
var cmd = [
'-p', hostname + ':' + port,
serviceName,
'Meta::health',
'--body', JSON.stringify({})
];

tcurl.exec(cmd, {
error: function error(err) {
assert.ifError(err);
},
response: function response(res) {
assert.deepEqual(res.body, {
ok: true,
message: null
}, 'caller receives thrift body from handler');
},
exit: function exit() {
server.close();
assert.end();
}
});
}

function isHealthy() {
return {
ok: true
};
}
});

test('hitting non-existent endpoint', function t(assert) {

var serviceName = 'meta';
Expand Down