diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/lib/nodejs | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/thrift/lib/nodejs')
64 files changed, 9498 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/nodejs/Makefile.am b/src/jaegertracing/thrift/lib/nodejs/Makefile.am new file mode 100755 index 000000000..71068b58f --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/Makefile.am @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# We call npm twice to work around npm issues + +stubs: $(top_srcdir)/test/ThriftTest.thrift + $(THRIFT) --gen js:node -o test/ $(top_srcdir)/test/ThriftTest.thrift + +deps: $(top_srcdir)/package.json + $(NPM) install $(top_srcdir)/ || $(NPM) install $(top_srcdir)/ + +all-local: deps + +precross: deps stubs + +# TODO: Lint nodejs lib and gen-code as part of build +check: deps + cd $(top_srcdir) && $(NPM) test && $(NPM) run lint-tests && cd lib/nodejs + +clean-local: + $(RM) -r test/gen-* + $(RM) -r $(top_srcdir)/node_modules + $(RM) -r test/episodic-code-generation-test/gen* + $(RM) -r test/episodic-code-generation-test/node_modules + +EXTRA_DIST = \ + examples \ + lib \ + test \ + coding_standards.md \ + README.md diff --git a/src/jaegertracing/thrift/lib/nodejs/README.md b/src/jaegertracing/thrift/lib/nodejs/README.md new file mode 100644 index 000000000..ed306c15f --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/README.md @@ -0,0 +1,111 @@ +Thrift Node.js Library +========================= + +License +------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +## Compatibility + +node version 6 or later is required + +## Install + + npm install thrift + +## Thrift Compiler + +You can compile IDL sources for Node.js with the following command: + + thrift --gen js:node thrift_file + +## Cassandra Client Example: + +Here is a Cassandra example: + +```js +var thrift = require('thrift'), + Cassandra = require('./gen-nodejs/Cassandra') + ttypes = require('./gen-nodejs/cassandra_types'); + +var connection = thrift.createConnection("localhost", 9160), + client = thrift.createClient(Cassandra, connection); + +connection.on('error', function(err) { + console.error(err); +}); + +client.get_slice("Keyspace", "key", new ttypes.ColumnParent({column_family: "ExampleCF"}), new ttypes.SlicePredicate({slice_range: new ttypes.SliceRange({start: '', finish: ''})}), ttypes.ConsistencyLevel.ONE, function(err, data) { + if (err) { + // handle err + } else { + // data == [ttypes.ColumnOrSuperColumn, ...] + } + connection.end(); +}); +``` + +<a name="int64"></a> +## Int64 + +Since JavaScript represents all numbers as doubles, int64 values cannot be accurately represented naturally. To solve this, int64 values in responses will be wrapped with Thrift.Int64 objects. The Int64 implementation used is [broofa/node-int64](https://github.com/broofa/node-int64). + +## Client and server examples + +Several example clients and servers are included in the thrift/lib/nodejs/examples folder and the cross language tutorial thrift/tutorial/nodejs folder. + +## Use on browsers + +You can use code generated with js:node on browsers with Webpack. Here is an example. + +thrift --gen js:node,ts,es6,with_ns + +``` +import * as thrift from 'thrift/browser'; +import { MyServiceClient } from '../gen-nodejs/MyService'; + +let host = window.location.hostname; +let port = 443; +let opts = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol, + headers: { + 'Content-Type': 'application/vnd.apache.thrift.json', + }, + https: true, + path: '/url/path', + useCORS: true, +}; + +let connection = thrift.createXHRConnection(host, port, opts); +let thriftClient = thrift.createXHRClient(MyServiceClient, connection); + +connection.on('error', (err) => { + console.error(err); +}); + +thriftClient.myService(param) + .then((result) => { + console.log(result); + }) + .catch((err) => { + .... + }); +``` + +Note that thrift/index.js must be renamed or skipped for browsers. diff --git a/src/jaegertracing/thrift/lib/nodejs/coding_standards.md b/src/jaegertracing/thrift/lib/nodejs/coding_standards.md new file mode 100644 index 000000000..fa0390bb5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/coding_standards.md @@ -0,0 +1 @@ +Please follow [General Coding Standards](/doc/coding_standards.md) diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/Makefile b/src/jaegertracing/thrift/lib/nodejs/examples/Makefile new file mode 100644 index 000000000..87157db63 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/Makefile @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +all: + ../../../compiler/cpp/thrift --gen js:node user.thrift + +server: all + NODE_PATH=../lib:../lib/thrift:$(NODE_PATH) node server.js + +client: all + NODE_PATH=../lib:../lib/thrift:$(NODE_PATH) node client.js diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/README.md b/src/jaegertracing/thrift/lib/nodejs/examples/README.md new file mode 100644 index 000000000..7350c10c9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/README.md @@ -0,0 +1,40 @@ +# Thrift Node.js Examples + +## License +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +## Running the user example + +Generate the bindings: + + ../../../compiler/cpp/thrift --gen js:node user.thrift + ../../../compiler/cpp/thrift --gen js:node --gen py hello.thrift + +To run the user example, first start up the server in one terminal: + + NODE_PATH=../lib:../lib/thrift node server.js + +Now run the client: + + NODE_PATH=../lib:../lib/thrift node client.js + +For an example using JavaScript in the browser to connect to +a node.js server look at hello.html, hello.js and hello.thrift + +HTTP examples are provided also: httpClient.js and httpServer.js +You can test HTTP cross platform with the httpServer.py Python server diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/client.js b/src/jaegertracing/thrift/lib/nodejs/examples/client.js new file mode 100644 index 000000000..c83b34234 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/client.js @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var thrift = require('thrift'); + +var UserStorage = require('./gen-nodejs/UserStorage.js'), + ttypes = require('./gen-nodejs/user_types'); + +var connection = thrift.createConnection('localhost', 9090), + client = thrift.createClient(UserStorage, connection); + +var user = new ttypes.UserProfile({uid: 1, + name: "Mark Slee", + blurb: "I'll find something to put here."}); + +connection.on('error', function(err) { + console.error(err); +}); + +client.store(user, function(err, response) { + if (err) { + console.error(err); + } else { + console.log("client stored:", user.uid); + client.retrieve(user.uid, function(err, responseUser) { + if (err) { + console.error(err); + } else { + console.log("client retrieved:", responseUser.uid); + connection.end(); + } + }); + } +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/client_multitransport.js b/src/jaegertracing/thrift/lib/nodejs/examples/client_multitransport.js new file mode 100644 index 000000000..1e37de32f --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/client_multitransport.js @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var thrift = require('thrift'), + ttransport = require('thrift/transport'); + +var UserStorage = require('./gen-nodejs/UserStorage'), + ttypes = require('./gen-nodejs/user_types'); + +var f_conn = thrift.createConnection('localhost', 9090), // default: framed + f_client = thrift.createClient(UserStorage, f_conn); +var b_conn = thrift.createConnection('localhost', 9091, {transport: ttransport.TBufferedTransport}), + b_client = thrift.createClient(UserStorage, b_conn); +var user1 = new ttypes.UserProfile({uid: 1, + name: "Mark Slee", + blurb: "I'll find something to put here."}); +var user2 = new ttypes.UserProfile({uid: 2, + name: "Satoshi Tagomori", + blurb: "ok, let's test with buffered transport."}); + +f_conn.on('error', function(err) { + console.error("framed:", err); +}); + +f_client.store(user1, function(err, response) { + if (err) { console.error(err); return; } + + console.log("stored:", user1.uid, " as ", user1.name); + b_client.retrieve(user1.uid, function(err, responseUser) { + if (err) { console.error(err); return; } + console.log("retrieved:", responseUser.uid, " as ", responseUser.name); + }); +}); + +b_client.store(user2, function(err, response) { + if (err) { console.error(err); return; } + + console.log("stored:", user2.uid, " as ", user2.name); + f_client.retrieve(user2.uid, function(err, responseUser) { + if (err) { console.error(err); return; } + console.log("retrieved:", responseUser.uid, " as ", responseUser.name); + }); +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/hello.html b/src/jaegertracing/thrift/lib/nodejs/examples/hello.html new file mode 100644 index 000000000..fe85a7e9d --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/hello.html @@ -0,0 +1,65 @@ +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. +--> +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Apache Thrift JavaScript Browser Client Demo</title> + <script src="thrift.js" type="text/javascript"></script> + <script src="gen-js/HelloSvc.js" type="text/javascript"></script> + <script src="gen-js/TimesTwo.js" type="text/javascript"></script> +</head> +<body> + <h1>Apache Thrift JavaScript Browser Client Demo</h1> + <p>This html file demonstrates Apache Thrift JavaScrpt RPC between a browser client to a node.js server. Clicking the buttons below will call the RPC functions hosted by the Apache Thrift server at localhost:8585. The file hello.js contains the JavaScript node.js server required. Here are the steps to get the example running:</p> + <ol> + <li>Install Node.js <pre><a href="http://nodejs.org">nodejs.org</a></pre></li> + <li>Install Apache Thrift for node (note that the node package manager will create the node_modules folder in the current directory so make sure to run npm from the same directory as hello.js so that the server can find the Thrift libraries. This example requires Apache Thrift 0.9.2+) <pre>$ npm install thrift</pre></li> + <li>Compile the hello.idl for JavaScript and Node.js (you'll need to have the Apache Thrift compiler installed for this step. This also needs to be executed in the same directory as hello.js because hello.js and hello.html look for the gen-nodejs and gen-js directories here.)<pre>$ thrift -gen js -gen js:node hello.thrift</pre></li> + <li>Run the node server in the directory with the hello.html file<pre>$ node hello.js</pre></li> + <li>Copy the Apache Thrift JavaScript library, thrift.js, into the directory with this html file.<pre>$ cp ...../thrift.js . (you should be able to use Bower to install the browser based Apache Thrift library in the near future.)</pre> + <li>Reload this page in a browser through the node server using using the URL: <pre>http://localhost:8585/hello.html</pre>then click a button below to make an RPC call</li> + </ol> + <button id="btn">Get Message from Node Server</button> + <button id="btnDbl">Double 25</button> + <script type="text/javascript"> + document.getElementById("btn").addEventListener("click", getMessage, false); + + function getMessage() { + var transport = new Thrift.TXHRTransport("http://localhost:8585/hello"); + var protocol = new Thrift.TJSONProtocol(transport); + var client = new HelloSvcClient(protocol); + var msg = client.hello_func(); + document.getElementById("output").innerHTML = msg; + } + + document.getElementById("btnDbl").addEventListener("click", dblMessage, false); + + function dblMessage() { + var transport = new Thrift.TXHRTransport("http://localhost:8585/dbl"); + var protocol = new Thrift.TJSONProtocol(transport); + var client = new TimesTwoClient(protocol); + var val = client.dbl(25); + document.getElementById("output2").innerHTML = val; + } + </script> + <h2>Server Response: <div id="output"></div></h2> + <h2>Server Dbl: <div id="output2"></div></h2> +</body> +</html> + diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/hello.js b/src/jaegertracing/thrift/lib/nodejs/examples/hello.js new file mode 100644 index 000000000..8b7c4e4ea --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/hello.js @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var thrift = require('thrift'); +var HelloSvc = require('./gen-nodejs/HelloSvc.js'); +var TimesTwoSvc = require('./gen-nodejs/TimesTwo.js'); + +var helloHandler = { + hello_func: function(result) { + this.call_counter = this.call_counter || 0; + console.log("Client call: " + (++this.call_counter)); + result(null, "Hello Apache Thrift for JavaScript " + this.call_counter); + } +} + +var timesTwoHandler = { + dbl: function(val, result) { + console.log("Client call: " + val); + result(null, val * 2); + } +} + +var helloService = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol, + processor: HelloSvc, + handler: helloHandler +}; + +var dblService = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol, + processor: TimesTwoSvc, + handler: timesTwoHandler +}; + +var ServerOptions = { + files: ".", + services: { + "/hello": helloService, + "/dbl": dblService, + } +} + +var server = thrift.createWebServer(ServerOptions); +var port = 8585; +server.listen(port); +console.log("Http/Thrift Server running on port: " + port); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/hello.thrift b/src/jaegertracing/thrift/lib/nodejs/examples/hello.thrift new file mode 100644 index 000000000..deaf5a5f9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/hello.thrift @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +service HelloSvc { + string hello_func(), +} + +service TimesTwo { + i64 dbl(1: i64 val), +} + + diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/httpClient.js b/src/jaegertracing/thrift/lib/nodejs/examples/httpClient.js new file mode 100644 index 000000000..19cc0c362 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/httpClient.js @@ -0,0 +1,23 @@ +var thrift = require('thrift'); +var helloSvc = require('./gen-nodejs/HelloSvc.js'); + +var options = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol, + path: "/hello", + headers: {"Connection": "close"}, + https: false +}; + +var connection = thrift.createHttpConnection("localhost", 9090, options); +var client = thrift.createHttpClient(helloSvc, connection); + +connection.on("error", function(err) { + console.log("Error: " + err); +}); + +client.hello_func(function(error, result) { + console.log("Msg from server: " + result); +}); + + diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.js b/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.js new file mode 100644 index 000000000..acae1369a --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.js @@ -0,0 +1,31 @@ +var thrift = require('thrift'); +var helloSvc = require('./gen-nodejs/HelloSvc'); + +//ServiceHandler: Implement the hello service +var helloHandler = { + hello_func: function (result) { + console.log("Received Hello call"); + result(null, "Hello from Node.js"); + } +}; + +//ServiceOptions: The I/O stack for the service +var helloSvcOpt = { + handler: helloHandler, + processor: helloSvc, + protocol: thrift.TJSONProtocol, + transport: thrift.TBufferedTransport +}; + +//ServerOptions: Define server features +var serverOpt = { + services: { + "/hello": helloSvcOpt + } +} + +//Create and start the web server +var port = 9090; +thrift.createWebServer(serverOpt).listen(port); +console.log("Http/Thrift Server running on port: " + port); + diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.py b/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.py new file mode 100644 index 000000000..76e9f4aa3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/httpServer.py @@ -0,0 +1,20 @@ +import sys +sys.path.append('gen-py') + +from hello import HelloSvc +from thrift.protocol import TJSONProtocol +from thrift.server import THttpServer + + +class HelloSvcHandler: + def hello_func(self): + print("Hello Called") + return "hello from Python" + + +processor = HelloSvc.Processor(HelloSvcHandler()) +protoFactory = TJSONProtocol.TJSONProtocolFactory() +port = 9090 +server = THttpServer.THttpServer(processor, ("localhost", port), protoFactory) +print "Python server running on port " + str(port) +server.serve() diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/parse.js b/src/jaegertracing/thrift/lib/nodejs/examples/parse.js new file mode 100644 index 000000000..168a1aeea --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/parse.js @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + + This is a standalone deserialize/parse example if you just want to deserialize + thrift decoupled from cassandra server + + 1. acquire thrift template specification files from who ever built it (eg: EXAMPLE.thrift) + + 2. Install thrift on local machine + + 3. generate thrift clients for nodejs using template specification files (#1) + thrift --gen js:node schema/EXAMPLE.thrift + + This creates creates gen-node.js directory containing a new file, GENERATED.js + + 4. Inside GENERATED.js is a class you will want to instanciate. Find this class name and plug + it into the example code below (ie, "YOUR_CLASS_NAME") + */ + +function parseThrift(thriftEncodedData, callback) { + var thrift = require('thrift'); + var transport = new thrift.TFramedTransport(thriftEncodedData); + var protocol = new thrift.TBinaryProtocol(transport); + + var clientClass = require('../gen-nodejs/GENERATED').YOUR_CLASS_NAME; + var client = new clientClass(); + client.read(protocol); + callback(null, client); +} diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/server.js b/src/jaegertracing/thrift/lib/nodejs/examples/server.js new file mode 100644 index 000000000..1c482fe71 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/server.js @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var thrift = require('thrift'); + +var UserStorage = require('./gen-nodejs/UserStorage.js'), + ttypes = require('./gen-nodejs/user_types'); + +var users = {}; + +var server = thrift.createServer(UserStorage, { + store: function(user, result) { + console.log("server stored:", user.uid); + users[user.uid] = user; + result(null); + }, + + retrieve: function(uid, result) { + console.log("server retrieved:", uid); + result(null, users[uid]); + }, +}); + +server.listen(9090); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/server_http.js b/src/jaegertracing/thrift/lib/nodejs/examples/server_http.js new file mode 100644 index 000000000..ef2dc83a2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/server_http.js @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var connect = require('connect'); +var thrift = require('thrift'); + +var UserStorage = require('./gen-nodejs/UserStorage'), + ttypes = require('./gen-nodejs/user_types'); + +var users = {}; + +var store = function(user, result) { + console.log("stored:", user.uid); + users[user.uid] = user; + result(null); +}; +var retrieve = function(uid, result) { + console.log("retrieved:", uid); + result(null, users[uid]); +}; + +var server_http = thrift.createHttpServer(UserStorage, { + store: store, + retrieve: retrieve +}); +server_http.listen(9090); + +var server_connect = connect(thrift.httpMiddleware(UserStorage, { + store: store, + retrieve: retrieve +})); +server_http.listen(9091); + +var server_connect_json = connect(thrift.httpMiddleware(UserStorage, { + store: store, + retrieve: retrieve +}, {protocol: thrift.TJSONProtocol})); +server_connect_json.listen(9092); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/server_multitransport.js b/src/jaegertracing/thrift/lib/nodejs/examples/server_multitransport.js new file mode 100644 index 000000000..a348e6847 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/server_multitransport.js @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var thrift = require('thrift'), + ttransport = require('thrift/transport'); + +var UserStorage = require('./gen-nodejs/UserStorage'), + ttypes = require('./gen-nodejs/user_types'); + +var users = {}; + +var store = function(user, result) { + console.log("stored:", user.uid); + users[user.uid] = user; + result(null); +}; +var retrieve = function(uid, result) { + console.log("retrieved:", uid); + result(null, users[uid]); +}; + +var server_framed = thrift.createServer(UserStorage, { + store: store, + retrieve: retrieve +}); +server_framed.listen(9090); +var server_buffered = thrift.createServer(UserStorage, { + store: store, + retrieve: retrieve +}, {transport: ttransport.TBufferedTransport}); +server_buffered.listen(9091); diff --git a/src/jaegertracing/thrift/lib/nodejs/examples/user.thrift b/src/jaegertracing/thrift/lib/nodejs/examples/user.thrift new file mode 100644 index 000000000..d087fd442 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/examples/user.thrift @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +struct UserProfile { + 1: i32 uid, + 2: string name, + 3: string blurb +} + +service UserStorage { + void store(1: UserProfile user), + UserProfile retrieve(1: i32 uid) +} diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary.js new file mode 100644 index 000000000..9813ffdb1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary.js @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var POW_8 = Math.pow(2, 8); +var POW_16 = Math.pow(2, 16); +var POW_24 = Math.pow(2, 24); +var POW_32 = Math.pow(2, 32); +var POW_40 = Math.pow(2, 40); +var POW_48 = Math.pow(2, 48); +var POW_52 = Math.pow(2, 52); +var POW_1022 = Math.pow(2, 1022); + +exports.readByte = function(b){ + return b > 127 ? b-256 : b; +}; + +exports.readI16 = function(buff, off) { + off = off || 0; + var v = buff[off + 1]; + v += buff[off] << 8; + if (buff[off] & 128) { + v -= POW_16; + } + return v; +}; + +exports.readI32 = function(buff, off) { + off = off || 0; + var v = buff[off + 3]; + v += buff[off + 2] << 8; + v += buff[off + 1] << 16; + v += buff[off] * POW_24; + if (buff[off] & 0x80) { + v -= POW_32; + } + return v; +}; + +exports.writeI16 = function(buff, v) { + buff[1] = v & 0xff; + v >>= 8; + buff[0] = v & 0xff; + return buff; +}; + +exports.writeI32 = function(buff, v) { + buff[3] = v & 0xff; + v >>= 8; + buff[2] = v & 0xff; + v >>= 8; + buff[1] = v & 0xff; + v >>= 8; + buff[0] = v & 0xff; + return buff; +}; + +exports.readDouble = function(buff, off) { + off = off || 0; + var signed = buff[off] & 0x80; + var e = (buff[off+1] & 0xF0) >> 4; + e += (buff[off] & 0x7F) << 4; + + var m = buff[off+7]; + m += buff[off+6] << 8; + m += buff[off+5] << 16; + m += buff[off+4] * POW_24; + m += buff[off+3] * POW_32; + m += buff[off+2] * POW_40; + m += (buff[off+1] & 0x0F) * POW_48; + + switch (e) { + case 0: + e = -1022; + break; + case 2047: + return m ? NaN : (signed ? -Infinity : Infinity); + default: + m += POW_52; + e -= 1023; + } + + if (signed) { + m *= -1; + } + + return m * Math.pow(2, e - 52); +}; + +/* + * Based on code from the jspack module: + * http://code.google.com/p/jspack/ + */ +exports.writeDouble = function(buff, v) { + var m, e, c; + + buff[0] = (v < 0 ? 0x80 : 0x00); + + v = Math.abs(v); + if (v !== v) { + // NaN, use QNaN IEEE format + m = 2251799813685248; + e = 2047; + } else if (v === Infinity) { + m = 0; + e = 2047; + } else { + e = Math.floor(Math.log(v) / Math.LN2); + c = Math.pow(2, -e); + if (v * c < 1) { + e--; + c *= 2; + } + + if (e + 1023 >= 2047) + { + // Overflow + m = 0; + e = 2047; + } + else if (e + 1023 >= 1) + { + // Normalized - term order matters, as Math.pow(2, 52-e) and v*Math.pow(2, 52) can overflow + m = (v*c-1) * POW_52; + e += 1023; + } + else + { + // Denormalized - also catches the '0' case, somewhat by chance + m = (v * POW_1022) * POW_52; + e = 0; + } + } + + buff[1] = (e << 4) & 0xf0; + buff[0] |= (e >> 4) & 0x7f; + + buff[7] = m & 0xff; + m = Math.floor(m / POW_8); + buff[6] = m & 0xff; + m = Math.floor(m / POW_8); + buff[5] = m & 0xff; + m = Math.floor(m / POW_8); + buff[4] = m & 0xff; + m >>= 8; + buff[3] = m & 0xff; + m >>= 8; + buff[2] = m & 0xff; + m >>= 8; + buff[1] |= m & 0x0f; + + return buff; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary_protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary_protocol.js new file mode 100644 index 000000000..af8836cf5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/binary_protocol.js @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var log = require('./log'); +var binary = require('./binary'); +var Int64 = require('node-int64'); +var Thrift = require('./thrift'); +var Type = Thrift.Type; + +module.exports = TBinaryProtocol; + +// JavaScript supports only numeric doubles, therefore even hex values are always signed. +// The largest integer value which can be represented in JavaScript is +/-2^53. +// Bitwise operations convert numbers to 32 bit integers but perform sign extension +// upon assigning values back to variables. +var VERSION_MASK = -65536, // 0xffff0000 + VERSION_1 = -2147418112, // 0x80010000 + TYPE_MASK = 0x000000ff; + +TBinaryProtocol.VERSION_MASK = VERSION_MASK; +TBinaryProtocol.VERSION_1 = VERSION_1; +TBinaryProtocol.TYPE_MASK = TYPE_MASK + +function TBinaryProtocol(trans, strictRead, strictWrite) { + this.trans = trans; + this.strictRead = (strictRead !== undefined ? strictRead : false); + this.strictWrite = (strictWrite !== undefined ? strictWrite : true); + this._seqid = null; +}; + +TBinaryProtocol.prototype.flush = function() { + return this.trans.flush(); +}; + +TBinaryProtocol.prototype.writeMessageBegin = function(name, type, seqid) { + if (this.strictWrite) { + this.writeI32(VERSION_1 | type); + this.writeString(name); + this.writeI32(seqid); + } else { + this.writeString(name); + this.writeByte(type); + this.writeI32(seqid); + } + // Record client seqid to find callback again + if (this._seqid !== null) { + log.warning('SeqId already set', { 'name': name }); + } else { + this._seqid = seqid; + this.trans.setCurrSeqId(seqid); + } +}; + +TBinaryProtocol.prototype.writeMessageEnd = function() { + if (this._seqid !== null) { + this._seqid = null; + } else { + log.warning('No seqid to unset'); + } +}; + +TBinaryProtocol.prototype.writeStructBegin = function(name) { +}; + +TBinaryProtocol.prototype.writeStructEnd = function() { +}; + +TBinaryProtocol.prototype.writeFieldBegin = function(name, type, id) { + this.writeByte(type); + this.writeI16(id); +}; + +TBinaryProtocol.prototype.writeFieldEnd = function() { +}; + +TBinaryProtocol.prototype.writeFieldStop = function() { + this.writeByte(Type.STOP); +}; + +TBinaryProtocol.prototype.writeMapBegin = function(ktype, vtype, size) { + this.writeByte(ktype); + this.writeByte(vtype); + this.writeI32(size); +}; + +TBinaryProtocol.prototype.writeMapEnd = function() { +}; + +TBinaryProtocol.prototype.writeListBegin = function(etype, size) { + this.writeByte(etype); + this.writeI32(size); +}; + +TBinaryProtocol.prototype.writeListEnd = function() { +}; + +TBinaryProtocol.prototype.writeSetBegin = function(etype, size) { + this.writeByte(etype); + this.writeI32(size); +}; + +TBinaryProtocol.prototype.writeSetEnd = function() { +}; + +TBinaryProtocol.prototype.writeBool = function(bool) { + if (bool) { + this.writeByte(1); + } else { + this.writeByte(0); + } +}; + +TBinaryProtocol.prototype.writeByte = function(b) { + this.trans.write(new Buffer([b])); +}; + +TBinaryProtocol.prototype.writeI16 = function(i16) { + this.trans.write(binary.writeI16(new Buffer(2), i16)); +}; + +TBinaryProtocol.prototype.writeI32 = function(i32) { + this.trans.write(binary.writeI32(new Buffer(4), i32)); +}; + +TBinaryProtocol.prototype.writeI64 = function(i64) { + if (i64.buffer) { + this.trans.write(i64.buffer); + } else { + this.trans.write(new Int64(i64).buffer); + } +}; + +TBinaryProtocol.prototype.writeDouble = function(dub) { + this.trans.write(binary.writeDouble(new Buffer(8), dub)); +}; + +TBinaryProtocol.prototype.writeStringOrBinary = function(name, encoding, arg) { + if (typeof(arg) === 'string') { + this.writeI32(Buffer.byteLength(arg, encoding)); + this.trans.write(new Buffer(arg, encoding)); + } else if ((arg instanceof Buffer) || + (Object.prototype.toString.call(arg) == '[object Uint8Array]')) { + // Buffers in Node.js under Browserify may extend UInt8Array instead of + // defining a new object. We detect them here so we can write them + // correctly + this.writeI32(arg.length); + this.trans.write(arg); + } else { + throw new Error(name + ' called without a string/Buffer argument: ' + arg); + } +}; + +TBinaryProtocol.prototype.writeString = function(arg) { + this.writeStringOrBinary('writeString', 'utf8', arg); +}; + +TBinaryProtocol.prototype.writeBinary = function(arg) { + this.writeStringOrBinary('writeBinary', 'binary', arg); +}; + +TBinaryProtocol.prototype.readMessageBegin = function() { + var sz = this.readI32(); + var type, name, seqid; + + if (sz < 0) { + var version = sz & VERSION_MASK; + if (version != VERSION_1) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.BAD_VERSION, "Bad version in readMessageBegin: " + sz); + } + type = sz & TYPE_MASK; + name = this.readString(); + seqid = this.readI32(); + } else { + if (this.strictRead) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.BAD_VERSION, "No protocol version header"); + } + name = this.trans.read(sz); + type = this.readByte(); + seqid = this.readI32(); + } + return {fname: name, mtype: type, rseqid: seqid}; +}; + +TBinaryProtocol.prototype.readMessageEnd = function() { +}; + +TBinaryProtocol.prototype.readStructBegin = function() { + return {fname: ''}; +}; + +TBinaryProtocol.prototype.readStructEnd = function() { +}; + +TBinaryProtocol.prototype.readFieldBegin = function() { + var type = this.readByte(); + if (type == Type.STOP) { + return {fname: null, ftype: type, fid: 0}; + } + var id = this.readI16(); + return {fname: null, ftype: type, fid: id}; +}; + +TBinaryProtocol.prototype.readFieldEnd = function() { +}; + +TBinaryProtocol.prototype.readMapBegin = function() { + var ktype = this.readByte(); + var vtype = this.readByte(); + var size = this.readI32(); + return {ktype: ktype, vtype: vtype, size: size}; +}; + +TBinaryProtocol.prototype.readMapEnd = function() { +}; + +TBinaryProtocol.prototype.readListBegin = function() { + var etype = this.readByte(); + var size = this.readI32(); + return {etype: etype, size: size}; +}; + +TBinaryProtocol.prototype.readListEnd = function() { +}; + +TBinaryProtocol.prototype.readSetBegin = function() { + var etype = this.readByte(); + var size = this.readI32(); + return {etype: etype, size: size}; +}; + +TBinaryProtocol.prototype.readSetEnd = function() { +}; + +TBinaryProtocol.prototype.readBool = function() { + var b = this.readByte(); + if (b === 0) { + return false; + } + return true; +}; + +TBinaryProtocol.prototype.readByte = function() { + return this.trans.readByte(); +}; + +TBinaryProtocol.prototype.readI16 = function() { + return this.trans.readI16(); +}; + +TBinaryProtocol.prototype.readI32 = function() { + return this.trans.readI32(); +}; + +TBinaryProtocol.prototype.readI64 = function() { + var buff = this.trans.read(8); + return new Int64(buff); +}; + +TBinaryProtocol.prototype.readDouble = function() { + return this.trans.readDouble(); +}; + +TBinaryProtocol.prototype.readBinary = function() { + var len = this.readI32(); + if (len === 0) { + return new Buffer(0); + } + + if (len < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative binary size"); + } + return this.trans.read(len); +}; + +TBinaryProtocol.prototype.readString = function() { + var len = this.readI32(); + if (len === 0) { + return ""; + } + + if (len < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative string size"); + } + return this.trans.readString(len); +}; + +TBinaryProtocol.prototype.getTransport = function() { + return this.trans; +}; + +TBinaryProtocol.prototype.skip = function(type) { + switch (type) { + case Type.BOOL: + this.readBool(); + break; + case Type.BYTE: + this.readByte(); + break; + case Type.I16: + this.readI16(); + break; + case Type.I32: + this.readI32(); + break; + case Type.I64: + this.readI64(); + break; + case Type.DOUBLE: + this.readDouble(); + break; + case Type.STRING: + this.readString(); + break; + case Type.STRUCT: + this.readStructBegin(); + while (true) { + var r = this.readFieldBegin(); + if (r.ftype === Type.STOP) { + break; + } + this.skip(r.ftype); + this.readFieldEnd(); + } + this.readStructEnd(); + break; + case Type.MAP: + var mapBegin = this.readMapBegin(); + for (var i = 0; i < mapBegin.size; ++i) { + this.skip(mapBegin.ktype); + this.skip(mapBegin.vtype); + } + this.readMapEnd(); + break; + case Type.SET: + var setBegin = this.readSetBegin(); + for (var i2 = 0; i2 < setBegin.size; ++i2) { + this.skip(setBegin.etype); + } + this.readSetEnd(); + break; + case Type.LIST: + var listBegin = this.readListBegin(); + for (var i3 = 0; i3 < listBegin.size; ++i3) { + this.skip(listBegin.etype); + } + this.readListEnd(); + break; + default: + throw new Error("Invalid type: " + type); + } +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/browser.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/browser.js new file mode 100644 index 000000000..67ce8535b --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/browser.js @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +exports.Thrift = require('./thrift'); + +var xhrConnection = require('./xhr_connection'); +exports.XHRConnection = xhrConnection.XHRConnection; +exports.createXHRConnection = xhrConnection.createXHRConnection; +exports.createXHRClient = xhrConnection.createXHRClient; + +exports.Multiplexer = require('./multiplexed_protocol').Multiplexer; + +exports.TWebSocketTransport = require('./ws_transport'); +exports.TBufferedTransport = require('./buffered_transport'); +exports.TFramedTransport = require('./framed_transport'); + +exports.Protocol = exports.TJSONProtocol = require('./json_protocol'); +exports.TBinaryProtocol = require('./binary_protocol'); +exports.TCompactProtocol = require('./compact_protocol'); + +exports.Int64 = require('node-int64'); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/buffered_transport.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/buffered_transport.js new file mode 100644 index 000000000..113e21616 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/buffered_transport.js @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var binary = require('./binary'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); +var THeaderTransport = require('./header_transport'); + +module.exports = TBufferedTransport; + +function TBufferedTransport(buffer, callback) { + this.defaultReadBufferSize = 1024; + this.writeBufferSize = 512; // Soft Limit + this.inBuf = new Buffer(this.defaultReadBufferSize); + this.readCursor = 0; + this.writeCursor = 0; // for input buffer + this.outBuffers = []; + this.outCount = 0; + this.onFlush = callback; +}; + +TBufferedTransport.prototype = new THeaderTransport(); + +TBufferedTransport.prototype.reset = function() { + this.inBuf = new Buffer(this.defaultReadBufferSize); + this.readCursor = 0; + this.writeCursor = 0; + this.outBuffers = []; + this.outCount = 0; +} + +TBufferedTransport.receiver = function(callback, seqid) { + var reader = new TBufferedTransport(); + + return function(data) { + if (reader.writeCursor + data.length > reader.inBuf.length) { + var buf = new Buffer(reader.writeCursor + data.length); + reader.inBuf.copy(buf, 0, 0, reader.writeCursor); + reader.inBuf = buf; + } + data.copy(reader.inBuf, reader.writeCursor, 0); + reader.writeCursor += data.length; + + callback(reader, seqid); + }; +}; + + +TBufferedTransport.prototype.commitPosition = function(){ + var unreadSize = this.writeCursor - this.readCursor; + var bufSize = (unreadSize * 2 > this.defaultReadBufferSize) ? + unreadSize * 2 : this.defaultReadBufferSize; + var buf = new Buffer(bufSize); + if (unreadSize > 0) { + this.inBuf.copy(buf, 0, this.readCursor, this.writeCursor); + } + this.readCursor = 0; + this.writeCursor = unreadSize; + this.inBuf = buf; +}; + +TBufferedTransport.prototype.rollbackPosition = function(){ + this.readCursor = 0; +} + + // TODO: Implement open/close support +TBufferedTransport.prototype.isOpen = function() { + return true; +}; + +TBufferedTransport.prototype.open = function() { +}; + +TBufferedTransport.prototype.close = function() { +}; + + // Set the seqid of the message in the client + // So that callbacks can be found +TBufferedTransport.prototype.setCurrSeqId = function(seqid) { + this._seqid = seqid; +}; + +TBufferedTransport.prototype.ensureAvailable = function(len) { + if (this.readCursor + len > this.writeCursor) { + throw new InputBufferUnderrunError(); + } +}; + +TBufferedTransport.prototype.read = function(len) { + this.ensureAvailable(len); + var buf = new Buffer(len); + this.inBuf.copy(buf, 0, this.readCursor, this.readCursor + len); + this.readCursor += len; + return buf; +}; + +TBufferedTransport.prototype.readByte = function() { + this.ensureAvailable(1); + return binary.readByte(this.inBuf[this.readCursor++]); +}; + +TBufferedTransport.prototype.readI16 = function() { + this.ensureAvailable(2); + var i16 = binary.readI16(this.inBuf, this.readCursor); + this.readCursor += 2; + return i16; +}; + +TBufferedTransport.prototype.readI32 = function() { + this.ensureAvailable(4); + var i32 = binary.readI32(this.inBuf, this.readCursor); + this.readCursor += 4; + return i32; +}; + +TBufferedTransport.prototype.readDouble = function() { + this.ensureAvailable(8); + var d = binary.readDouble(this.inBuf, this.readCursor); + this.readCursor += 8; + return d; +}; + +TBufferedTransport.prototype.readString = function(len) { + this.ensureAvailable(len); + var str = this.inBuf.toString('utf8', this.readCursor, this.readCursor + len); + this.readCursor += len; + return str; +}; + +TBufferedTransport.prototype.borrow = function() { + var obj = {buf: this.inBuf, readIndex: this.readCursor, writeIndex: this.writeCursor}; + return obj; +}; + +TBufferedTransport.prototype.consume = function(bytesConsumed) { + this.readCursor += bytesConsumed; +}; + +TBufferedTransport.prototype.write = function(buf) { + if (typeof(buf) === "string") { + buf = new Buffer(buf, 'utf8'); + } + this.outBuffers.push(buf); + this.outCount += buf.length; +}; + +TBufferedTransport.prototype.flush = function() { + // If the seqid of the callback is available pass it to the onFlush + // Then remove the current seqid + var seqid = this._seqid; + this._seqid = null; + + if (this.outCount < 1) { + return; + } + + var msg = new Buffer(this.outCount), + pos = 0; + this.outBuffers.forEach(function(buf) { + buf.copy(msg, pos, 0); + pos += buf.length; + }); + + if (this.onFlush) { + // Passing seqid through this call to get it to the connection + this.onFlush(msg, seqid); + } + + this.outBuffers = []; + this.outCount = 0; +} diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/compact_protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/compact_protocol.js new file mode 100644 index 000000000..302a88d4d --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/compact_protocol.js @@ -0,0 +1,915 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var log = require('./log'); +var Int64 = require('node-int64'); +var Thrift = require('./thrift'); +var Type = Thrift.Type; + +module.exports = TCompactProtocol; + +var POW_8 = Math.pow(2, 8); +var POW_24 = Math.pow(2, 24); +var POW_32 = Math.pow(2, 32); +var POW_40 = Math.pow(2, 40); +var POW_48 = Math.pow(2, 48); +var POW_52 = Math.pow(2, 52); +var POW_1022 = Math.pow(2, 1022); + +/** + * Constructor Function for the Compact Protocol. + * @constructor + * @param {object} [trans] - The underlying transport to read/write. + * @classdesc The Apache Thrift Protocol layer performs serialization + * of base types, the compact protocol serializes data in binary + * form with minimal space used for scalar values. + */ +function TCompactProtocol(trans) { + this.trans = trans; + this.lastField_ = []; + this.lastFieldId_ = 0; + this.string_limit_ = 0; + this.string_buf_ = null; + this.string_buf_size_ = 0; + this.container_limit_ = 0; + this.booleanField_ = { + name: null, + hasBoolValue: false + }; + this.boolValue_ = { + hasBoolValue: false, + boolValue: false + }; +}; + + +// +// Compact Protocol Constants +// + +/** + * Compact Protocol ID number. + * @readonly + * @const {number} PROTOCOL_ID + */ +TCompactProtocol.PROTOCOL_ID = -126; //1000 0010 + +/** + * Compact Protocol version number. + * @readonly + * @const {number} VERSION_N + */ +TCompactProtocol.VERSION_N = 1; + +/** + * Compact Protocol version mask for combining protocol version and message type in one byte. + * @readonly + * @const {number} VERSION_MASK + */ +TCompactProtocol.VERSION_MASK = 0x1f; //0001 1111 + +/** + * Compact Protocol message type mask for combining protocol version and message type in one byte. + * @readonly + * @const {number} TYPE_MASK + */ +TCompactProtocol.TYPE_MASK = -32; //1110 0000 + +/** + * Compact Protocol message type bits for ensuring message type bit size. + * @readonly + * @const {number} TYPE_BITS + */ +TCompactProtocol.TYPE_BITS = 7; //0000 0111 + +/** + * Compact Protocol message type shift amount for combining protocol version and message type in one byte. + * @readonly + * @const {number} TYPE_SHIFT_AMOUNT + */ +TCompactProtocol.TYPE_SHIFT_AMOUNT = 5; + +/** + * Compact Protocol type IDs used to keep type data within one nibble. + * @readonly + * @property {number} CT_STOP - End of a set of fields. + * @property {number} CT_BOOLEAN_TRUE - Flag for Boolean field with true value (packed field and value). + * @property {number} CT_BOOLEAN_FALSE - Flag for Boolean field with false value (packed field and value). + * @property {number} CT_BYTE - Signed 8 bit integer. + * @property {number} CT_I16 - Signed 16 bit integer. + * @property {number} CT_I32 - Signed 32 bit integer. + * @property {number} CT_I64 - Signed 64 bit integer (2^53 max in JavaScript). + * @property {number} CT_DOUBLE - 64 bit IEEE 854 floating point. + * @property {number} CT_BINARY - Array of bytes (used for strings also). + * @property {number} CT_LIST - A collection type (unordered). + * @property {number} CT_SET - A collection type (unordered and without repeated values). + * @property {number} CT_MAP - A collection type (map/associative-array/dictionary). + * @property {number} CT_STRUCT - A multifield type. + */ +TCompactProtocol.Types = { + CT_STOP: 0x00, + CT_BOOLEAN_TRUE: 0x01, + CT_BOOLEAN_FALSE: 0x02, + CT_BYTE: 0x03, + CT_I16: 0x04, + CT_I32: 0x05, + CT_I64: 0x06, + CT_DOUBLE: 0x07, + CT_BINARY: 0x08, + CT_LIST: 0x09, + CT_SET: 0x0A, + CT_MAP: 0x0B, + CT_STRUCT: 0x0C +}; + +/** + * Array mapping Compact type IDs to standard Thrift type IDs. + * @readonly + */ +TCompactProtocol.TTypeToCType = [ + TCompactProtocol.Types.CT_STOP, // T_STOP + 0, // unused + TCompactProtocol.Types.CT_BOOLEAN_TRUE, // T_BOOL + TCompactProtocol.Types.CT_BYTE, // T_BYTE + TCompactProtocol.Types.CT_DOUBLE, // T_DOUBLE + 0, // unused + TCompactProtocol.Types.CT_I16, // T_I16 + 0, // unused + TCompactProtocol.Types.CT_I32, // T_I32 + 0, // unused + TCompactProtocol.Types.CT_I64, // T_I64 + TCompactProtocol.Types.CT_BINARY, // T_STRING + TCompactProtocol.Types.CT_STRUCT, // T_STRUCT + TCompactProtocol.Types.CT_MAP, // T_MAP + TCompactProtocol.Types.CT_SET, // T_SET + TCompactProtocol.Types.CT_LIST, // T_LIST +]; + + +// +// Compact Protocol Utilities +// + +/** + * Returns the underlying transport layer. + * @return {object} The underlying transport layer. + */TCompactProtocol.prototype.getTransport = function() { + return this.trans; +}; + +/** + * Lookup a Compact Protocol Type value for a given Thrift Type value. + * N.B. Used only internally. + * @param {number} ttype - Thrift type value + * @returns {number} Compact protocol type value + */ +TCompactProtocol.prototype.getCompactType = function(ttype) { + return TCompactProtocol.TTypeToCType[ttype]; +}; + +/** + * Lookup a Thrift Type value for a given Compact Protocol Type value. + * N.B. Used only internally. + * @param {number} type - Compact Protocol type value + * @returns {number} Thrift Type value + */ +TCompactProtocol.prototype.getTType = function(type) { + switch (type) { + case Type.STOP: + return Type.STOP; + case TCompactProtocol.Types.CT_BOOLEAN_FALSE: + case TCompactProtocol.Types.CT_BOOLEAN_TRUE: + return Type.BOOL; + case TCompactProtocol.Types.CT_BYTE: + return Type.BYTE; + case TCompactProtocol.Types.CT_I16: + return Type.I16; + case TCompactProtocol.Types.CT_I32: + return Type.I32; + case TCompactProtocol.Types.CT_I64: + return Type.I64; + case TCompactProtocol.Types.CT_DOUBLE: + return Type.DOUBLE; + case TCompactProtocol.Types.CT_BINARY: + return Type.STRING; + case TCompactProtocol.Types.CT_LIST: + return Type.LIST; + case TCompactProtocol.Types.CT_SET: + return Type.SET; + case TCompactProtocol.Types.CT_MAP: + return Type.MAP; + case TCompactProtocol.Types.CT_STRUCT: + return Type.STRUCT; + default: + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.INVALID_DATA, "Unknown type: " + type); + } + return Type.STOP; +}; + + +// +// Compact Protocol write operations +// + +/** + * Send any buffered bytes to the end point. + */ +TCompactProtocol.prototype.flush = function() { + return this.trans.flush(); +}; + +/** + * Writes an RPC message header + * @param {string} name - The method name for the message. + * @param {number} type - The type of message (CALL, REPLY, EXCEPTION, ONEWAY). + * @param {number} seqid - The call sequence number (if any). + */ +TCompactProtocol.prototype.writeMessageBegin = function(name, type, seqid) { + this.writeByte(TCompactProtocol.PROTOCOL_ID); + this.writeByte((TCompactProtocol.VERSION_N & TCompactProtocol.VERSION_MASK) | + ((type << TCompactProtocol.TYPE_SHIFT_AMOUNT) & TCompactProtocol.TYPE_MASK)); + this.writeVarint32(seqid); + this.writeString(name); + + // Record client seqid to find callback again + if (this._seqid) { + log.warning('SeqId already set', { 'name': name }); + } else { + this._seqid = seqid; + this.trans.setCurrSeqId(seqid); + } +}; + +TCompactProtocol.prototype.writeMessageEnd = function() { +}; + +TCompactProtocol.prototype.writeStructBegin = function(name) { + this.lastField_.push(this.lastFieldId_); + this.lastFieldId_ = 0; +}; + +TCompactProtocol.prototype.writeStructEnd = function() { + this.lastFieldId_ = this.lastField_.pop(); +}; + +/** + * Writes a struct field header + * @param {string} name - The field name (not written with the compact protocol). + * @param {number} type - The field data type (a normal Thrift field Type). + * @param {number} id - The IDL field Id. + */ +TCompactProtocol.prototype.writeFieldBegin = function(name, type, id) { + if (type != Type.BOOL) { + return this.writeFieldBeginInternal(name, type, id, -1); + } + + this.booleanField_.name = name; + this.booleanField_.fieldType = type; + this.booleanField_.fieldId = id; +}; + +TCompactProtocol.prototype.writeFieldEnd = function() { +}; + +TCompactProtocol.prototype.writeFieldStop = function() { + this.writeByte(TCompactProtocol.Types.CT_STOP); +}; + +/** + * Writes a map collection header + * @param {number} keyType - The Thrift type of the map keys. + * @param {number} valType - The Thrift type of the map values. + * @param {number} size - The number of k/v pairs in the map. + */ +TCompactProtocol.prototype.writeMapBegin = function(keyType, valType, size) { + if (size === 0) { + this.writeByte(0); + } else { + this.writeVarint32(size); + this.writeByte(this.getCompactType(keyType) << 4 | this.getCompactType(valType)); + } +}; + +TCompactProtocol.prototype.writeMapEnd = function() { +}; + +/** + * Writes a list collection header + * @param {number} elemType - The Thrift type of the list elements. + * @param {number} size - The number of elements in the list. + */ +TCompactProtocol.prototype.writeListBegin = function(elemType, size) { + this.writeCollectionBegin(elemType, size); +}; + +TCompactProtocol.prototype.writeListEnd = function() { +}; + +/** + * Writes a set collection header + * @param {number} elemType - The Thrift type of the set elements. + * @param {number} size - The number of elements in the set. + */ +TCompactProtocol.prototype.writeSetBegin = function(elemType, size) { + this.writeCollectionBegin(elemType, size); +}; + +TCompactProtocol.prototype.writeSetEnd = function() { +}; + +TCompactProtocol.prototype.writeBool = function(value) { + if (this.booleanField_.name !== null) { + // we haven't written the field header yet + this.writeFieldBeginInternal(this.booleanField_.name, + this.booleanField_.fieldType, + this.booleanField_.fieldId, + (value ? TCompactProtocol.Types.CT_BOOLEAN_TRUE + : TCompactProtocol.Types.CT_BOOLEAN_FALSE)); + this.booleanField_.name = null; + } else { + // we're not part of a field, so just write the value + this.writeByte((value ? TCompactProtocol.Types.CT_BOOLEAN_TRUE + : TCompactProtocol.Types.CT_BOOLEAN_FALSE)); + } +}; + +TCompactProtocol.prototype.writeByte = function(b) { + this.trans.write(new Buffer([b])); +}; + +TCompactProtocol.prototype.writeI16 = function(i16) { + this.writeVarint32(this.i32ToZigzag(i16)); +}; + +TCompactProtocol.prototype.writeI32 = function(i32) { + this.writeVarint32(this.i32ToZigzag(i32)); +}; + +TCompactProtocol.prototype.writeI64 = function(i64) { + this.writeVarint64(this.i64ToZigzag(i64)); +}; + +// Little-endian, unlike TBinaryProtocol +TCompactProtocol.prototype.writeDouble = function(v) { + var buff = new Buffer(8); + var m, e, c; + + buff[7] = (v < 0 ? 0x80 : 0x00); + + v = Math.abs(v); + if (v !== v) { + // NaN, use QNaN IEEE format + m = 2251799813685248; + e = 2047; + } else if (v === Infinity) { + m = 0; + e = 2047; + } else { + e = Math.floor(Math.log(v) / Math.LN2); + c = Math.pow(2, -e); + if (v * c < 1) { + e--; + c *= 2; + } + + if (e + 1023 >= 2047) + { + // Overflow + m = 0; + e = 2047; + } + else if (e + 1023 >= 1) + { + // Normalized - term order matters, as Math.pow(2, 52-e) and v*Math.pow(2, 52) can overflow + m = (v*c-1) * POW_52; + e += 1023; + } + else + { + // Denormalized - also catches the '0' case, somewhat by chance + m = (v * POW_1022) * POW_52; + e = 0; + } + } + + buff[6] = (e << 4) & 0xf0; + buff[7] |= (e >> 4) & 0x7f; + + buff[0] = m & 0xff; + m = Math.floor(m / POW_8); + buff[1] = m & 0xff; + m = Math.floor(m / POW_8); + buff[2] = m & 0xff; + m = Math.floor(m / POW_8); + buff[3] = m & 0xff; + m >>= 8; + buff[4] = m & 0xff; + m >>= 8; + buff[5] = m & 0xff; + m >>= 8; + buff[6] |= m & 0x0f; + + this.trans.write(buff); +}; + +TCompactProtocol.prototype.writeStringOrBinary = function(name, encoding, arg) { + if (typeof arg === 'string') { + this.writeVarint32(Buffer.byteLength(arg, encoding)) ; + this.trans.write(new Buffer(arg, encoding)); + } else if (arg instanceof Buffer || + Object.prototype.toString.call(arg) == '[object Uint8Array]') { + // Buffers in Node.js under Browserify may extend UInt8Array instead of + // defining a new object. We detect them here so we can write them + // correctly + this.writeVarint32(arg.length); + this.trans.write(arg); + } else { + throw new Error(name + ' called without a string/Buffer argument: ' + arg); + } +}; + +TCompactProtocol.prototype.writeString = function(arg) { + this.writeStringOrBinary('writeString', 'utf8', arg); +}; + +TCompactProtocol.prototype.writeBinary = function(arg) { + this.writeStringOrBinary('writeBinary', 'binary', arg); +}; + + +// +// Compact Protocol internal write methods +// + +TCompactProtocol.prototype.writeFieldBeginInternal = function(name, + fieldType, + fieldId, + typeOverride) { + //If there's a type override, use that. + var typeToWrite = (typeOverride == -1 ? this.getCompactType(fieldType) : typeOverride); + //Check if we can delta encode the field id + if (fieldId > this.lastFieldId_ && fieldId - this.lastFieldId_ <= 15) { + //Include the type delta with the field ID + this.writeByte((fieldId - this.lastFieldId_) << 4 | typeToWrite); + } else { + //Write separate type and ID values + this.writeByte(typeToWrite); + this.writeI16(fieldId); + } + this.lastFieldId_ = fieldId; +}; + +TCompactProtocol.prototype.writeCollectionBegin = function(elemType, size) { + if (size <= 14) { + //Combine size and type in one byte if possible + this.writeByte(size << 4 | this.getCompactType(elemType)); + } else { + this.writeByte(0xf0 | this.getCompactType(elemType)); + this.writeVarint32(size); + } +}; + +/** + * Write an i32 as a varint. Results in 1-5 bytes on the wire. + */ +TCompactProtocol.prototype.writeVarint32 = function(n) { + var buf = new Buffer(5); + var wsize = 0; + while (true) { + if ((n & ~0x7F) === 0) { + buf[wsize++] = n; + break; + } else { + buf[wsize++] = ((n & 0x7F) | 0x80); + n = n >>> 7; + } + } + var wbuf = new Buffer(wsize); + buf.copy(wbuf,0,0,wsize); + this.trans.write(wbuf); +}; + +/** + * Write an i64 as a varint. Results in 1-10 bytes on the wire. + * N.B. node-int64 is always big endian + */ +TCompactProtocol.prototype.writeVarint64 = function(n) { + if (typeof n === "number"){ + n = new Int64(n); + } + if (! (n instanceof Int64)) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.INVALID_DATA, "Expected Int64 or Number, found: " + n); + } + + var buf = new Buffer(10); + var wsize = 0; + var hi = n.buffer.readUInt32BE(0, true); + var lo = n.buffer.readUInt32BE(4, true); + var mask = 0; + while (true) { + if (((lo & ~0x7F) === 0) && (hi === 0)) { + buf[wsize++] = lo; + break; + } else { + buf[wsize++] = ((lo & 0x7F) | 0x80); + mask = hi << 25; + lo = lo >>> 7; + hi = hi >>> 7; + lo = lo | mask; + } + } + var wbuf = new Buffer(wsize); + buf.copy(wbuf,0,0,wsize); + this.trans.write(wbuf); +}; + +/** + * Convert l into a zigzag long. This allows negative numbers to be + * represented compactly as a varint. + */ +TCompactProtocol.prototype.i64ToZigzag = function(l) { + if (typeof l === 'string') { + l = new Int64(parseInt(l, 10)); + } else if (typeof l === 'number') { + l = new Int64(l); + } + if (! (l instanceof Int64)) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.INVALID_DATA, "Expected Int64 or Number, found: " + l); + } + var hi = l.buffer.readUInt32BE(0, true); + var lo = l.buffer.readUInt32BE(4, true); + var sign = hi >>> 31; + hi = ((hi << 1) | (lo >>> 31)) ^ ((!!sign) ? 0xFFFFFFFF : 0); + lo = (lo << 1) ^ ((!!sign) ? 0xFFFFFFFF : 0); + return new Int64(hi, lo); +}; + +/** + * Convert n into a zigzag int. This allows negative numbers to be + * represented compactly as a varint. + */ +TCompactProtocol.prototype.i32ToZigzag = function(n) { + return (n << 1) ^ ((n & 0x80000000) ? 0xFFFFFFFF : 0); +}; + + +// +// Compact Protocol read operations +// + +TCompactProtocol.prototype.readMessageBegin = function() { + //Read protocol ID + var protocolId = this.trans.readByte(); + if (protocolId != TCompactProtocol.PROTOCOL_ID) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.BAD_VERSION, "Bad protocol identifier " + protocolId); + } + + //Read Version and Type + var versionAndType = this.trans.readByte(); + var version = (versionAndType & TCompactProtocol.VERSION_MASK); + if (version != TCompactProtocol.VERSION_N) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.BAD_VERSION, "Bad protocol version " + version); + } + var type = ((versionAndType >> TCompactProtocol.TYPE_SHIFT_AMOUNT) & TCompactProtocol.TYPE_BITS); + + //Read SeqId + var seqid = this.readVarint32(); + + //Read name + var name = this.readString(); + + return {fname: name, mtype: type, rseqid: seqid}; +}; + +TCompactProtocol.prototype.readMessageEnd = function() { +}; + +TCompactProtocol.prototype.readStructBegin = function() { + this.lastField_.push(this.lastFieldId_); + this.lastFieldId_ = 0; + return {fname: ''}; +}; + +TCompactProtocol.prototype.readStructEnd = function() { + this.lastFieldId_ = this.lastField_.pop(); +}; + +TCompactProtocol.prototype.readFieldBegin = function() { + var fieldId = 0; + var b = this.trans.readByte(b); + var type = (b & 0x0f); + + if (type == TCompactProtocol.Types.CT_STOP) { + return {fname: null, ftype: Thrift.Type.STOP, fid: 0}; + } + + //Mask off the 4 MSB of the type header to check for field id delta. + var modifier = ((b & 0x000000f0) >>> 4); + if (modifier === 0) { + //If not a delta read the field id. + fieldId = this.readI16(); + } else { + //Recover the field id from the delta + fieldId = (this.lastFieldId_ + modifier); + } + var fieldType = this.getTType(type); + + //Boolean are encoded with the type + if (type == TCompactProtocol.Types.CT_BOOLEAN_TRUE || + type == TCompactProtocol.Types.CT_BOOLEAN_FALSE) { + this.boolValue_.hasBoolValue = true; + this.boolValue_.boolValue = + (type == TCompactProtocol.Types.CT_BOOLEAN_TRUE ? true : false); + } + + //Save the new field for the next delta computation. + this.lastFieldId_ = fieldId; + return {fname: null, ftype: fieldType, fid: fieldId}; +}; + +TCompactProtocol.prototype.readFieldEnd = function() { +}; + +TCompactProtocol.prototype.readMapBegin = function() { + var msize = this.readVarint32(); + if (msize < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative map size"); + } + + var kvType = 0; + if (msize !== 0) { + kvType = this.trans.readByte(); + } + + var keyType = this.getTType((kvType & 0xf0) >>> 4); + var valType = this.getTType(kvType & 0xf); + return {ktype: keyType, vtype: valType, size: msize}; +}; + +TCompactProtocol.prototype.readMapEnd = function() { +}; + +TCompactProtocol.prototype.readListBegin = function() { + var size_and_type = this.trans.readByte(); + + var lsize = (size_and_type >>> 4) & 0x0000000f; + if (lsize == 15) { + lsize = this.readVarint32(); + } + + if (lsize < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative list size"); + } + + var elemType = this.getTType(size_and_type & 0x0000000f); + + return {etype: elemType, size: lsize}; +}; + +TCompactProtocol.prototype.readListEnd = function() { +}; + +TCompactProtocol.prototype.readSetBegin = function() { + return this.readListBegin(); +}; + +TCompactProtocol.prototype.readSetEnd = function() { +}; + +TCompactProtocol.prototype.readBool = function() { + var value = false; + var rsize = 0; + if (this.boolValue_.hasBoolValue === true) { + value = this.boolValue_.boolValue; + this.boolValue_.hasBoolValue = false; + } else { + var res = this.trans.readByte(); + rsize = res.rsize; + value = (res.value == TCompactProtocol.Types.CT_BOOLEAN_TRUE); + } + return value; +}; + +TCompactProtocol.prototype.readByte = function() { + return this.trans.readByte(); +}; + +TCompactProtocol.prototype.readI16 = function() { + return this.readI32(); +}; + +TCompactProtocol.prototype.readI32 = function() { + return this.zigzagToI32(this.readVarint32()); +}; + +TCompactProtocol.prototype.readI64 = function() { + return this.zigzagToI64(this.readVarint64()); +}; + +// Little-endian, unlike TBinaryProtocol +TCompactProtocol.prototype.readDouble = function() { + var buff = this.trans.read(8); + var off = 0; + + var signed = buff[off + 7] & 0x80; + var e = (buff[off+6] & 0xF0) >> 4; + e += (buff[off+7] & 0x7F) << 4; + + var m = buff[off]; + m += buff[off+1] << 8; + m += buff[off+2] << 16; + m += buff[off+3] * POW_24; + m += buff[off+4] * POW_32; + m += buff[off+5] * POW_40; + m += (buff[off+6] & 0x0F) * POW_48; + + switch (e) { + case 0: + e = -1022; + break; + case 2047: + return m ? NaN : (signed ? -Infinity : Infinity); + default: + m += POW_52; + e -= 1023; + } + + if (signed) { + m *= -1; + } + + return m * Math.pow(2, e - 52); +}; + +TCompactProtocol.prototype.readBinary = function() { + var size = this.readVarint32(); + if (size === 0) { + return new Buffer(0); + } + + if (size < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative binary size"); + } + return this.trans.read(size); +}; + +TCompactProtocol.prototype.readString = function() { + var size = this.readVarint32(); + // Catch empty string case + if (size === 0) { + return ""; + } + + // Catch error cases + if (size < 0) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.NEGATIVE_SIZE, "Negative string size"); + } + return this.trans.readString(size); +}; + + +// +// Compact Protocol internal read operations +// + +/** + * Read an i32 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 5 bytes. + */ +TCompactProtocol.prototype.readVarint32 = function() { + return this.readVarint64().toNumber(); +}; + +/** + * Read an i64 from the wire as a proper varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 10 bytes. + */ +TCompactProtocol.prototype.readVarint64 = function() { + var rsize = 0; + var lo = 0; + var hi = 0; + var shift = 0; + while (true) { + var b = this.trans.readByte(); + rsize ++; + if (shift <= 25) { + lo = lo | ((b & 0x7f) << shift); + } else if (25 < shift && shift < 32) { + lo = lo | ((b & 0x7f) << shift); + hi = hi | ((b & 0x7f) >>> (32-shift)); + } else { + hi = hi | ((b & 0x7f) << (shift-32)); + } + shift += 7; + if (!(b & 0x80)) { + break; + } + if (rsize >= 10) { + throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.INVALID_DATA, "Variable-length int over 10 bytes."); + } + } + return new Int64(hi, lo); +}; + +/** + * Convert from zigzag int to int. + */ +TCompactProtocol.prototype.zigzagToI32 = function(n) { + return (n >>> 1) ^ (-1 * (n & 1)); +}; + +/** + * Convert from zigzag long to long. + */ +TCompactProtocol.prototype.zigzagToI64 = function(n) { + var hi = n.buffer.readUInt32BE(0, true); + var lo = n.buffer.readUInt32BE(4, true); + + var neg = new Int64(hi & 0, lo & 1); + neg._2scomp(); + var hi_neg = neg.buffer.readUInt32BE(0, true); + var lo_neg = neg.buffer.readUInt32BE(4, true); + + var hi_lo = (hi << 31); + hi = (hi >>> 1) ^ (hi_neg); + lo = ((lo >>> 1) | hi_lo) ^ (lo_neg); + return new Int64(hi, lo); +}; + +TCompactProtocol.prototype.skip = function(type) { + switch (type) { + case Type.BOOL: + this.readBool(); + break; + case Type.BYTE: + this.readByte(); + break; + case Type.I16: + this.readI16(); + break; + case Type.I32: + this.readI32(); + break; + case Type.I64: + this.readI64(); + break; + case Type.DOUBLE: + this.readDouble(); + break; + case Type.STRING: + this.readString(); + break; + case Type.STRUCT: + this.readStructBegin(); + while (true) { + var r = this.readFieldBegin(); + if (r.ftype === Type.STOP) { + break; + } + this.skip(r.ftype); + this.readFieldEnd(); + } + this.readStructEnd(); + break; + case Type.MAP: + var mapBegin = this.readMapBegin(); + for (var i = 0; i < mapBegin.size; ++i) { + this.skip(mapBegin.ktype); + this.skip(mapBegin.vtype); + } + this.readMapEnd(); + break; + case Type.SET: + var setBegin = this.readSetBegin(); + for (var i2 = 0; i2 < setBegin.size; ++i2) { + this.skip(setBegin.etype); + } + this.readSetEnd(); + break; + case Type.LIST: + var listBegin = this.readListBegin(); + for (var i3 = 0; i3 < listBegin.size; ++i3) { + this.skip(listBegin.etype); + } + this.readListEnd(); + break; + default: + throw new Error("Invalid type: " + type); + } +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/connection.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/connection.js new file mode 100644 index 000000000..25e34ed42 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/connection.js @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var constants = require('constants'); +var net = require('net'); +var tls = require('tls'); +var thrift = require('./thrift'); +var log = require('./log'); + +var TBufferedTransport = require('./buffered_transport'); +var TBinaryProtocol = require('./binary_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +var createClient = require('./create_client'); + +var binary = require('./binary'); + +var Connection = exports.Connection = function(stream, options) { + var self = this; + EventEmitter.call(this); + + this.seqId2Service = {}; + this.connection = stream; + this.ssl = (stream.encrypted); + this.options = options || {}; + this.transport = this.options.transport || TBufferedTransport; + this.protocol = this.options.protocol || TBinaryProtocol; + this.offline_queue = []; + this.connected = false; + this.initialize_retry_vars(); + + this._debug = this.options.debug || false; + if (this.options.max_attempts && + !isNaN(this.options.max_attempts) && + this.options.max_attempts > 0) { + this.max_attempts = +this.options.max_attempts; + } + this.retry_max_delay = null; + if (this.options.retry_max_delay !== undefined && + !isNaN(this.options.retry_max_delay) && + this.options.retry_max_delay > 0) { + this.retry_max_delay = this.options.retry_max_delay; + } + this.connect_timeout = false; + if (this.options.connect_timeout && + !isNaN(this.options.connect_timeout) && + this.options.connect_timeout > 0) { + this.connect_timeout = +this.options.connect_timeout; + } + + this.connection.addListener(this.ssl ? "secureConnect" : "connect", function() { + self.connected = true; + + this.setTimeout(self.options.timeout || 0); + this.setNoDelay(); + this.frameLeft = 0; + this.framePos = 0; + this.frame = null; + self.initialize_retry_vars(); + self.flush_offline_queue(); + + self.emit("connect"); + }); + + this.connection.addListener("error", function(err) { + // Only emit the error if no-one else is listening on the connection + // or if someone is listening on us, because Node turns unhandled + // 'error' events into exceptions. + if (self.connection.listeners('error').length === 1 || + self.listeners('error').length > 0) { + self.emit("error", err); + } + }); + + // Add a close listener + this.connection.addListener("close", function() { + self.connection_gone(); // handle close event. try to reconnect + }); + + this.connection.addListener("timeout", function() { + self.emit("timeout"); + }); + + this.connection.addListener("data", self.transport.receiver(function(transport_with_data) { + var message = new self.protocol(transport_with_data); + try { + while (true) { + var header = message.readMessageBegin(); + var dummy_seqid = header.rseqid * -1; + var client = self.client; + //The Multiplexed Protocol stores a hash of seqid to service names + // in seqId2Service. If the SeqId is found in the hash we need to + // lookup the appropriate client for this call. + // The connection.client object is a single client object when not + // multiplexing, when using multiplexing it is a service name keyed + // hash of client objects. + //NOTE: The 2 way interdependencies between protocols, transports, + // connections and clients in the Node.js implementation are irregular + // and make the implementation difficult to extend and maintain. We + // should bring this stuff inline with typical thrift I/O stack + // operation soon. + // --ra + var service_name = self.seqId2Service[header.rseqid]; + if (service_name) { + client = self.client[service_name]; + } + /*jshint -W083 */ + client._reqs[dummy_seqid] = function(err, success){ + transport_with_data.commitPosition(); + + var callback = client._reqs[header.rseqid]; + delete client._reqs[header.rseqid]; + if (service_name) { + delete self.seqId2Service[header.rseqid]; + } + if (callback) { + callback(err, success); + } + }; + /*jshint +W083 */ + + if(client['recv_' + header.fname]) { + client['recv_' + header.fname](message, header.mtype, dummy_seqid); + } else { + delete client._reqs[dummy_seqid]; + self.emit("error", + new thrift.TApplicationException(thrift.TApplicationExceptionType.WRONG_METHOD_NAME, + "Received a response to an unknown RPC function")); + } + } + } + catch (e) { + if (e instanceof InputBufferUnderrunError) { + transport_with_data.rollbackPosition(); + } + else { + self.emit('error', e); + } + } + })); +}; +util.inherits(Connection, EventEmitter); + +Connection.prototype.end = function() { + this.connection.end(); +}; + +Connection.prototype.destroy = function() { + this.connection.destroy(); +}; + +Connection.prototype.initialize_retry_vars = function () { + this.retry_timer = null; + this.retry_totaltime = 0; + this.retry_delay = 150; + this.retry_backoff = 1.7; + this.attempts = 0; +}; + +Connection.prototype.flush_offline_queue = function () { + var self = this; + var offline_queue = this.offline_queue; + + // Reset offline queue + this.offline_queue = []; + // Attempt to write queued items + offline_queue.forEach(function(data) { + self.write(data); + }); +}; + +Connection.prototype.write = function(data) { + if (!this.connected) { + this.offline_queue.push(data); + return; + } + this.connection.write(data); +}; + +Connection.prototype.connection_gone = function () { + var self = this; + this.connected = false; + + // If a retry is already in progress, just let that happen + if (this.retry_timer) { + return; + } + // We cannot reconnect a secure socket. + if (!this.max_attempts || this.ssl) { + self.emit("close"); + return; + } + + if (this.retry_max_delay !== null && this.retry_delay >= this.retry_max_delay) { + this.retry_delay = this.retry_max_delay; + } else { + this.retry_delay = Math.floor(this.retry_delay * this.retry_backoff); + } + + log.debug("Retry connection in " + this.retry_delay + " ms"); + + if (this.max_attempts && this.attempts >= this.max_attempts) { + this.retry_timer = null; + console.error("thrift: Couldn't get thrift connection after " + this.max_attempts + " attempts."); + self.emit("close"); + return; + } + + this.attempts += 1; + this.emit("reconnecting", { + delay: self.retry_delay, + attempt: self.attempts + }); + + this.retry_timer = setTimeout(function () { + if (self.connection.destroyed) { + self.retry_timer = null; + return; + } + + log.debug("Retrying connection..."); + + self.retry_totaltime += self.retry_delay; + + if (self.connect_timeout && self.retry_totaltime >= self.connect_timeout) { + self.retry_timer = null; + console.error("thrift: Couldn't get thrift connection after " + self.retry_totaltime + "ms."); + self.emit("close"); + return; + } + + if (self.path !== undefined) { + self.connection.connect(self.path); + } else { + self.connection.connect(self.port, self.host); + } + self.retry_timer = null; + }, this.retry_delay); +}; + +exports.createConnection = function(host, port, options) { + var stream = net.createConnection( { + port: port, + host: host, + timeout: options.connect_timeout || options.timeout || 0 + }); + var connection = new Connection(stream, options); + connection.host = host; + connection.port = port; + + return connection; +}; + +exports.createUDSConnection = function(path, options) { + var stream = net.createConnection(path); + var connection = new Connection(stream, options); + connection.path = path; + + return connection; +}; + +exports.createSSLConnection = function(host, port, options) { + if (!('secureProtocol' in options) && !('secureOptions' in options)) { + options.secureProtocol = "SSLv23_method"; + options.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3; + } + + var stream = tls.connect(port, host, options); + var connection = new Connection(stream, options); + connection.host = host; + connection.port = port; + + return connection; +}; + + +exports.createClient = createClient; + +var child_process = require('child_process'); +var StdIOConnection = exports.StdIOConnection = function(command, options) { + var command_parts = command.split(' '); + command = command_parts[0]; + var args = command_parts.splice(1,command_parts.length -1); + var child = this.child = child_process.spawn(command,args); + + var self = this; + EventEmitter.call(this); + + this.connection = child.stdin; + this.options = options || {}; + this.transport = this.options.transport || TBufferedTransport; + this.protocol = this.options.protocol || TBinaryProtocol; + this.offline_queue = []; + + if (log.getLogLevel() === 'debug') { + this.child.stderr.on('data', function (err) { + log.debug(err.toString(), 'CHILD ERROR'); + }); + + this.child.on('exit', function (code,signal) { + log.debug(code + ':' + signal, 'CHILD EXITED'); + }); + } + + this.frameLeft = 0; + this.framePos = 0; + this.frame = null; + this.connected = true; + + self.flush_offline_queue(); + + this.connection.addListener("error", function(err) { + self.emit("error", err); + }); + + // Add a close listener + this.connection.addListener("close", function() { + self.emit("close"); + }); + + child.stdout.addListener("data", self.transport.receiver(function(transport_with_data) { + var message = new self.protocol(transport_with_data); + try { + var header = message.readMessageBegin(); + var dummy_seqid = header.rseqid * -1; + var client = self.client; + client._reqs[dummy_seqid] = function(err, success){ + transport_with_data.commitPosition(); + + var callback = client._reqs[header.rseqid]; + delete client._reqs[header.rseqid]; + if (callback) { + callback(err, success); + } + }; + client['recv_' + header.fname](message, header.mtype, dummy_seqid); + } + catch (e) { + if (e instanceof InputBufferUnderrunError) { + transport_with_data.rollbackPosition(); + } + else { + throw e; + } + } + })); +}; + +util.inherits(StdIOConnection, EventEmitter); + +StdIOConnection.prototype.end = function() { + this.connection.end(); +}; + +StdIOConnection.prototype.flush_offline_queue = function () { + var self = this; + var offline_queue = this.offline_queue; + + // Reset offline queue + this.offline_queue = []; + // Attempt to write queued items + offline_queue.forEach(function(data) { + self.write(data); + }); +}; + +StdIOConnection.prototype.write = function(data) { + if (!this.connected) { + this.offline_queue.push(data); + return; + } + this.connection.write(data); +}; + +exports.createStdIOConnection = function(command,options){ + return new StdIOConnection(command,options); +}; + +exports.createStdIOClient = createClient; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/create_client.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/create_client.js new file mode 100644 index 000000000..d6b77a833 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/create_client.js @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = createClient; + +/** + * Creates a new client object for the specified Thrift service. + * @param {object} ServiceClient - The module containing the generated service client + * @param {Connection} Connection - The connection to use. + * @returns {object} The client object. + */ +function createClient(ServiceClient, connection) { + // TODO validate required options and throw otherwise + if (ServiceClient.Client) { + ServiceClient = ServiceClient.Client; + } + // TODO detangle these initialization calls + // creating "client" requires + // - new service client instance + // + // New service client instance requires + // - new transport instance + // - protocol class reference + // + // New transport instance requires + // - Buffer to use (or none) + // - Callback to call on flush + + // Wrap the write method + var writeCb = function(buf, seqid) { + connection.write(buf, seqid); + }; + var transport = new connection.transport(undefined, writeCb); + var client = new ServiceClient(transport, connection.protocol); + transport.client = client; + connection.client = client; + return client; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/framed_transport.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/framed_transport.js new file mode 100644 index 000000000..f7daa3f1c --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/framed_transport.js @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var binary = require('./binary'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); +var THeaderTransport = require('./header_transport'); + +module.exports = TFramedTransport; + +function TFramedTransport(buffer, callback) { + this.inBuf = buffer || new Buffer(0); + this.outBuffers = []; + this.outCount = 0; + this.readPos = 0; + this.onFlush = callback; +}; + +TFramedTransport.prototype = new THeaderTransport(); + +TFramedTransport.receiver = function(callback, seqid) { + var residual = null; + + return function(data) { + // Prepend any residual data from our previous read + if (residual) { + data = Buffer.concat([residual, data]); + residual = null; + } + + // framed transport + while (data.length) { + if (data.length < 4) { + // Not enough bytes to continue, save and resume on next packet + residual = data; + return; + } + var frameSize = binary.readI32(data, 0); + if (data.length < 4 + frameSize) { + // Not enough bytes to continue, save and resume on next packet + residual = data; + return; + } + + var frame = data.slice(4, 4 + frameSize); + residual = data.slice(4 + frameSize); + + callback(new TFramedTransport(frame), seqid); + + data = residual; + residual = null; + } + }; +}; + +TFramedTransport.prototype.commitPosition = function(){}, +TFramedTransport.prototype.rollbackPosition = function(){}, + + // TODO: Implement open/close support +TFramedTransport.prototype.isOpen = function() { + return true; +}; +TFramedTransport.prototype.open = function() {}; +TFramedTransport.prototype.close = function() {}; + + // Set the seqid of the message in the client + // So that callbacks can be found +TFramedTransport.prototype.setCurrSeqId = function(seqid) { + this._seqid = seqid; +}; + +TFramedTransport.prototype.ensureAvailable = function(len) { + if (this.readPos + len > this.inBuf.length) { + throw new InputBufferUnderrunError(); + } +}; + +TFramedTransport.prototype.read = function(len) { // this function will be used for each frames. + this.ensureAvailable(len); + var end = this.readPos + len; + + if (this.inBuf.length < end) { + throw new Error('read(' + len + ') failed - not enough data'); + } + + var buf = this.inBuf.slice(this.readPos, end); + this.readPos = end; + return buf; +}; + +TFramedTransport.prototype.readByte = function() { + this.ensureAvailable(1); + return binary.readByte(this.inBuf[this.readPos++]); +}; + +TFramedTransport.prototype.readI16 = function() { + this.ensureAvailable(2); + var i16 = binary.readI16(this.inBuf, this.readPos); + this.readPos += 2; + return i16; +}; + +TFramedTransport.prototype.readI32 = function() { + this.ensureAvailable(4); + var i32 = binary.readI32(this.inBuf, this.readPos); + this.readPos += 4; + return i32; +}; + +TFramedTransport.prototype.readDouble = function() { + this.ensureAvailable(8); + var d = binary.readDouble(this.inBuf, this.readPos); + this.readPos += 8; + return d; +}; + +TFramedTransport.prototype.readString = function(len) { + this.ensureAvailable(len); + var str = this.inBuf.toString('utf8', this.readPos, this.readPos + len); + this.readPos += len; + return str; +}; + +TFramedTransport.prototype.borrow = function() { + return { + buf: this.inBuf, + readIndex: this.readPos, + writeIndex: this.inBuf.length + }; +}; + +TFramedTransport.prototype.consume = function(bytesConsumed) { + this.readPos += bytesConsumed; +}; + +TFramedTransport.prototype.write = function(buf, encoding) { + if (typeof(buf) === "string") { + buf = new Buffer(buf, encoding || 'utf8'); + } + this.outBuffers.push(buf); + this.outCount += buf.length; +}; + +TFramedTransport.prototype.flush = function() { + // If the seqid of the callback is available pass it to the onFlush + // Then remove the current seqid + var seqid = this._seqid; + this._seqid = null; + + var out = new Buffer(this.outCount), + pos = 0; + this.outBuffers.forEach(function(buf) { + buf.copy(out, pos, 0); + pos += buf.length; + }); + + if (this.onFlush) { + // TODO: optimize this better, allocate one buffer instead of both: + var msg = new Buffer(out.length + 4); + binary.writeI32(msg, out.length); + out.copy(msg, 4, 0, out.length); + if (this.onFlush) { + // Passing seqid through this call to get it to the connection + this.onFlush(msg, seqid); + } + } + + this.outBuffers = []; + this.outCount = 0; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_protocol.js new file mode 100644 index 000000000..0c3b0db43 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_protocol.js @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var TBinaryProtocol = require('./binary_protocol'); +var TCompactProtocol = require('./compact_protocol'); +var THeaderTransport = require('./header_transport'); + +var ProtocolMap = {}; +ProtocolMap[THeaderTransport.SubprotocolId.BINARY] = TBinaryProtocol; +ProtocolMap[THeaderTransport.SubprotocolId.COMPACT] = TCompactProtocol; + +module.exports = THeaderProtocol; + +function THeaderProtocolError(message) { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; +} + +util.inherits(THeaderProtocolError, Error); + +/** + * A framed protocol with headers. + * + * THeaderProtocol frames other Thrift protocols and adds support for + * optional out-of-band headers. The currently supported subprotocols are + * TBinaryProtocol and TCompactProtocol. It can currently only be used with + * transports that inherit THeaderTransport. + * + * THeaderProtocol does not currently support THTTPServer, TNonblockingServer, + * or TProcessPoolServer. + * + * See doc/specs/HeaderFormat.md for details of the wire format. + */ +function THeaderProtocol(trans) { + if (!(trans instanceof THeaderTransport)) { + throw new THeaderProtocolError( + 'Only transports that inherit THeaderTransport can be' + + ' used with THeaderProtocol' + ); + } + this.trans = trans; + this.setProtocol(); +}; + +THeaderProtocol.prototype.flush = function() { + // Headers must be written prior to flushing because because + // you need to calculate the length of the payload for the length + // field of the header + this.trans.writeHeaders(); + return this.trans.flush(); +}; + +THeaderProtocol.prototype.writeMessageBegin = function(name, type, seqid) { + return this.protocol.writeMessageBegin(name, type, seqid); +}; + +THeaderProtocol.prototype.writeMessageEnd = function() { + return this.protocol.writeMessageEnd(); +}; + +THeaderProtocol.prototype.writeStructBegin = function(name) { + return this.protocol.writeStructBegin(name); +}; + +THeaderProtocol.prototype.writeStructEnd = function() { + return this.protocol.writeStructEnd(); +}; + +THeaderProtocol.prototype.writeFieldBegin = function(name, type, id) { + return this.protocol.writeFieldBegin(name, type, id); +} + +THeaderProtocol.prototype.writeFieldEnd = function() { + return this.protocol.writeFieldEnd(); +}; + +THeaderProtocol.prototype.writeFieldStop = function() { + return this.protocol.writeFieldStop(); +}; + +THeaderProtocol.prototype.writeMapBegin = function(ktype, vtype, size) { + return this.protocol.writeMapBegin(ktype, vtype, size); +}; + +THeaderProtocol.prototype.writeMapEnd = function() { + return this.protocol.writeMapEnd(); +}; + +THeaderProtocol.prototype.writeListBegin = function(etype, size) { + return this.protocol.writeListBegin(etype, size); +}; + +THeaderProtocol.prototype.writeListEnd = function() { + return this.protocol.writeListEnd(); +}; + +THeaderProtocol.prototype.writeSetBegin = function(etype, size) { + return this.protocol.writeSetBegin(etype, size); +}; + +THeaderProtocol.prototype.writeSetEnd = function() { + return this.protocol.writeSetEnd(); +}; + +THeaderProtocol.prototype.writeBool = function(b) { + return this.protocol.writeBool(b); +}; + +THeaderProtocol.prototype.writeByte = function(b) { + return this.protocol.writeByte(b); +}; + +THeaderProtocol.prototype.writeI16 = function(i16) { + return this.protocol.writeI16(i16); +}; + +THeaderProtocol.prototype.writeI32 = function(i32) { + return this.protocol.writeI32(i32); +}; + +THeaderProtocol.prototype.writeI64 = function(i64) { + return this.protocol.writeI64(i64); +}; + +THeaderProtocol.prototype.writeDouble = function(dub) { + return this.protocol.writeDouble(dub); +}; + +THeaderProtocol.prototype.writeStringOrBinary = function(name, encoding, arg) { + return this.protocol.writeStringOrBinary(name, encoding, arg); +}; + +THeaderProtocol.prototype.writeString = function(arg) { + return this.protocol.writeString(arg); +}; + +THeaderProtocol.prototype.writeBinary = function(arg) { + return this.protocol.writeBinary(arg); +}; + +THeaderProtocol.prototype.readMessageBegin = function() { + this.trans.readHeaders(); + this.setProtocol(); + return this.protocol.readMessageBegin(); +}; + +THeaderProtocol.prototype.readMessageEnd = function() { + return this.protocol.readMessageEnd(); +}; + +THeaderProtocol.prototype.readStructBegin = function() { + return this.protocol.readStructBegin(); +}; + +THeaderProtocol.prototype.readStructEnd = function() { + return this.protocol.readStructEnd(); +}; + +THeaderProtocol.prototype.readFieldBegin = function() { + return this.protocol.readFieldBegin(); +}; + +THeaderProtocol.prototype.readFieldEnd = function() { + return this.protocol.readFieldEnd(); +}; + +THeaderProtocol.prototype.readMapBegin = function() { + return this.protocol.readMapBegin(); +}; + +THeaderProtocol.prototype.readMapEnd = function() { + return this.protocol.readMapEnd(); +}; + +THeaderProtocol.prototype.readListBegin = function() { + return this.protocol.readListBegin(); +}; + +THeaderProtocol.prototype.readListEnd = function() { + return this.protocol.readListEnd(); +}; + +THeaderProtocol.prototype.readSetBegin = function() { + return this.protocol.readSetBegin(); +}; + +THeaderProtocol.prototype.readSetEnd = function() { + return this.protocol.readSetEnd(); +}; + +THeaderProtocol.prototype.readBool = function() { + return this.protocol.readBool(); +}; + +THeaderProtocol.prototype.readByte = function() { + return this.protocol.readByte(); +}; + +THeaderProtocol.prototype.readI16 = function() { + return this.protocol.readI16(); +}; + +THeaderProtocol.prototype.readI32 = function() { + return this.protocol.readI32(); +}; + +THeaderProtocol.prototype.readI64 = function() { + return this.protocol.readI64(); +}; + +THeaderProtocol.prototype.readDouble = function() { + return this.protocol.readDouble(); +}; + +THeaderProtocol.prototype.readBinary = function() { + return this.protocol.readBinary(); +}; + +THeaderProtocol.prototype.readString = function() { + return this.protocol.readString(); +}; + +THeaderProtocol.prototype.getTransport = function() { + return this.trans; +}; + +THeaderProtocol.prototype.skip = function(type) { + return this.protocol.skip(type); +}; + +THeaderProtocol.prototype.setProtocol = function(subProtocolId) { + var subProtocolId = this.trans.getProtocolId(); + if (!ProtocolMap[subProtocolId]) { + throw new THeaderProtocolError('Headers not supported for protocol ' + subProtocolId); + } + + this.protocol = new ProtocolMap[subProtocolId](this.trans); +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_transport.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_transport.js new file mode 100644 index 000000000..c5f133e8d --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/header_transport.js @@ -0,0 +1,339 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var util = require('util'); +var TCompactProtocol = require('./compact_protocol'); +var TBinaryProtocol = require('./binary_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +function THeaderTransportError(message) { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; +} + +util.inherits(THeaderTransportError, Error); + +module.exports = THeaderTransport; + +// from HeaderFormat.md +var COMPACT_PROTOCOL_OFFSET = 0; +var COMPACT_PROTOCOL_VERSION_OFFSET = 1; +var FRAME_SIZE_OFFSET = 0; +var HEADER_MAGIC_OFFSET = 32 / 8; +var FLAGS_OFFSET = 48 / 8; +var SEQID_OFFSET = 64 / 8; +var HEADER_SIZE_OFFSET = 96 / 8; +var HEADER_START_OFFSET = 112 / 8; + +var HEADER_MAGIC = 0x0FFF; + +var TINFO_HEADER_KEY_VALUE_TYPE = 0x01; +var MAX_FRAME_SIZE = 0x3FFFFFFF; + + // A helper class for reading/writing varints. Uses + // TCompactProtocol under the hood +function VarintHelper(readBuffer) { + var TBufferedTransport = require('./buffered_transport'); + this.outputBuffer = null; + var _this = this; + this.transport = new TBufferedTransport(null, function(output) { + _this.outputBuffer = output; + }); + + this.transport.inBuf = readBuffer || Buffer.alloc(0); + this.transport.writeCursor = this.transport.inBuf.length; + this.protocol = new TCompactProtocol(this.transport); +}; + +VarintHelper.prototype.readVarint32 = function() { + return this.protocol.readVarint32(); +}; + +VarintHelper.prototype.writeVarint32 = function(i) { + this.protocol.writeVarint32(i); +}; + +VarintHelper.prototype.readString = function() { + return this.protocol.readString(); +}; + +VarintHelper.prototype.writeString = function(str) { + this.protocol.writeString(str); +} + +VarintHelper.prototype.getOutCount = function() { + return this.transport.outCount; +}; + +VarintHelper.prototype.write = function(str) { + this.transport.write(str); +}; + +VarintHelper.prototype.toBuffer = function() { + this.transport.flush(); + return this.outputBuffer; +}; + +// from lib/cpp/src/thrift/protocol/TProtocolTypes.h +THeaderTransport.SubprotocolId = { + BINARY: 0, + JSON: 1, + COMPACT: 2, +}; + +/** + An abstract transport used as a prototype for other transports + to enable reading/writing theaders. This should NOT be used as a standalone transport + The methods in this transport are called by THeaderProtocol, which will call readHeaders/writeHeaders + in the read/writeMessageBegin methods and parse/write headers to/from a request + prior to reading/writing. + + The reason this is not a standalone transport type is because different transport types + have their own individual static receiver methods that are called prior to instantiation. + There doesn't seem to be a way for THeaderTransport to know which receiver method to use + without reworking the server API. + + For reading headers from a request, the parsed headers can be retrieved via + getReadHeader. Similarly, you can set headers to be written on the client via + setWriteHeader. + */ +function THeaderTransport() { + this.maxFrameSize = MAX_FRAME_SIZE; + this.protocolId = THeaderTransport.SubprotocolId.BINARY; + this.rheaders = {}; + this.wheaders = {}; + this.inBuf = Buffer.alloc(0); + this.outCount = 0; + this.flags = null; + this.seqid = 0; + this.shouldWriteHeaders = true; +}; + +var validateHeaders = function(key, value) { + if (typeof key !== 'string' || typeof value !== 'string') { + throw new THeaderTransportError('Header key and values must be strings'); + } +}; + +var validateProtocolId = function(protocolId) { + var protocols = Object.keys(THeaderTransport.SubprotocolId); + for (var i = 0; i < protocols.length; i++) { + if (protocolId === THeaderTransport.SubprotocolId[protocols[i]]) return true; + } + + throw new Error(protocolId + ' is not a valid protocol id'); +}; + +THeaderTransport.prototype.setSeqId = function(seqid) { + this.seqid = seqid; +}; + +THeaderTransport.prototype.getSeqId = function(seqid) { + return this.seqid; +}; + +THeaderTransport.prototype.setFlags = function(flags) { + this.flags = flags; +}; + +THeaderTransport.prototype.getReadHeaders = function() { + return this.rheaders; +}; + +THeaderTransport.prototype.setReadHeader = function(key, value) { + validateHeaders(key, value); + this.rheaders[key] = value; +}; + +THeaderTransport.prototype.clearReadHeaders = function() { + this.rheaders = {}; +}; + +THeaderTransport.prototype.getWriteHeaders = function() { + return this.wheaders; +}; + +THeaderTransport.prototype.setWriteHeader = function(key, value) { + validateHeaders(key, value); + this.wheaders[key] = value; +}; + +THeaderTransport.prototype.clearWriteHeaders = function() { + this.wheaders = {}; +}; + +THeaderTransport.prototype.setMaxFrameSize = function(frameSize) { + this.maxFrameSize = frameSize; +}; + +THeaderTransport.prototype.setProtocolId = function(protocolId) { + validateProtocolId(protocolId); + this.protocolId = protocolId; +}; + +THeaderTransport.prototype.getProtocolId = function() { + return this.protocolId; +}; + +var isUnframedBinary = function(readBuffer) { + var version = readBuffer.readInt32BE(); + return (version & TBinaryProtocol.VERSION_MASK) === TBinaryProtocol.VERSION_1; +} + +var isUnframedCompact = function(readBuffer) { + var protocolId = readBuffer.readInt8(COMPACT_PROTOCOL_OFFSET); + var version = readBuffer.readInt8(COMPACT_PROTOCOL_VERSION_OFFSET); + return protocolId === TCompactProtocol.PROTOCOL_ID && + (version & TCompactProtocol.VERSION_MASK) === TCompactProtocol.VERSION_N; +} + +THeaderTransport.prototype.readHeaders = function() { + var readBuffer = this.inBuf; + + var isUnframed = false; + if (isUnframedBinary(readBuffer)) { + this.setProtocolId(THeaderTransport.SubprotocolId.BINARY); + isUnframed = true; + } + + if (isUnframedCompact(readBuffer)) { + this.setProtocolId(THeaderTransport.SubprotocolId.COMPACT); + isUnframed = true; + } + + if (isUnframed) { + this.shouldWriteHeaders = false; + return; + } + + var frameSize = readBuffer.readInt32BE(FRAME_SIZE_OFFSET); + if (frameSize > this.maxFrameSize) { + throw new THeaderTransportError('Frame exceeds maximum frame size'); + } + + var headerMagic = readBuffer.readInt16BE(HEADER_MAGIC_OFFSET); + this.shouldWriteHeaders = headerMagic === HEADER_MAGIC; + if (!this.shouldWriteHeaders) { + return; + } + + this.setFlags(readBuffer.readInt16BE(FLAGS_OFFSET)); + this.setSeqId(readBuffer.readInt32BE(SEQID_OFFSET)); + var headerSize = readBuffer.readInt16BE(HEADER_SIZE_OFFSET) * 4; + var endOfHeaders = HEADER_START_OFFSET + headerSize; + if (endOfHeaders > readBuffer.length) { + throw new THeaderTransportError('Header size is greater than frame size'); + } + + var headerBuffer = Buffer.alloc(headerSize); + readBuffer.copy(headerBuffer, 0, HEADER_START_OFFSET, endOfHeaders); + + var varintHelper = new VarintHelper(headerBuffer); + this.setProtocolId(varintHelper.readVarint32()); + var transformCount = varintHelper.readVarint32(); + if (transformCount > 0) { + throw new THeaderTransportError('Transforms are not yet supported'); + } + + while (true) { + try { + var headerType = varintHelper.readVarint32(); + if (headerType !== TINFO_HEADER_KEY_VALUE_TYPE) { + break; + } + + var numberOfHeaders = varintHelper.readVarint32(); + for (var i = 0; i < numberOfHeaders; i++) { + var key = varintHelper.readString(); + var value = varintHelper.readString(); + this.setReadHeader(key, value); + } + } catch (e) { + if (e instanceof InputBufferUnderrunError) { + break; + } + throw e; + } + } + + // moves the read cursor past the headers + this.read(endOfHeaders); + return this.getReadHeaders(); +}; + +THeaderTransport.prototype.writeHeaders = function() { + // only write headers on the server if the client contained headers + if (!this.shouldWriteHeaders) { + return; + } + var headers = this.getWriteHeaders(); + + var varintWriter = new VarintHelper(); + varintWriter.writeVarint32(this.protocolId); + varintWriter.writeVarint32(0); // transforms not supported + + // writing info header key values + var headerKeys = Object.keys(headers); + if (headerKeys.length > 0) { + varintWriter.writeVarint32(TINFO_HEADER_KEY_VALUE_TYPE); + varintWriter.writeVarint32(headerKeys.length); + for (var i = 0; i < headerKeys.length; i++) { + var key = headerKeys[i]; + var value = headers[key]; + + varintWriter.writeString(key); + varintWriter.writeString(value); + } + } + var headerSizeWithoutPadding = varintWriter.getOutCount(); + var paddingNeeded = (4 - (headerSizeWithoutPadding % 4)) % 4; + + var headerSize = Buffer.alloc(2); + headerSize.writeInt16BE(Math.floor((headerSizeWithoutPadding + paddingNeeded) / 4)); + + var paddingBuffer = Buffer.alloc(paddingNeeded); + paddingBuffer.fill(0x00); + varintWriter.write(paddingBuffer); + var headerContentBuffer = varintWriter.toBuffer(); + var frameSize = Buffer.alloc(4); + frameSize.writeInt32BE(10 + this.outCount + headerContentBuffer.length); + var headerMagic = Buffer.alloc(2); + headerMagic.writeInt16BE(HEADER_MAGIC); + + // flags are not yet supported, so write a zero + var flags = Buffer.alloc(2); + flags.writeInt16BE(0); + + var seqid = Buffer.alloc(4); + seqid.writeInt32BE(this.getSeqId()); + + var headerBuffer = Buffer.concat([ + frameSize, + headerMagic, + flags, + seqid, + headerSize, + headerContentBuffer, + ]); + + this.outBuffers.unshift(headerBuffer); + this.outCount += headerBuffer.length; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/http_connection.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/http_connection.js new file mode 100644 index 000000000..3c2ab0f53 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/http_connection.js @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var http = require('http'); +var https = require('https'); +var EventEmitter = require('events').EventEmitter; +var thrift = require('./thrift'); + +var TBufferedTransport = require('./buffered_transport'); +var TBinaryProtocol = require('./binary_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +var createClient = require('./create_client'); + +/** + * @class + * @name ConnectOptions + * @property {string} transport - The Thrift layered transport to use (TBufferedTransport, etc). + * @property {string} protocol - The Thrift serialization protocol to use (TBinaryProtocol, etc.). + * @property {string} path - The URL path to POST to (e.g. "/", "/mySvc", "/thrift/quoteSvc", etc.). + * @property {object} headers - A standard Node.js header hash, an object hash containing key/value + * pairs where the key is the header name string and the value is the header value string. + * @property {boolean} https - True causes the connection to use https, otherwise http is used. + * @property {object} nodeOptions - Options passed on to node. + * @example + * //Use a connection that requires ssl/tls, closes the connection after each request, + * // uses the buffered transport layer, uses the JSON protocol and directs RPC traffic + * // to https://thrift.example.com:9090/hello + * var thrift = require('thrift'); + * var options = { + * transport: thrift.TBufferedTransport, + * protocol: thrift.TJSONProtocol, + * path: "/hello", + * headers: {"Connection": "close"}, + * https: true + * }; + * var con = thrift.createHttpConnection("thrift.example.com", 9090, options); + * var client = thrift.createHttpClient(myService, connection); + * client.myServiceFunction(); + */ + +/** + * Initializes a Thrift HttpConnection instance (use createHttpConnection() rather than + * instantiating directly). + * @constructor + * @param {ConnectOptions} options - The configuration options to use. + * @throws {error} Exceptions other than InputBufferUnderrunError are rethrown + * @event {error} The "error" event is fired when a Node.js error event occurs during + * request or response processing, in which case the node error is passed on. An "error" + * event may also be fired when the connection can not map a response back to the + * appropriate client (an internal error), generating a TApplicationException. + * @classdesc HttpConnection objects provide Thrift end point transport + * semantics implemented over the Node.js http.request() method. + * @see {@link createHttpConnection} + */ +var HttpConnection = exports.HttpConnection = function(options) { + //Initialize the emitter base object + EventEmitter.call(this); + + //Set configuration + var self = this; + this.options = options || {}; + this.host = this.options.host; + this.port = this.options.port; + this.socketPath = this.options.socketPath; + this.https = this.options.https || false; + this.transport = this.options.transport || TBufferedTransport; + this.protocol = this.options.protocol || TBinaryProtocol; + + //Prepare Node.js options + this.nodeOptions = { + host: this.host, + port: this.port, + socketPath: this.socketPath, + path: this.options.path || '/', + method: 'POST', + headers: this.options.headers || {}, + responseType: this.options.responseType || null + }; + for (var attrname in this.options.nodeOptions) { + this.nodeOptions[attrname] = this.options.nodeOptions[attrname]; + } + /*jshint -W069 */ + if (! this.nodeOptions.headers['Connection']) { + this.nodeOptions.headers['Connection'] = 'keep-alive'; + } + /*jshint +W069 */ + + //The sequence map is used to map seqIDs back to the + // calling client in multiplexed scenarios + this.seqId2Service = {}; + + function decodeCallback(transport_with_data) { + var proto = new self.protocol(transport_with_data); + try { + while (true) { + var header = proto.readMessageBegin(); + var dummy_seqid = header.rseqid * -1; + var client = self.client; + //The Multiplexed Protocol stores a hash of seqid to service names + // in seqId2Service. If the SeqId is found in the hash we need to + // lookup the appropriate client for this call. + // The client var is a single client object when not multiplexing, + // when using multiplexing it is a service name keyed hash of client + // objects. + //NOTE: The 2 way interdependencies between protocols, transports, + // connections and clients in the Node.js implementation are irregular + // and make the implementation difficult to extend and maintain. We + // should bring this stuff inline with typical thrift I/O stack + // operation soon. + // --ra + var service_name = self.seqId2Service[header.rseqid]; + if (service_name) { + client = self.client[service_name]; + delete self.seqId2Service[header.rseqid]; + } + /*jshint -W083 */ + client._reqs[dummy_seqid] = function(err, success){ + transport_with_data.commitPosition(); + var clientCallback = client._reqs[header.rseqid]; + delete client._reqs[header.rseqid]; + if (clientCallback) { + process.nextTick(function() { + clientCallback(err, success); + }); + } + }; + /*jshint +W083 */ + if(client['recv_' + header.fname]) { + client['recv_' + header.fname](proto, header.mtype, dummy_seqid); + } else { + delete client._reqs[dummy_seqid]; + self.emit("error", + new thrift.TApplicationException( + thrift.TApplicationExceptionType.WRONG_METHOD_NAME, + "Received a response to an unknown RPC function")); + } + } + } + catch (e) { + if (e instanceof InputBufferUnderrunError) { + transport_with_data.rollbackPosition(); + } else { + self.emit('error', e); + } + } + } + + + //Response handler + ////////////////////////////////////////////////// + this.responseCallback = function(response) { + var data = []; + var dataLen = 0; + + if (response.statusCode !== 200) { + this.emit("error", new THTTPException(response)); + } + + response.on('error', function (e) { + self.emit("error", e); + }); + + // When running directly under node, chunk will be a buffer, + // however, when running in a Browser (e.g. Browserify), chunk + // will be a string or an ArrayBuffer. + response.on('data', function (chunk) { + if ((typeof chunk == 'string') || + (Object.prototype.toString.call(chunk) == '[object Uint8Array]')) { + // Wrap ArrayBuffer/string in a Buffer so data[i].copy will work + data.push(new Buffer(chunk)); + } else { + data.push(chunk); + } + dataLen += chunk.length; + }); + + response.on('end', function(){ + var buf = new Buffer(dataLen); + for (var i=0, len=data.length, pos=0; i<len; i++) { + data[i].copy(buf, pos); + pos += data[i].length; + } + //Get the receiver function for the transport and + // call it with the buffer + self.transport.receiver(decodeCallback)(buf); + }); + }; +}; +util.inherits(HttpConnection, EventEmitter); + +/** + * Writes Thrift message data to the connection + * @param {Buffer} data - A Node.js Buffer containing the data to write + * @returns {void} No return value. + * @event {error} the "error" event is raised upon request failure passing the + * Node.js error object to the listener. + */ +HttpConnection.prototype.write = function(data) { + var self = this; + var opts = self.nodeOptions; + opts.headers["Content-length"] = data.length; + if (!opts.headers["Content-Type"]) + opts.headers["Content-Type"] = "application/x-thrift"; + var req = (self.https) ? + https.request(opts, self.responseCallback) : + http.request(opts, self.responseCallback); + req.on('error', function(err) { + self.emit("error", err); + }); + req.write(data); + req.end(); +}; + +/** + * Creates a new HttpConnection object, used by Thrift clients to connect + * to Thrift HTTP based servers. + * @param {string} host - The host name or IP to connect to. + * @param {number} port - The TCP port to connect to. + * @param {ConnectOptions} options - The configuration options to use. + * @returns {HttpConnection} The connection object. + * @see {@link ConnectOptions} + */ +exports.createHttpConnection = function(host, port, options) { + options.host = host; + options.port = port || 80; + return new HttpConnection(options); +}; + +exports.createHttpUDSConnection = function(path, options) { + options.socketPath = path; + return new HttpConnection(options); +}; + +exports.createHttpClient = createClient + + +function THTTPException(response) { + thrift.TApplicationException.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.statusCode = response.statusCode; + this.response = response; + this.type = thrift.TApplicationExceptionType.PROTOCOL_ERROR; + this.message = "Received a response with a bad HTTP status code: " + response.statusCode; +} +util.inherits(THTTPException, thrift.TApplicationException); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/index.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/index.js new file mode 100644 index 000000000..0a2d02b71 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/index.js @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +exports.Thrift = require('./thrift'); + +var log = require('./log'); +exports.setLogFunc = log.setLogFunc; +exports.setLogLevel = log.setLogLevel; +exports.getLogLevel = log.getLogLevel; + +var connection = require('./connection'); +exports.Connection = connection.Connection; +exports.createClient = connection.createClient; +exports.createConnection = connection.createConnection; +exports.createUDSConnection = connection.createUDSConnection; +exports.createSSLConnection = connection.createSSLConnection; +exports.createStdIOClient = connection.createStdIOClient; +exports.createStdIOConnection = connection.createStdIOConnection; + +var httpConnection = require('./http_connection'); +exports.HttpConnection = httpConnection.HttpConnection; +exports.createHttpConnection = httpConnection.createHttpConnection; +exports.createHttpUDSConnection = httpConnection.createHttpUDSConnection; +exports.createHttpClient = httpConnection.createHttpClient; + +var wsConnection = require('./ws_connection'); +exports.WSConnection = wsConnection.WSConnection; +exports.createWSConnection = wsConnection.createWSConnection; +exports.createWSClient = wsConnection.createWSClient; + +var xhrConnection = require('./xhr_connection'); +exports.XHRConnection = xhrConnection.XHRConnection; +exports.createXHRConnection = xhrConnection.createXHRConnection; +exports.createXHRClient = xhrConnection.createXHRClient; + +var server = require('./server'); +exports.createServer = server.createServer; +exports.createMultiplexServer = server.createMultiplexServer; + +var web_server = require('./web_server'); +exports.createWebServer = web_server.createWebServer; + +exports.Int64 = require('node-int64'); +exports.Q = require('q'); + +var mprocessor = require('./multiplexed_processor'); +var mprotocol = require('./multiplexed_protocol'); +exports.Multiplexer = mprotocol.Multiplexer; +exports.MultiplexedProcessor = mprocessor.MultiplexedProcessor; + +/* + * Export transport and protocol so they can be used outside of a + * cassandra/server context + */ +exports.TFramedTransport = require('./framed_transport'); +exports.TBufferedTransport = require('./buffered_transport'); +exports.TBinaryProtocol = require('./binary_protocol'); +exports.TJSONProtocol = require('./json_protocol'); +exports.TCompactProtocol = require('./compact_protocol'); +exports.THeaderProtocol = require('./header_protocol'); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/input_buffer_underrun_error.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/input_buffer_underrun_error.js new file mode 100644 index 000000000..72555e516 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/input_buffer_underrun_error.js @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require("util"); + +module.exports = InputBufferUnderrunError; + +function InputBufferUnderrunError(message) { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; +}; + +util.inherits(InputBufferUnderrunError, Error); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/int64_util.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/int64_util.js new file mode 100644 index 000000000..e8d707de4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/int64_util.js @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Int64 = require('node-int64'); + +var Int64Util = module.exports = {}; + +var POW2_24 = Math.pow(2, 24); +var POW2_31 = Math.pow(2, 31); +var POW2_32 = Math.pow(2, 32); +var POW10_11 = Math.pow(10, 11); + +Int64Util.toDecimalString = function(i64) { + var b = i64.buffer; + var o = i64.offset; + if ((!b[o] && !(b[o + 1] & 0xe0)) || + (!~b[o] && !~(b[o + 1] & 0xe0))) { + // The magnitude is small enough. + return i64.toString(); + } else { + var negative = b[o] & 0x80; + if (negative) { + // 2's complement + var incremented = false; + var buffer = new Buffer(8); + for (var i = 7; i >= 0; --i) { + buffer[i] = (~b[o + i] + (incremented ? 0 : 1)) & 0xff; + incremented |= b[o + i]; + } + b = buffer; + } + var high2 = b[o + 1] + (b[o] << 8); + // Lesser 11 digits with exceeding values but is under 53 bits capacity. + var low = b[o + 7] + (b[o + 6] << 8) + (b[o + 5] << 16) + + b[o + 4] * POW2_24 // Bit shift renders 32th bit as sign, so use multiplication + + (b[o + 3] + (b[o + 2] << 8)) * POW2_32 + high2 * 74976710656; // The literal is 2^48 % 10^11 + // 12th digit and greater. + var high = Math.floor(low / POW10_11) + high2 * 2814; // The literal is 2^48 / 10^11 + // Make it exactly 11 with leading zeros. + low = ('00000000000' + String(low % POW10_11)).slice(-11); + return (negative ? '-' : '') + String(high) + low; + } +}; + +Int64Util.fromDecimalString = function(text) { + var negative = text.charAt(0) === '-'; + if (text.length < (negative ? 17 : 16)) { + // The magnitude is smaller than 2^53. + return new Int64(+text); + } else if (text.length > (negative ? 20 : 19)) { + throw new RangeError('Too many digits for Int64: ' + text); + } else { + // Most significant (up to 5) digits + var high5 = +text.slice(negative ? 1 : 0, -15); + var low = +text.slice(-15) + high5 * 2764472320; // The literal is 10^15 % 2^32 + var high = Math.floor(low / POW2_32) + high5 * 232830; // The literal is 10^15 / 2^&32 + low = low % POW2_32; + if (high >= POW2_31 && + !(negative && high == POW2_31 && low == 0) // Allow minimum Int64 + ) { + throw new RangeError('The magnitude is too large for Int64.'); + } + if (negative) { + // 2's complement + high = ~high; + if (low === 0) { + high = (high + 1) & 0xffffffff; + } else { + low = ~low + 1; + } + high = 0x80000000 | high; + } + return new Int64(high, low); + } +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_parse.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_parse.js new file mode 100644 index 000000000..93b0bf2ab --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_parse.js @@ -0,0 +1,299 @@ +/* + * Imported from Douglas Crockford's reference implementation with minimum modification + * to handle Int64. + * + * https://github.com/douglascrockford/JSON-js/blob/c98948ae1944a28e2e8ebc3717894e580aeaaa05/json_parse.js + * + * Original license header: + * + * json_parse.js + * 2015-05-02 + * Public Domain. + * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + */ + + +/*jslint for */ + +/*property + at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, + prototype, push, r, t, text +*/ + +var Int64 = require('node-int64'); +var Int64Util = require('./int64_util'); + +var json_parse = module.exports = (function () { + "use strict"; + +// This is a function that can parse a JSON text, producing a JavaScript +// data structure. It is a simple, recursive descent parser. It does not use +// eval or regular expressions, so it can be used as a model for implementing +// a JSON parser in other languages. + +// We are defining the function inside of another function to avoid creating +// global variables. + + var at, // The index of the current character + ch, // The current character + escapee = { + '"': '"', + '\\': '\\', + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' + }, + text, + + error = function (m) { + +// Call error when something is wrong. + + throw new SyntaxError(m); + }, + + next = function (c) { + +// If a c parameter is provided, verify that it matches the current character. + + if (c && c !== ch) { + error("Expected '" + c + "' instead of '" + ch + "'"); + } + +// Get the next character. When there are no more characters, +// return the empty string. + + ch = text.charAt(at); + at += 1; + return ch; + }, + + number = function () { + +// Parse a number value. + + var number, + string = ''; + + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { + string += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + } + number = +string; + if (!isFinite(number)) { + error("Bad number"); + } else if (number >= Int64.MAX_INT || number <= Int64.MIN_INT) { + // Return raw string for further process in TJSONProtocol + return string; + } else { + return number; + } + }, + + string = function () { + +// Parse a string value. + + var hex, + i, + string = '', + uffff; + +// When parsing for string values, we must look for " and \ characters. + + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } + if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + } + } + error("Bad string"); + }, + + white = function () { + +// Skip whitespace. + + while (ch && ch <= ' ') { + next(); + } + }, + + word = function () { + +// true, false, or null. + + switch (ch) { + case 't': + next('t'); + next('r'); + next('u'); + next('e'); + return true; + case 'f': + next('f'); + next('a'); + next('l'); + next('s'); + next('e'); + return false; + case 'n': + next('n'); + next('u'); + next('l'); + next('l'); + return null; + } + error("Unexpected '" + ch + "'"); + }, + + value, // Place holder for the value function. + + array = function () { + +// Parse an array value. + + var array = []; + + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); + white(); + if (ch === ']') { + next(']'); + return array; + } + next(','); + white(); + } + } + error("Bad array"); + }, + + object = function () { + +// Parse an object value. + + var key, + object = {}; + + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); + white(); + if (ch === '}') { + next('}'); + return object; + } + next(','); + white(); + } + } + error("Bad object"); + }; + + value = function () { + +// Parse a JSON value. It could be an object, an array, a string, a number, +// or a word. + + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' + ? number() + : word(); + } + }; + +// Return the json_parse function. It will have access to all of the above +// functions and variables. + + return function (source) { + var result; + + text = source; + at = 0; + ch = ' '; + result = value(); + white(); + if (ch) { + error("Syntax error"); + } + + return result; + }; +}()); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_protocol.js new file mode 100644 index 000000000..7e2b7c908 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/json_protocol.js @@ -0,0 +1,799 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Int64 = require('node-int64'); +var Thrift = require('./thrift'); +var Type = Thrift.Type; +var util = require("util"); + +var Int64Util = require('./int64_util'); +var json_parse = require('./json_parse'); + +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +module.exports = TJSONProtocol; + +/** + * Initializes a Thrift JSON protocol instance. + * @constructor + * @param {Thrift.Transport} trans - The transport to serialize to/from. + * @classdesc Apache Thrift Protocols perform serialization which enables cross + * language RPC. The Protocol type is the JavaScript browser implementation + * of the Apache Thrift TJSONProtocol. + * @example + * var protocol = new Thrift.Protocol(transport); + */ +function TJSONProtocol(trans) { + this.tstack = []; + this.tpos = []; + this.trans = trans; +}; + +/** + * Thrift IDL type Id to string mapping. + * @readonly + * @see {@link Thrift.Type} + */ +TJSONProtocol.Type = {}; +TJSONProtocol.Type[Type.BOOL] = '"tf"'; +TJSONProtocol.Type[Type.BYTE] = '"i8"'; +TJSONProtocol.Type[Type.I16] = '"i16"'; +TJSONProtocol.Type[Type.I32] = '"i32"'; +TJSONProtocol.Type[Type.I64] = '"i64"'; +TJSONProtocol.Type[Type.DOUBLE] = '"dbl"'; +TJSONProtocol.Type[Type.STRUCT] = '"rec"'; +TJSONProtocol.Type[Type.STRING] = '"str"'; +TJSONProtocol.Type[Type.MAP] = '"map"'; +TJSONProtocol.Type[Type.LIST] = '"lst"'; +TJSONProtocol.Type[Type.SET] = '"set"'; + +/** + * Thrift IDL type string to Id mapping. + * @readonly + * @see {@link Thrift.Type} + */ +TJSONProtocol.RType = {}; +TJSONProtocol.RType.tf = Type.BOOL; +TJSONProtocol.RType.i8 = Type.BYTE; +TJSONProtocol.RType.i16 = Type.I16; +TJSONProtocol.RType.i32 = Type.I32; +TJSONProtocol.RType.i64 = Type.I64; +TJSONProtocol.RType.dbl = Type.DOUBLE; +TJSONProtocol.RType.rec = Type.STRUCT; +TJSONProtocol.RType.str = Type.STRING; +TJSONProtocol.RType.map = Type.MAP; +TJSONProtocol.RType.lst = Type.LIST; +TJSONProtocol.RType.set = Type.SET; + +/** + * The TJSONProtocol version number. + * @readonly + * @const {number} Version + * @memberof Thrift.Protocol + */ +TJSONProtocol.Version = 1; + +TJSONProtocol.prototype.flush = function() { + this.writeToTransportIfStackIsFlushable(); + return this.trans.flush(); +}; + +TJSONProtocol.prototype.writeToTransportIfStackIsFlushable = function() { + if (this.tstack.length === 1) { + this.trans.write(this.tstack.pop()); + } +}; + +/** + * Serializes the beginning of a Thrift RPC message. + * @param {string} name - The service method to call. + * @param {Thrift.MessageType} messageType - The type of method call. + * @param {number} seqid - The sequence number of this call (always 0 in Apache Thrift). + */ +TJSONProtocol.prototype.writeMessageBegin = function(name, messageType, seqid) { + this.tstack.push([TJSONProtocol.Version, '"' + name + '"', messageType, seqid]); +}; + +/** + * Serializes the end of a Thrift RPC message. + */ +TJSONProtocol.prototype.writeMessageEnd = function() { + var obj = this.tstack.pop(); + + this.wobj = this.tstack.pop(); + this.wobj.push(obj); + + this.wbuf = '[' + this.wobj.join(',') + ']'; + + // we assume there is nothing more to come so we write + this.trans.write(this.wbuf); +}; + +/** + * Serializes the beginning of a struct. + * @param {string} name - The name of the struct. + */ +TJSONProtocol.prototype.writeStructBegin = function(name) { + this.tpos.push(this.tstack.length); + this.tstack.push({}); +}; + +/** + * Serializes the end of a struct. + */ +TJSONProtocol.prototype.writeStructEnd = function() { + var p = this.tpos.pop(); + var struct = this.tstack[p]; + var str = '{'; + var first = true; + for (var key in struct) { + if (first) { + first = false; + } else { + str += ','; + } + + str += key + ':' + struct[key]; + } + + str += '}'; + this.tstack[p] = str; + + this.writeToTransportIfStackIsFlushable(); +}; + +/** + * Serializes the beginning of a struct field. + * @param {string} name - The name of the field. + * @param {Thrift.Protocol.Type} fieldType - The data type of the field. + * @param {number} fieldId - The field's unique identifier. + */ +TJSONProtocol.prototype.writeFieldBegin = function(name, fieldType, fieldId) { + this.tpos.push(this.tstack.length); + this.tstack.push({ 'fieldId': '"' + + fieldId + '"', 'fieldType': TJSONProtocol.Type[fieldType] + }); +}; + +/** + * Serializes the end of a field. + */ +TJSONProtocol.prototype.writeFieldEnd = function() { + var value = this.tstack.pop(); + var fieldInfo = this.tstack.pop(); + + if (':' + value === ":[object Object]") { + this.tstack[this.tstack.length - 1][fieldInfo.fieldId] = '{' + + fieldInfo.fieldType + ':' + JSON.stringify(value) + '}'; + } else { + this.tstack[this.tstack.length - 1][fieldInfo.fieldId] = '{' + + fieldInfo.fieldType + ':' + value + '}'; + } + this.tpos.pop(); + + this.writeToTransportIfStackIsFlushable(); +}; + +/** + * Serializes the end of the set of fields for a struct. + */ +TJSONProtocol.prototype.writeFieldStop = function() { +}; + +/** + * Serializes the beginning of a map collection. + * @param {Thrift.Type} keyType - The data type of the key. + * @param {Thrift.Type} valType - The data type of the value. + * @param {number} [size] - The number of elements in the map (ignored). + */ +TJSONProtocol.prototype.writeMapBegin = function(keyType, valType, size) { + //size is invalid, we'll set it on end. + this.tpos.push(this.tstack.length); + this.tstack.push([TJSONProtocol.Type[keyType], TJSONProtocol.Type[valType], 0]); +}; + +/** + * Serializes the end of a map. + */ +TJSONProtocol.prototype.writeMapEnd = function() { + var p = this.tpos.pop(); + + if (p == this.tstack.length) { + return; + } + + if ((this.tstack.length - p - 1) % 2 !== 0) { + this.tstack.push(''); + } + + var size = (this.tstack.length - p - 1) / 2; + + this.tstack[p][this.tstack[p].length - 1] = size; + + var map = '}'; + var first = true; + while (this.tstack.length > p + 1) { + var v = this.tstack.pop(); + var k = this.tstack.pop(); + if (first) { + first = false; + } else { + map = ',' + map; + } + + if (! isNaN(k)) { k = '"' + k + '"'; } //json "keys" need to be strings + map = k + ':' + v + map; + } + map = '{' + map; + + this.tstack[p].push(map); + this.tstack[p] = '[' + this.tstack[p].join(',') + ']'; + + this.writeToTransportIfStackIsFlushable(); +}; + +/** + * Serializes the beginning of a list collection. + * @param {Thrift.Type} elemType - The data type of the elements. + * @param {number} size - The number of elements in the list. + */ +TJSONProtocol.prototype.writeListBegin = function(elemType, size) { + this.tpos.push(this.tstack.length); + this.tstack.push([TJSONProtocol.Type[elemType], size]); +}; + +/** + * Serializes the end of a list. + */ +TJSONProtocol.prototype.writeListEnd = function() { + var p = this.tpos.pop(); + + while (this.tstack.length > p + 1) { + var tmpVal = this.tstack[p + 1]; + this.tstack.splice(p + 1, 1); + this.tstack[p].push(tmpVal); + } + + this.tstack[p] = '[' + this.tstack[p].join(',') + ']'; + + this.writeToTransportIfStackIsFlushable(); +}; + +/** + * Serializes the beginning of a set collection. + * @param {Thrift.Type} elemType - The data type of the elements. + * @param {number} size - The number of elements in the list. + */ +TJSONProtocol.prototype.writeSetBegin = function(elemType, size) { + this.tpos.push(this.tstack.length); + this.tstack.push([TJSONProtocol.Type[elemType], size]); +}; + +/** + * Serializes the end of a set. + */ +TJSONProtocol.prototype.writeSetEnd = function() { + var p = this.tpos.pop(); + + while (this.tstack.length > p + 1) { + var tmpVal = this.tstack[p + 1]; + this.tstack.splice(p + 1, 1); + this.tstack[p].push(tmpVal); + } + + this.tstack[p] = '[' + this.tstack[p].join(',') + ']'; + + this.writeToTransportIfStackIsFlushable(); +}; + +/** Serializes a boolean */ +TJSONProtocol.prototype.writeBool = function(bool) { + this.tstack.push(bool ? 1 : 0); +}; + +/** Serializes a number */ +TJSONProtocol.prototype.writeByte = function(byte) { + this.tstack.push(byte); +}; + +/** Serializes a number */ +TJSONProtocol.prototype.writeI16 = function(i16) { + this.tstack.push(i16); +}; + +/** Serializes a number */ +TJSONProtocol.prototype.writeI32 = function(i32) { + this.tstack.push(i32); +}; + +/** Serializes a number */ +TJSONProtocol.prototype.writeI64 = function(i64) { + if (i64 instanceof Int64) { + this.tstack.push(Int64Util.toDecimalString(i64)); + } else { + this.tstack.push(i64); + } +}; + +/** Serializes a number */ +TJSONProtocol.prototype.writeDouble = function(dub) { + this.tstack.push(dub); +}; + +/** Serializes a string */ +TJSONProtocol.prototype.writeString = function(arg) { + // We do not encode uri components for wire transfer: + if (arg === null) { + this.tstack.push(null); + } else { + if (typeof arg === 'string') { + var str = arg; + } else if (arg instanceof Buffer) { + var str = arg.toString('utf8'); + } else { + throw new Error('writeString called without a string/Buffer argument: ' + arg); + } + + // concat may be slower than building a byte buffer + var escapedString = ''; + for (var i = 0; i < str.length; i++) { + var ch = str.charAt(i); // a single double quote: " + if (ch === '\"') { + escapedString += '\\\"'; // write out as: \" + } else if (ch === '\\') { // a single backslash: \ + escapedString += '\\\\'; // write out as: \\ + /* Currently escaped forward slashes break TJSONProtocol. + * As it stands, we can simply pass forward slashes into + * our strings across the wire without being escaped. + * I think this is the protocol's bug, not thrift.js + * } else if(ch === '/') { // a single forward slash: / + * escapedString += '\\/'; // write out as \/ + * } + */ + } else if (ch === '\b') { // a single backspace: invisible + escapedString += '\\b'; // write out as: \b" + } else if (ch === '\f') { // a single formfeed: invisible + escapedString += '\\f'; // write out as: \f" + } else if (ch === '\n') { // a single newline: invisible + escapedString += '\\n'; // write out as: \n" + } else if (ch === '\r') { // a single return: invisible + escapedString += '\\r'; // write out as: \r" + } else if (ch === '\t') { // a single tab: invisible + escapedString += '\\t'; // write out as: \t" + } else { + escapedString += ch; // Else it need not be escaped + } + } + this.tstack.push('"' + escapedString + '"'); + } +}; + +/** Serializes a string */ +TJSONProtocol.prototype.writeBinary = function(arg) { + if (typeof arg === 'string') { + var buf = new Buffer(arg, 'binary'); + } else if (arg instanceof Buffer || + Object.prototype.toString.call(arg) == '[object Uint8Array]') { + var buf = arg; + } else { + throw new Error('writeBinary called without a string/Buffer argument: ' + arg); + } + this.tstack.push('"' + buf.toString('base64') + '"'); +}; + +/** + * @class + * @name AnonReadMessageBeginReturn + * @property {string} fname - The name of the service method. + * @property {Thrift.MessageType} mtype - The type of message call. + * @property {number} rseqid - The sequence number of the message (0 in Thrift RPC). + */ +/** + * Deserializes the beginning of a message. + * @returns {AnonReadMessageBeginReturn} + */ +TJSONProtocol.prototype.readMessageBegin = function() { + this.rstack = []; + this.rpos = []; + + //Borrow the inbound transport buffer and ensure data is present/consistent + var transBuf = this.trans.borrow(); + if (transBuf.readIndex >= transBuf.writeIndex) { + throw new InputBufferUnderrunError(); + } + var cursor = transBuf.readIndex; + + if (transBuf.buf[cursor] !== 0x5B) { //[ + throw new Error("Malformed JSON input, no opening bracket"); + } + + //Parse a single message (there may be several in the buffer) + // TODO: Handle characters using multiple code units + cursor++; + var openBracketCount = 1; + var inString = false; + for (; cursor < transBuf.writeIndex; cursor++) { + var chr = transBuf.buf[cursor]; + //we use hexa charcode here because data[i] returns an int and not a char + if (inString) { + if (chr === 0x22) { //" + inString = false; + } else if (chr === 0x5C) { //\ + //escaped character, skip + cursor += 1; + } + } else { + if (chr === 0x5B) { //[ + openBracketCount += 1; + } else if (chr === 0x5D) { //] + openBracketCount -= 1; + if (openBracketCount === 0) { + //end of json message detected + break; + } + } else if (chr === 0x22) { //" + inString = true; + } + } + } + + if (openBracketCount !== 0) { + // Missing closing bracket. Can be buffer underrun. + throw new InputBufferUnderrunError(); + } + + //Reconstitute the JSON object and conume the necessary bytes + this.robj = json_parse(transBuf.buf.slice(transBuf.readIndex, cursor+1).toString()); + this.trans.consume(cursor + 1 - transBuf.readIndex); + + //Verify the protocol version + var version = this.robj.shift(); + if (version != TJSONProtocol.Version) { + throw new Error('Wrong thrift protocol version: ' + version); + } + + //Objectify the thrift message {name/type/sequence-number} for return + // and then save the JSON object in rstack + var r = {}; + r.fname = this.robj.shift(); + r.mtype = this.robj.shift(); + r.rseqid = this.robj.shift(); + this.rstack.push(this.robj.shift()); + return r; +}; + +/** Deserializes the end of a message. */ +TJSONProtocol.prototype.readMessageEnd = function() { +}; + +/** + * Deserializes the beginning of a struct. + * @param {string} [name] - The name of the struct (ignored) + * @returns {object} - An object with an empty string fname property + */ +TJSONProtocol.prototype.readStructBegin = function() { + var r = {}; + r.fname = ''; + + //incase this is an array of structs + if (this.rstack[this.rstack.length - 1] instanceof Array) { + this.rstack.push(this.rstack[this.rstack.length - 1].shift()); + } + + return r; +}; + +/** Deserializes the end of a struct. */ +TJSONProtocol.prototype.readStructEnd = function() { + this.rstack.pop(); +}; + +/** + * @class + * @name AnonReadFieldBeginReturn + * @property {string} fname - The name of the field (always ''). + * @property {Thrift.Type} ftype - The data type of the field. + * @property {number} fid - The unique identifier of the field. + */ +/** + * Deserializes the beginning of a field. + * @returns {AnonReadFieldBeginReturn} + */ +TJSONProtocol.prototype.readFieldBegin = function() { + var r = {}; + + var fid = -1; + var ftype = Type.STOP; + + //get a fieldId + for (var f in (this.rstack[this.rstack.length - 1])) { + if (f === null) { + continue; + } + + fid = parseInt(f, 10); + this.rpos.push(this.rstack.length); + + var field = this.rstack[this.rstack.length - 1][fid]; + + //remove so we don't see it again + delete this.rstack[this.rstack.length - 1][fid]; + + this.rstack.push(field); + + break; + } + + if (fid != -1) { + //should only be 1 of these but this is the only + //way to match a key + for (var i in (this.rstack[this.rstack.length - 1])) { + if (TJSONProtocol.RType[i] === null) { + continue; + } + + ftype = TJSONProtocol.RType[i]; + this.rstack[this.rstack.length - 1] = this.rstack[this.rstack.length - 1][i]; + } + } + + r.fname = ''; + r.ftype = ftype; + r.fid = fid; + + return r; +}; + +/** Deserializes the end of a field. */ +TJSONProtocol.prototype.readFieldEnd = function() { + var pos = this.rpos.pop(); + + //get back to the right place in the stack + while (this.rstack.length > pos) { + this.rstack.pop(); + } +}; + +/** + * @class + * @name AnonReadMapBeginReturn + * @property {Thrift.Type} ktype - The data type of the key. + * @property {Thrift.Type} vtype - The data type of the value. + * @property {number} size - The number of elements in the map. + */ +/** + * Deserializes the beginning of a map. + * @returns {AnonReadMapBeginReturn} + */ +TJSONProtocol.prototype.readMapBegin = function() { + var map = this.rstack.pop(); + var first = map.shift(); + if (first instanceof Array) { + this.rstack.push(map); + map = first; + first = map.shift(); + } + + var r = {}; + r.ktype = TJSONProtocol.RType[first]; + r.vtype = TJSONProtocol.RType[map.shift()]; + r.size = map.shift(); + + + this.rpos.push(this.rstack.length); + this.rstack.push(map.shift()); + + return r; +}; + +/** Deserializes the end of a map. */ +TJSONProtocol.prototype.readMapEnd = function() { + this.readFieldEnd(); +}; + +/** + * @class + * @name AnonReadColBeginReturn + * @property {Thrift.Type} etype - The data type of the element. + * @property {number} size - The number of elements in the collection. + */ +/** + * Deserializes the beginning of a list. + * @returns {AnonReadColBeginReturn} + */ +TJSONProtocol.prototype.readListBegin = function() { + var list = this.rstack[this.rstack.length - 1]; + + var r = {}; + r.etype = TJSONProtocol.RType[list.shift()]; + r.size = list.shift(); + + this.rpos.push(this.rstack.length); + this.rstack.push(list.shift()); + + return r; +}; + +/** Deserializes the end of a list. */ +TJSONProtocol.prototype.readListEnd = function() { + var pos = this.rpos.pop() - 2; + var st = this.rstack; + st.pop(); + if (st instanceof Array && st.length > pos && st[pos].length > 0) { + st.push(st[pos].shift()); + } +}; + +/** + * Deserializes the beginning of a set. + * @returns {AnonReadColBeginReturn} + */ +TJSONProtocol.prototype.readSetBegin = function() { + return this.readListBegin(); +}; + +/** Deserializes the end of a set. */ +TJSONProtocol.prototype.readSetEnd = function() { + return this.readListEnd(); +}; + +TJSONProtocol.prototype.readBool = function() { + return this.readValue() == '1'; +}; + +TJSONProtocol.prototype.readByte = function() { + return this.readI32(); +}; + +TJSONProtocol.prototype.readI16 = function() { + return this.readI32(); +}; + +TJSONProtocol.prototype.readI32 = function(f) { + return +this.readValue(); +} + +/** Returns the next value found in the protocol buffer */ +TJSONProtocol.prototype.readValue = function(f) { + if (f === undefined) { + f = this.rstack[this.rstack.length - 1]; + } + + var r = {}; + + if (f instanceof Array) { + if (f.length === 0) { + r.value = undefined; + } else { + r.value = f.shift(); + } + } else if (!(f instanceof Int64) && f instanceof Object) { + for (var i in f) { + if (i === null) { + continue; + } + this.rstack.push(f[i]); + delete f[i]; + + r.value = i; + break; + } + } else { + r.value = f; + this.rstack.pop(); + } + + return r.value; +}; + +TJSONProtocol.prototype.readI64 = function() { + var n = this.readValue() + if (typeof n === 'string') { + // Assuming no one is sending in 1.11111e+33 format + return Int64Util.fromDecimalString(n); + } else { + return new Int64(n); + } +}; + +TJSONProtocol.prototype.readDouble = function() { + return this.readI32(); +}; + +TJSONProtocol.prototype.readBinary = function() { + return new Buffer(this.readValue(), 'base64'); +}; + +TJSONProtocol.prototype.readString = function() { + return this.readValue(); +}; + +/** + * Returns the underlying transport. + * @readonly + * @returns {Thrift.Transport} The underlying transport. + */ +TJSONProtocol.prototype.getTransport = function() { + return this.trans; +}; + +/** + * Method to arbitrarily skip over data + */ +TJSONProtocol.prototype.skip = function(type) { + switch (type) { + case Type.BOOL: + this.readBool(); + break; + case Type.BYTE: + this.readByte(); + break; + case Type.I16: + this.readI16(); + break; + case Type.I32: + this.readI32(); + break; + case Type.I64: + this.readI64(); + break; + case Type.DOUBLE: + this.readDouble(); + break; + case Type.STRING: + this.readString(); + break; + case Type.STRUCT: + this.readStructBegin(); + while (true) { + var r = this.readFieldBegin(); + if (r.ftype === Type.STOP) { + break; + } + this.skip(r.ftype); + this.readFieldEnd(); + } + this.readStructEnd(); + break; + case Type.MAP: + var mapBegin = this.readMapBegin(); + for (var i = 0; i < mapBegin.size; ++i) { + this.skip(mapBegin.ktype); + this.skip(mapBegin.vtype); + } + this.readMapEnd(); + break; + case Type.SET: + var setBegin = this.readSetBegin(); + for (var i2 = 0; i2 < setBegin.size; ++i2) { + this.skip(setBegin.etype); + } + this.readSetEnd(); + break; + case Type.LIST: + var listBegin = this.readListBegin(); + for (var i3 = 0; i3 < listBegin.size; ++i3) { + this.skip(listBegin.etype); + } + this.readListEnd(); + break; + default: + throw new Error("Invalid type: " + type); + } +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/log.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/log.js new file mode 100644 index 000000000..053e81361 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/log.js @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var util = require('util'); + +var disabled = function () {}; +var logFunc = console.log; +var logLevel = 'error'; // default level + +function factory(level) { + return function () { + // better use spread syntax, but due to compatibility, + // use legacy method here. + var args = ['thrift: [' + level + '] '].concat(Array.from(arguments)); + return logFunc(util.format.apply(null, args)); + }; +} + +var trace = disabled; +var debug = disabled; +var error = disabled; +var warning = disabled; +var info = disabled; + +exports.setLogFunc = function (func) { + logFunc = func; +}; + +var setLogLevel = exports.setLogLevel = function (level) { + trace = debug = error = warning = info = disabled; + logLevel = level; + switch (logLevel) { + case 'trace': + trace = factory('TRACE'); + case 'debug': + debug = factory('DEBUG'); + case 'error': + error = factory('ERROR'); + case 'warning': + warning = factory('WARN'); + case 'info': + info = factory('INFO'); + } +}; + +// set default +setLogLevel(logLevel); + +exports.getLogLevel = function () { + return logLevel; +}; + +exports.trace = function () { + return trace.apply(null, arguments); +}; + +exports.debug = function () { + return debug.apply(null, arguments); +}; + +exports.error = function () { + return error.apply(null, arguments); +}; + +exports.warning = function () { + return warning.apply(null, arguments); +}; + +exports.info = function () { + return info.apply(null, arguments); +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_processor.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_processor.js new file mode 100644 index 000000000..67b62f7a2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_processor.js @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var Thrift = require('./thrift'); + +exports.MultiplexedProcessor = MultiplexedProcessor; + +function MultiplexedProcessor(stream, options) { + this.services = {}; +}; + +MultiplexedProcessor.prototype.registerProcessor = function(name, handler) { + this.services[name] = handler; +}; + +MultiplexedProcessor.prototype.process = function(inp, out) { + var begin = inp.readMessageBegin(); + + if (begin.mtype != Thrift.MessageType.CALL && begin.mtype != Thrift.MessageType.ONEWAY) { + throw new Thrift.TException('TMultiplexedProcessor: Unexpected message type'); + } + + var p = begin.fname.split(':'); + var sname = p[0]; + var fname = p[1]; + + if (! (sname in this.services)) { + throw new Thrift.TException('TMultiplexedProcessor: Unknown service: ' + sname); + } + + //construct a proxy object which stubs the readMessageBegin + //for the service + var inpProxy = {}; + + for (var attr in inp) { + inpProxy[attr] = inp[attr]; + } + + inpProxy.readMessageBegin = function() { + return { + fname: fname, + mtype: begin.mtype, + rseqid: begin.rseqid + }; + }; + + this.services[sname].process(inpProxy, out); +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_protocol.js new file mode 100644 index 000000000..d078aa226 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/multiplexed_protocol.js @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var Thrift = require('./thrift'); + +exports.Multiplexer = Multiplexer; + +function Wrapper(serviceName, protocol, connection) { + + function MultiplexProtocol(trans, strictRead, strictWrite) { + protocol.call(this, trans, strictRead, strictWrite); + }; + + util.inherits(MultiplexProtocol, protocol); + + MultiplexProtocol.prototype.writeMessageBegin = function(name, type, seqid) { + if (type == Thrift.MessageType.CALL || type == Thrift.MessageType.ONEWAY) { + connection.seqId2Service[seqid] = serviceName; + MultiplexProtocol.super_.prototype.writeMessageBegin.call(this, + serviceName + ":" + name, + type, + seqid); + } else { + MultiplexProtocol.super_.prototype.writeMessageBegin.call(this, name, type, seqid); + } + }; + + return MultiplexProtocol; +}; + +function Multiplexer() { + this.seqid = 0; +}; + +Multiplexer.prototype.createClient = function(serviceName, ServiceClient, connection) { + if (ServiceClient.Client) { + ServiceClient = ServiceClient.Client; + } + var writeCb = function(buf, seqid) { + connection.write(buf,seqid); + }; + var transport = new connection.transport(undefined, writeCb); + var protocolWrapper = new Wrapper(serviceName, connection.protocol, connection); + var client = new ServiceClient(transport, protocolWrapper); + var self = this; + client.new_seqid = function() { + self.seqid += 1; + return self.seqid; + }; + + if (typeof connection.client !== 'object') { + connection.client = {}; + } + connection.client[serviceName] = client; + + return client; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/protocol.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/protocol.js new file mode 100644 index 000000000..a70ebe287 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/protocol.js @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports.TBinaryProtocol = require('./binary_protocol'); +module.exports.TCompactProtocol = require('./compact_protocol'); +module.exports.TJSONProtocol = require('./json_protocol'); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/server.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/server.js new file mode 100644 index 000000000..16b74eaaf --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/server.js @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var constants = require('constants'); +var net = require('net'); +var tls = require('tls'); + +var TBufferedTransport = require('./buffered_transport'); +var TBinaryProtocol = require('./binary_protocol'); +var THeaderProtocol = require('./header_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +/** + * Create a Thrift server which can serve one or multiple services. + * @param {object} processor - A normal or multiplexedProcessor (must + * be preconstructed with the desired handler). + * @param {ServerOptions} options - Optional additional server configuration. + * @returns {object} - The Apache Thrift Multiplex Server. + */ +exports.createMultiplexServer = function(processor, options) { + var transport = (options && options.transport) ? options.transport : TBufferedTransport; + var protocol = (options && options.protocol) ? options.protocol : TBinaryProtocol; + + function serverImpl(stream) { + var self = this; + stream.on('error', function(err) { + self.emit('error', err); + }); + stream.on('data', transport.receiver(function(transportWithData) { + var input = new protocol(transportWithData); + var outputCb = function(buf) { + try { + stream.write(buf); + } catch (err) { + self.emit('error', err); + stream.end(); + } + }; + + var output = new protocol(new transport(undefined, outputCb)); + // Read and write need to be performed on the same transport + // for THeaderProtocol because we should only respond with + // headers if the request contains headers + if (protocol === THeaderProtocol) { + output = input; + output.trans.onFlush = outputCb; + } + + try { + do { + processor.process(input, output); + transportWithData.commitPosition(); + } while (true); + } catch (err) { + if (err instanceof InputBufferUnderrunError) { + //The last data in the buffer was not a complete message, wait for the rest + transportWithData.rollbackPosition(); + } + else if (err.message === "Invalid type: undefined") { + //No more data in the buffer + //This trap is a bit hackish + //The next step to improve the node behavior here is to have + // the compiler generated process method throw a more explicit + // error when the network buffer is empty (regardles of the + // protocol/transport stack in use) and replace this heuristic. + // Also transports should probably not force upper layers to + // manage their buffer positions (i.e. rollbackPosition() and + // commitPosition() should be eliminated in lieu of a transport + // encapsulated buffer management strategy.) + transportWithData.rollbackPosition(); + } + else { + //Unexpected error + self.emit('error', err); + stream.end(); + } + } + })); + + stream.on('end', function() { + stream.end(); + }); + } + + if (options && options.tls) { + if (!('secureProtocol' in options.tls) && !('secureOptions' in options.tls)) { + options.tls.secureProtocol = "SSLv23_method"; + options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3; + } + return tls.createServer(options.tls, serverImpl); + } else { + return net.createServer(serverImpl); + } +}; + +/** + * Create a single service Apache Thrift server. + * @param {object} processor - A service class or processor function. + * @param {ServerOptions} options - Optional additional server configuration. + * @returns {object} - The Apache Thrift Multiplex Server. + */ +exports.createServer = function(processor, handler, options) { + if (processor.Processor) { + processor = processor.Processor; + } + return exports.createMultiplexServer(new processor(handler), options); +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/thrift.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/thrift.js new file mode 100644 index 000000000..f2b289672 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/thrift.js @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); + +var Type = exports.Type = { + STOP: 0, + VOID: 1, + BOOL: 2, + BYTE: 3, + I08: 3, + DOUBLE: 4, + I16: 6, + I32: 8, + I64: 10, + STRING: 11, + UTF7: 11, + STRUCT: 12, + MAP: 13, + SET: 14, + LIST: 15, + UTF8: 16, + UTF16: 17 +}; + +exports.MessageType = { + CALL: 1, + REPLY: 2, + EXCEPTION: 3, + ONEWAY: 4 +}; + +exports.TException = TException; + +function TException(message) { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.message = message; +}; +util.inherits(TException, Error); + +var TApplicationExceptionType = exports.TApplicationExceptionType = { + UNKNOWN: 0, + UNKNOWN_METHOD: 1, + INVALID_MESSAGE_TYPE: 2, + WRONG_METHOD_NAME: 3, + BAD_SEQUENCE_ID: 4, + MISSING_RESULT: 5, + INTERNAL_ERROR: 6, + PROTOCOL_ERROR: 7, + INVALID_TRANSFORM: 8, + INVALID_PROTOCOL: 9, + UNSUPPORTED_CLIENT_TYPE: 10 +}; + +exports.TApplicationException = TApplicationException; + +function TApplicationException(type, message) { + TException.call(this); + Error.captureStackTrace(this, this.constructor); + this.type = type || TApplicationExceptionType.UNKNOWN; + this.name = this.constructor.name; + this.message = message; +}; +util.inherits(TApplicationException, TException); + +TApplicationException.prototype.read = function(input) { + var ftype; + var ret = input.readStructBegin('TApplicationException'); + + while(1){ + ret = input.readFieldBegin(); + if(ret.ftype == Type.STOP) + break; + + switch(ret.fid){ + case 1: + if( ret.ftype == Type.STRING ){ + ret = input.readString(); + this.message = ret; + } else { + ret = input.skip(ret.ftype); + } + break; + case 2: + if( ret.ftype == Type.I32 ){ + ret = input.readI32(); + this.type = ret; + } else { + ret = input.skip(ret.ftype); + } + break; + default: + ret = input.skip(ret.ftype); + break; + } + input.readFieldEnd(); + } + input.readStructEnd(); +}; + +TApplicationException.prototype.write = function(output){ + output.writeStructBegin('TApplicationException'); + + if (this.message) { + output.writeFieldBegin('message', Type.STRING, 1); + output.writeString(this.message); + output.writeFieldEnd(); + } + + if (this.code) { + output.writeFieldBegin('type', Type.I32, 2); + output.writeI32(this.code); + output.writeFieldEnd(); + } + + output.writeFieldStop(); + output.writeStructEnd(); +}; + +var TProtocolExceptionType = exports.TProtocolExceptionType = { + UNKNOWN: 0, + INVALID_DATA: 1, + NEGATIVE_SIZE: 2, + SIZE_LIMIT: 3, + BAD_VERSION: 4, + NOT_IMPLEMENTED: 5, + DEPTH_LIMIT: 6 +}; + + +exports.TProtocolException = TProtocolException; + +function TProtocolException(type, message) { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.type = type; + this.message = message; +}; +util.inherits(TProtocolException, Error); + +exports.objectLength = function(obj) { + return Object.keys(obj).length; +}; + +exports.inherits = function(constructor, superConstructor) { + util.inherits(constructor, superConstructor); +}; + +var copyList, copyMap; + +copyList = function(lst, types) { + + if (!lst) {return lst; } + + var type; + + if (types.shift === undefined) { + type = types; + } + else { + type = types[0]; + } + var Type = type; + + var len = lst.length, result = [], i, val; + for (i = 0; i < len; i++) { + val = lst[i]; + if (type === null) { + result.push(val); + } + else if (type === copyMap || type === copyList) { + result.push(type(val, types.slice(1))); + } + else { + result.push(new Type(val)); + } + } + return result; +}; + +copyMap = function(obj, types){ + + if (!obj) {return obj; } + + var type; + + if (types.shift === undefined) { + type = types; + } + else { + type = types[0]; + } + var Type = type; + + var result = {}, val; + for(var prop in obj) { + if(obj.hasOwnProperty(prop)) { + val = obj[prop]; + if (type === null) { + result[prop] = val; + } + else if (type === copyMap || type === copyList) { + result[prop] = type(val, types.slice(1)); + } + else { + result[prop] = new Type(val); + } + } + } + return result; +}; + +module.exports.copyMap = copyMap; +module.exports.copyList = copyList; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/transport.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/transport.js new file mode 100644 index 000000000..59daa987d --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/transport.js @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports.TBufferedTransport = require('./buffered_transport'); +module.exports.TFramedTransport = require('./framed_transport'); +module.exports.InputBufferUnderrunError = require('./input_buffer_underrun_error'); diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/web_server.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/web_server.js new file mode 100644 index 000000000..a33f47aed --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/web_server.js @@ -0,0 +1,567 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var http = require('http'); +var https = require('https'); +var url = require("url"); +var path = require("path"); +var fs = require("fs"); +var crypto = require("crypto"); +var log = require('./log'); + +var MultiplexedProcessor = require('./multiplexed_processor').MultiplexedProcessor; + +var TBufferedTransport = require('./buffered_transport'); +var TBinaryProtocol = require('./binary_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +// WSFrame constructor and prototype +///////////////////////////////////////////////////////////////////// + +/** Apache Thrift RPC Web Socket Transport + * Frame layout conforming to RFC 6455 circa 12/2011 + * + * Theoretical frame size limit is 4GB*4GB, however the Node Buffer + * limit is 1GB as of v0.10. The frame length encoding is also + * configured for a max of 4GB presently and needs to be adjusted + * if Node/Browsers become capabile of > 4GB frames. + * + * - FIN is 1 if the message is complete + * - RSV1/2/3 are always 0 + * - Opcode is 1(TEXT) for TJSONProtocol and 2(BIN) for TBinaryProtocol + * - Mask Present bit is 1 sending to-server and 0 sending to-client + * - Payload Len: + * + If < 126: then represented directly + * + If >=126: but within range of an unsigned 16 bit integer + * then Payload Len is 126 and the two following bytes store + * the length + * + Else: Payload Len is 127 and the following 8 bytes store the + * length as an unsigned 64 bit integer + * - Masking key is a 32 bit key only present when sending to the server + * - Payload follows the masking key or length + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|M| Payload len | Extended payload length | + * |I|S|S|S| (4) |A| (7) | (16/64) | + * |N|V|V|V| |S| | (if payload len==126/127) | + * | |1|2|3| |K| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | |Masking-key, if MASK set to 1 | + * +-------------------------------+-------------------------------+ + * | Masking-key (continued) | Payload Data | + * +-------------------------------- - - - - - - - - - - - - - - - + + * : Payload Data continued ... : + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * | Payload Data continued ... | + * +---------------------------------------------------------------+ + */ +var wsFrame = { + /** Encodes a WebSocket frame + * + * @param {Buffer} data - The raw data to encode + * @param {Buffer} mask - The mask to apply when sending to server, null for no mask + * @param {Boolean} binEncoding - True for binary encoding, false for text encoding + * @returns {Buffer} - The WebSocket frame, ready to send + */ + encode: function(data, mask, binEncoding) { + var frame = new Buffer(wsFrame.frameSizeFromData(data, mask)); + //Byte 0 - FIN & OPCODE + frame[0] = wsFrame.fin.FIN + + (binEncoding ? wsFrame.frameOpCodes.BIN : wsFrame.frameOpCodes.TEXT); + //Byte 1 or 1-3 or 1-9 - MASK FLAG & SIZE + var payloadOffset = 2; + if (data.length < 0x7E) { + frame[1] = data.length + (mask ? wsFrame.mask.TO_SERVER : wsFrame.mask.TO_CLIENT); + } else if (data.length < 0xFFFF) { + frame[1] = 0x7E + (mask ? wsFrame.mask.TO_SERVER : wsFrame.mask.TO_CLIENT); + frame.writeUInt16BE(data.length, 2, true); + payloadOffset = 4; + } else { + frame[1] = 0x7F + (mask ? wsFrame.mask.TO_SERVER : wsFrame.mask.TO_CLIENT); + frame.writeUInt32BE(0, 2, true); + frame.writeUInt32BE(data.length, 6, true); + payloadOffset = 10; + } + //MASK + if (mask) { + mask.copy(frame, payloadOffset, 0, 4); + payloadOffset += 4; + } + //Payload + data.copy(frame, payloadOffset); + if (mask) { + wsFrame.applyMask(frame.slice(payloadOffset), frame.slice(payloadOffset-4,payloadOffset)); + } + return frame; + }, + + /** + * @class + * @name WSDecodeResult + * @property {Buffer} data - The decoded data for the first ATRPC message + * @property {Buffer} mask - The frame mask + * @property {Boolean} binEncoding - True if binary (TBinaryProtocol), + * False if text (TJSONProtocol) + * @property {Buffer} nextFrame - Multiple ATRPC messages may be sent in a + * single WebSocket frame, this Buffer contains + * any bytes remaining to be decoded + * @property {Boolean} FIN - True is the message is complete + */ + + /** Decodes a WebSocket frame + * + * @param {Buffer} frame - The raw inbound frame, if this is a continuation + * frame it must have a mask property with the mask. + * @returns {WSDecodeResult} - The decoded payload + * + * @see {@link WSDecodeResult} + */ + decode: function(frame) { + var result = { + data: null, + mask: null, + binEncoding: false, + nextFrame: null, + FIN: true + }; + + //Byte 0 - FIN & OPCODE + if (wsFrame.fin.FIN != (frame[0] & wsFrame.fin.FIN)) { + result.FIN = false; + } + result.binEncoding = (wsFrame.frameOpCodes.BIN == (frame[0] & wsFrame.frameOpCodes.BIN)); + //Byte 1 or 1-3 or 1-9 - SIZE + var lenByte = (frame[1] & 0x0000007F); + var len = lenByte; + var dataOffset = 2; + if (lenByte == 0x7E) { + len = frame.readUInt16BE(2); + dataOffset = 4; + } else if (lenByte == 0x7F) { + len = frame.readUInt32BE(6); + dataOffset = 10; + } + //MASK + if (wsFrame.mask.TO_SERVER == (frame[1] & wsFrame.mask.TO_SERVER)) { + result.mask = new Buffer(4); + frame.copy(result.mask, 0, dataOffset, dataOffset + 4); + dataOffset += 4; + } + //Payload + result.data = new Buffer(len); + frame.copy(result.data, 0, dataOffset, dataOffset+len); + if (result.mask) { + wsFrame.applyMask(result.data, result.mask); + } + //Next Frame + if (frame.length > dataOffset+len) { + result.nextFrame = new Buffer(frame.length - (dataOffset+len)); + frame.copy(result.nextFrame, 0, dataOffset+len, frame.length); + } + //Don't forward control frames + if (frame[0] & wsFrame.frameOpCodes.FINCTRL) { + result.data = null; + } + + return result; + }, + + /** Masks/Unmasks data + * + * @param {Buffer} data - data to mask/unmask in place + * @param {Buffer} mask - the mask + */ + applyMask: function(data, mask){ + //TODO: look into xoring words at a time + var dataLen = data.length; + var maskLen = mask.length; + for (var i = 0; i < dataLen; i++) { + data[i] = data[i] ^ mask[i%maskLen]; + } + }, + + /** Computes frame size on the wire from data to be sent + * + * @param {Buffer} data - data.length is the assumed payload size + * @param {Boolean} mask - true if a mask will be sent (TO_SERVER) + */ + frameSizeFromData: function(data, mask) { + var headerSize = 10; + if (data.length < 0x7E) { + headerSize = 2; + } else if (data.length < 0xFFFF) { + headerSize = 4; + } + return headerSize + data.length + (mask ? 4 : 0); + }, + + frameOpCodes: { + CONT: 0x00, + TEXT: 0x01, + BIN: 0x02, + CTRL: 0x80 + }, + + mask: { + TO_SERVER: 0x80, + TO_CLIENT: 0x00 + }, + + fin: { + CONT: 0x00, + FIN: 0x80 + } +}; + + +// createWebServer constructor and options +///////////////////////////////////////////////////////////////////// + +/** + * @class + * @name ServerOptions + * @property {array} cors - Array of CORS origin strings to permit requests from. + * @property {string} files - Path to serve static files from, if absent or "" + * static file service is disabled. + * @property {object} headers - An object hash mapping header strings to header value + * strings, these headers are transmitted in response to + * static file GET operations. + * @property {object} services - An object hash mapping service URI strings + * to ServiceOptions objects + * @property {object} tls - Node.js TLS options (see: nodejs.org/api/tls.html), + * if not present or null regular http is used, + * at least a key and a cert must be defined to use SSL/TLS + * @see {@link ServiceOptions} + */ + +/** + * @class + * @name ServiceOptions + * @property {object} transport - The layered transport to use (defaults + * to TBufferedTransport). + * @property {object} protocol - The serialization Protocol to use (defaults to + * TBinaryProtocol). + * @property {object} processor - The Thrift Service class/processor generated + * by the IDL Compiler for the service (the "cls" + * key can also be used for this attribute). + * @property {object} handler - The handler methods for the Thrift Service. + */ + +/** + * Create a Thrift server which can serve static files and/or one or + * more Thrift Services. + * @param {ServerOptions} options - The server configuration. + * @returns {object} - The Apache Thrift Web Server. + */ +exports.createWebServer = function(options) { + var baseDir = options.files; + var contentTypesByExtension = { + '.txt': 'text/plain', + '.html': 'text/html', + '.css': 'text/css', + '.xml': 'application/xml', + '.json': 'application/json', + '.js': 'application/javascript', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.png': 'image/png', + '.svg': 'image/svg+xml' + }; + + //Setup all of the services + var services = options.services; + for (var uri in services) { + var svcObj = services[uri]; + + //Setup the processor + if (svcObj.processor instanceof MultiplexedProcessor) { + //Multiplex processors have pre embedded processor/handler pairs, save as is + svcObj.processor = svcObj.processor; + } else { + //For historical reasons Node.js supports processors passed in directly or via the + // IDL Compiler generated class housing the processor. Also, the options property + // for a Processor has been called both cls and processor at different times. We + // support any of the four possibilities here. + var processor = (svcObj.processor) ? (svcObj.processor.Processor || svcObj.processor) : + (svcObj.cls.Processor || svcObj.cls); + //Processors can be supplied as constructed objects with handlers already embedded, + // if a handler is provided we construct a new processor, if not we use the processor + // object directly + if (svcObj.handler) { + svcObj.processor = new processor(svcObj.handler); + } else { + svcObj.processor = processor; + } + } + svcObj.transport = svcObj.transport ? svcObj.transport : TBufferedTransport; + svcObj.protocol = svcObj.protocol ? svcObj.protocol : TBinaryProtocol; + } + + //Verify CORS requirements + function VerifyCORSAndSetHeaders(request, response) { + if (request.headers.origin && options.cors) { + if (options.cors["*"] || options.cors[request.headers.origin]) { + //Allow, origin allowed + response.setHeader("access-control-allow-origin", request.headers.origin); + response.setHeader("access-control-allow-methods", "GET, POST, OPTIONS"); + response.setHeader("access-control-allow-headers", "content-type, accept"); + response.setHeader("access-control-max-age", "60"); + return true; + } else { + //Disallow, origin denied + return false; + } + } + //Allow, CORS is not in use + return true; + } + + + //Handle OPTIONS method (CORS) + /////////////////////////////////////////////////// + function processOptions(request, response) { + if (VerifyCORSAndSetHeaders(request, response)) { + response.writeHead("204", "No Content", {"content-length": 0}); + } else { + response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {}); + } + response.end(); + } + + + //Handle POST methods (TXHRTransport) + /////////////////////////////////////////////////// + function processPost(request, response) { + //Lookup service + var uri = url.parse(request.url).pathname; + var svc = services[uri]; + if (!svc) { + response.writeHead("403", "No Apache Thrift Service at " + uri, {}); + response.end(); + return; + } + + //Verify CORS requirements + if (!VerifyCORSAndSetHeaders(request, response)) { + response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {}); + response.end(); + return; + } + + //Process XHR payload + request.on('data', svc.transport.receiver(function(transportWithData) { + var input = new svc.protocol(transportWithData); + var output = new svc.protocol(new svc.transport(undefined, function(buf) { + try { + response.writeHead(200); + response.end(buf); + } catch (err) { + response.writeHead(500); + response.end(); + } + })); + + try { + svc.processor.process(input, output); + transportWithData.commitPosition(); + } catch (err) { + if (err instanceof InputBufferUnderrunError) { + transportWithData.rollbackPosition(); + } else { + response.writeHead(500); + response.end(); + } + } + })); + } + + + //Handle GET methods (Static Page Server) + /////////////////////////////////////////////////// + function processGet(request, response) { + //Undefined or empty base directory means do not serve static files + if (!baseDir || "" === baseDir) { + response.writeHead(404); + response.end(); + return; + } + + //Verify CORS requirements + if (!VerifyCORSAndSetHeaders(request, response)) { + response.writeHead("403", "Origin " + request.headers.origin + " not allowed", {}); + response.end(); + return; + } + + //Locate the file requested and send it + var uri = url.parse(request.url).pathname; + var filename = path.resolve(path.join(baseDir, uri)); + + //Ensure the basedir path is not able to be escaped + if (filename.indexOf(baseDir) != 0) { + response.writeHead(400, "Invalid request path", {}); + response.end(); + return; + } + + fs.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) { + filename += '/index.html'; + } + + fs.readFile(filename, "binary", function(err, file) { + if (err) { + response.writeHead(500); + response.end(err + "\n"); + return; + } + var headers = {}; + var contentType = contentTypesByExtension[path.extname(filename)]; + if (contentType) { + headers["Content-Type"] = contentType; + } + for (var k in options.headers) { + headers[k] = options.headers[k]; + } + response.writeHead(200, headers); + response.write(file, "binary"); + response.end(); + }); + }); + } + + + //Handle WebSocket calls (TWebSocketTransport) + /////////////////////////////////////////////////// + function processWS(data, socket, svc, binEncoding) { + svc.transport.receiver(function(transportWithData) { + var input = new svc.protocol(transportWithData); + var output = new svc.protocol(new svc.transport(undefined, function(buf) { + try { + var frame = wsFrame.encode(buf, null, binEncoding); + socket.write(frame); + } catch (err) { + //TODO: Add better error processing + } + })); + + try { + svc.processor.process(input, output); + transportWithData.commitPosition(); + } + catch (err) { + if (err instanceof InputBufferUnderrunError) { + transportWithData.rollbackPosition(); + } + else { + //TODO: Add better error processing + } + } + })(data); + } + + //Create the server (HTTP or HTTPS) + var server = null; + if (options.tls) { + server = https.createServer(options.tls); + } else { + server = http.createServer(); + } + + //Wire up listeners for upgrade(to WebSocket) & request methods for: + // - GET static files, + // - POST XHR Thrift services + // - OPTIONS CORS requests + server.on('request', function(request, response) { + if (request.method === 'POST') { + processPost(request, response); + } else if (request.method === 'GET') { + processGet(request, response); + } else if (request.method === 'OPTIONS') { + processOptions(request, response); + } else { + response.writeHead(500); + response.end(); + } + }).on('upgrade', function(request, socket, head) { + //Lookup service + var svc; + try { + svc = services[Object.keys(services)[0]]; + } catch(e) { + socket.write("HTTP/1.1 403 No Apache Thrift Service available\r\n\r\n"); + return; + } + //Perform upgrade + var hash = crypto.createHash("sha1"); + hash.update(request.headers['sec-websocket-key'] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + socket.write("HTTP/1.1 101 Switching Protocols\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Accept: " + hash.digest("base64") + "\r\n" + + "Sec-WebSocket-Origin: " + request.headers.origin + "\r\n" + + "Sec-WebSocket-Location: ws://" + request.headers.host + request.url + "\r\n" + + "\r\n"); + //Handle WebSocket traffic + var data = null; + socket.on('data', function(frame) { + try { + while (frame) { + var result = wsFrame.decode(frame); + //Prepend any existing decoded data + if (data) { + if (result.data) { + var newData = new Buffer(data.length + result.data.length); + data.copy(newData); + result.data.copy(newData, data.length); + result.data = newData; + } else { + result.data = data; + } + data = null; + } + //If this completes a message process it + if (result.FIN) { + processWS(result.data, socket, svc, result.binEncoding); + } else { + data = result.data; + } + //Prepare next frame for decoding (if any) + frame = result.nextFrame; + } + } catch(e) { + log.error('TWebSocketTransport Exception: ' + e); + socket.destroy(); + } + }); + }); + + //Return the server + return server; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_connection.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_connection.js new file mode 100644 index 000000000..052cbd4e9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_connection.js @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var WebSocket = require('ws'); +var EventEmitter = require("events").EventEmitter; +var thrift = require('./thrift'); +var ttransport = require('./transport'); +var tprotocol = require('./protocol'); + +var TBufferedTransport = require('./buffered_transport'); +var TJSONProtocol = require('./json_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +var createClient = require('./create_client'); + +exports.WSConnection = WSConnection; + +/** + * @class + * @name WSConnectOptions + * @property {string} transport - The Thrift layered transport to use (TBufferedTransport, etc). + * @property {string} protocol - The Thrift serialization protocol to use (TJSONProtocol, etc.). + * @property {string} path - The URL path to connect to (e.g. "/", "/mySvc", "/thrift/quoteSvc", etc.). + * @property {object} headers - A standard Node.js header hash, an object hash containing key/value + * pairs where the key is the header name string and the value is the header value string. + * @property {boolean} secure - True causes the connection to use wss, otherwise ws is used. + * @property {object} wsOptions - Options passed on to WebSocket. + * @example + * //Use a secured websocket connection + * // uses the buffered transport layer, uses the JSON protocol and directs RPC traffic + * // to wss://thrift.example.com:9090/hello + * var thrift = require('thrift'); + * var options = { + * transport: thrift.TBufferedTransport, + * protocol: thrift.TJSONProtocol, + * path: "/hello", + * secure: true + * }; + * var con = thrift.createWSConnection("thrift.example.com", 9090, options); + * con.open() + * var client = thrift.createWSClient(myService, connection); + * client.myServiceFunction(); + * con.close() + */ + +/** + * Initializes a Thrift WSConnection instance (use createWSConnection() rather than + * instantiating directly). + * @constructor + * @param {string} host - The host name or IP to connect to. + * @param {number} port - The TCP port to connect to. + * @param {WSConnectOptions} options - The configuration options to use. + * @throws {error} Exceptions other than ttransport.InputBufferUnderrunError are rethrown + * @event {error} The "error" event is fired when a Node.js error event occurs during + * request or response processing, in which case the node error is passed on. An "error" + * event may also be fired when the connectison can not map a response back to the + * appropriate client (an internal error), generating a TApplicationException. + * @classdesc WSConnection objects provide Thrift end point transport + * semantics implemented using Websockets. + * @see {@link createWSConnection} + */ +function WSConnection(host, port, options) { + //Initialize the emitter base object + EventEmitter.call(this); + + //Set configuration + var self = this; + this.options = options || {}; + this.host = host; + this.port = port; + this.secure = this.options.secure || false; + this.transport = this.options.transport || TBufferedTransport; + this.protocol = this.options.protocol || TJSONProtocol; + this.path = this.options.path; + this.send_pending = []; + + //The sequence map is used to map seqIDs back to the + // calling client in multiplexed scenarios + this.seqId2Service = {}; + + //Prepare WebSocket options + this.wsOptions = { + host: this.host, + port: this.port || 80, + path: this.options.path || '/', + headers: this.options.headers || {} + }; + for (var attrname in this.options.wsOptions) { + this.wsOptions[attrname] = this.options.wsOptions[attrname]; + } +}; +util.inherits(WSConnection, EventEmitter); + +WSConnection.prototype.__reset = function() { + this.socket = null; //The web socket + this.send_pending = []; //Buffers/Callback pairs waiting to be sent +}; + +WSConnection.prototype.__onOpen = function() { + var self = this; + this.emit("open"); + if (this.send_pending.length > 0) { + //If the user made calls before the connection was fully + //open, send them now + this.send_pending.forEach(function(data) { + self.socket.send(data); + }); + this.send_pending = []; + } +}; + +WSConnection.prototype.__onClose = function(evt) { + this.emit("close"); + this.__reset(); +}; + +WSConnection.prototype.__decodeCallback = function(transport_with_data) { + var proto = new this.protocol(transport_with_data); + try { + while (true) { + var header = proto.readMessageBegin(); + var dummy_seqid = header.rseqid * -1; + var client = this.client; + //The Multiplexed Protocol stores a hash of seqid to service names + // in seqId2Service. If the SeqId is found in the hash we need to + // lookup the appropriate client for this call. + // The client var is a single client object when not multiplexing, + // when using multiplexing it is a service name keyed hash of client + // objects. + //NOTE: The 2 way interdependencies between protocols, transports, + // connections and clients in the Node.js implementation are irregular + // and make the implementation difficult to extend and maintain. We + // should bring this stuff inline with typical thrift I/O stack + // operation soon. + // --ra + var service_name = this.seqId2Service[header.rseqid]; + if (service_name) { + client = this.client[service_name]; + delete this.seqId2Service[header.rseqid]; + } + /*jshint -W083 */ + client._reqs[dummy_seqid] = function(err, success) { + transport_with_data.commitPosition(); + var clientCallback = client._reqs[header.rseqid]; + delete client._reqs[header.rseqid]; + if (clientCallback) { + clientCallback(err, success); + } + }; + /*jshint +W083 */ + if (client['recv_' + header.fname]) { + client['recv_' + header.fname](proto, header.mtype, dummy_seqid); + } else { + delete client._reqs[dummy_seqid]; + this.emit("error", + new thrift.TApplicationException( + thrift.TApplicationExceptionType.WRONG_METHOD_NAME, + "Received a response to an unknown RPC function")); + } + } + } catch (e) { + if (e instanceof InputBufferUnderrunError) { + transport_with_data.rollbackPosition(); + } else { + throw e; + } + } +}; + +WSConnection.prototype.__onData = function(data) { + if (Object.prototype.toString.call(data) == "[object ArrayBuffer]") { + data = new Uint8Array(data); + } + var buf = new Buffer(data); + this.transport.receiver(this.__decodeCallback.bind(this))(buf); + +}; + +WSConnection.prototype.__onMessage = function(evt) { + this.__onData(evt.data); +}; + +WSConnection.prototype.__onError = function(evt) { + this.emit("error", evt); + this.socket.close(); +}; + +/** + * Returns true if the transport is open + * @readonly + * @returns {boolean} + */ +WSConnection.prototype.isOpen = function() { + return this.socket && this.socket.readyState == this.socket.OPEN; +}; + +/** + * Opens the transport connection + */ +WSConnection.prototype.open = function() { + //If OPEN/CONNECTING/CLOSING ignore additional opens + if (this.socket && this.socket.readyState != this.socket.CLOSED) { + return; + } + //If there is no socket or the socket is closed: + this.socket = new WebSocket(this.uri(), "", this.wsOptions); + this.socket.binaryType = 'arraybuffer'; + this.socket.onopen = this.__onOpen.bind(this); + this.socket.onmessage = this.__onMessage.bind(this); + this.socket.onerror = this.__onError.bind(this); + this.socket.onclose = this.__onClose.bind(this); +}; + +/** + * Closes the transport connection + */ +WSConnection.prototype.close = function() { + this.socket.close(); +}; + +/** + * Return URI for the connection + * @returns {string} URI + */ +WSConnection.prototype.uri = function() { + var schema = this.secure ? 'wss' : 'ws'; + var port = ''; + var path = this.path || '/'; + var host = this.host; + + // avoid port if default for schema + if (this.port && (('wss' == schema && this.port != 443) || + ('ws' == schema && this.port != 80))) { + port = ':' + this.port; + } + + return schema + '://' + host + port + path; +}; + +/** + * Writes Thrift message data to the connection + * @param {Buffer} data - A Node.js Buffer containing the data to write + * @returns {void} No return value. + * @event {error} the "error" event is raised upon request failure passing the + * Node.js error object to the listener. + */ +WSConnection.prototype.write = function(data) { + if (this.isOpen()) { + //Send data and register a callback to invoke the client callback + this.socket.send(data); + } else { + //Queue the send to go out __onOpen + this.send_pending.push(data); + } +}; + +/** + * Creates a new WSConnection object, used by Thrift clients to connect + * to Thrift HTTP based servers. + * @param {string} host - The host name or IP to connect to. + * @param {number} port - The TCP port to connect to. + * @param {WSConnectOptions} options - The configuration options to use. + * @returns {WSConnection} The connection object. + * @see {@link WSConnectOptions} + */ +exports.createWSConnection = function(host, port, options) { + return new WSConnection(host, port, options); +}; + +exports.createWSClient = createClient; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_transport.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_transport.js new file mode 100644 index 000000000..3513b84be --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/ws_transport.js @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var log = require('./log'); + +module.exports = TWebSocketTransport; + +/** + * Constructor Function for the WebSocket transport. + * @constructor + * @param {string} [url] - The URL to connect to. + * @classdesc The Apache Thrift Transport layer performs byte level I/O + * between RPC clients and servers. The JavaScript TWebSocketTransport object + * uses the WebSocket protocol. Target servers must implement WebSocket. + * (see: node.js example server_http.js). + * @example + * var transport = new Thrift.TWebSocketTransport("http://localhost:8585"); + */ +function TWebSocketTransport(url) { + this.__reset(url); +}; + + +TWebSocketTransport.prototype.__reset = function(url) { + this.url = url; //Where to connect + this.socket = null; //The web socket + this.callbacks = []; //Pending callbacks + this.send_pending = []; //Buffers/Callback pairs waiting to be sent + this.send_buf = ''; //Outbound data, immutable until sent + this.recv_buf = ''; //Inbound data + this.rb_wpos = 0; //Network write position in receive buffer + this.rb_rpos = 0; //Client read position in receive buffer +}; + +/** + * Sends the current WS request and registers callback. The async + * parameter is ignored (WS flush is always async) and the callback + * function parameter is required. + * @param {object} async - Ignored. + * @param {object} callback - The client completion callback. + * @returns {undefined|string} Nothing (undefined) + */ +TWebSocketTransport.prototype.flush = function(async, callback) { + var self = this; + if (this.isOpen()) { + //Send data and register a callback to invoke the client callback + this.socket.send(this.send_buf); + this.callbacks.push((function() { + var clientCallback = callback; + return function(msg) { + self.setRecvBuffer(msg); + clientCallback(); + }; + }())); + } else { + //Queue the send to go out __onOpen + this.send_pending.push({ + buf: this.send_buf, + cb: callback + }); + } +}; + +TWebSocketTransport.prototype.__onOpen = function() { + var self = this; + if (this.send_pending.length > 0) { + //If the user made calls before the connection was fully + //open, send them now + this.send_pending.forEach(function(elem) { + this.socket.send(elem.buf); + this.callbacks.push((function() { + var clientCallback = elem.cb; + return function(msg) { + self.setRecvBuffer(msg); + clientCallback(); + }; + }())); + }); + this.send_pending = []; + } +}; + +TWebSocketTransport.prototype.__onClose = function(evt) { + this.__reset(this.url); +}; + +TWebSocketTransport.prototype.__onMessage = function(evt) { + if (this.callbacks.length) { + this.callbacks.shift()(evt.data); + } +}; + +TWebSocketTransport.prototype.__onError = function(evt) { + log.error('websocket: ' + evt.toString()); + this.socket.close(); +}; + +/** + * Sets the buffer to use when receiving server responses. + * @param {string} buf - The buffer to receive server responses. + */ +TWebSocketTransport.prototype.setRecvBuffer = function(buf) { + this.recv_buf = buf; + this.recv_buf_sz = this.recv_buf.length; + this.wpos = this.recv_buf.length; + this.rpos = 0; +}; + +/** + * Returns true if the transport is open + * @readonly + * @returns {boolean} + */ +TWebSocketTransport.prototype.isOpen = function() { + return this.socket && this.socket.readyState == this.socket.OPEN; +}; + +/** + * Opens the transport connection + */ +TWebSocketTransport.prototype.open = function() { + //If OPEN/CONNECTING/CLOSING ignore additional opens + if (this.socket && this.socket.readyState != this.socket.CLOSED) { + return; + } + //If there is no socket or the socket is closed: + this.socket = new WebSocket(this.url); + this.socket.onopen = this.__onOpen.bind(this); + this.socket.onmessage = this.__onMessage.bind(this); + this.socket.onerror = this.__onError.bind(this); + this.socket.onclose = this.__onClose.bind(this); +}; + +/** + * Closes the transport connection + */ +TWebSocketTransport.prototype.close = function() { + this.socket.close(); +}; + +/** + * Returns the specified number of characters from the response + * buffer. + * @param {number} len - The number of characters to return. + * @returns {string} Characters sent by the server. + */ +TWebSocketTransport.prototype.read = function(len) { + var avail = this.wpos - this.rpos; + + if (avail === 0) { + return ''; + } + + var give = len; + + if (avail < len) { + give = avail; + } + + var ret = this.read_buf.substr(this.rpos, give); + this.rpos += give; + + //clear buf when complete? + return ret; +}; + +/** + * Returns the entire response buffer. + * @returns {string} Characters sent by the server. + */ +TWebSocketTransport.prototype.readAll = function() { + return this.recv_buf; +}; + +/** + * Sets the send buffer to buf. + * @param {string} buf - The buffer to send. + */ +TWebSocketTransport.prototype.write = function(buf) { + this.send_buf = buf; +}; + +/** + * Returns the send buffer. + * @readonly + * @returns {string} The send buffer. + */ +TWebSocketTransport.prototype.getSendBuffer = function() { + return this.send_buf; +}; diff --git a/src/jaegertracing/thrift/lib/nodejs/lib/thrift/xhr_connection.js b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/xhr_connection.js new file mode 100644 index 000000000..6459c900c --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/lib/thrift/xhr_connection.js @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var util = require('util'); +var EventEmitter = require("events").EventEmitter; +var thrift = require('./thrift'); + +var TBufferedTransport = require('./buffered_transport'); +var TJSONProtocol = require('./json_protocol'); +var InputBufferUnderrunError = require('./input_buffer_underrun_error'); + +var createClient = require('./create_client'); + +exports.XHRConnection = XHRConnection; + +/** + * Constructor Function for the XHR Connection. + * If you do not specify a host and port then XHRConnection will default to the + * host and port of the page from which this javascript is served. + * @constructor + * @param {string} [url] - The URL to connect to. + * @classdesc TXHRConnection objects provide Thrift end point transport + * semantics implemented using XHR. + * @example + * var transport = new Thrift.TXHRConnection('localhost', 9099, {}); + */ +function XHRConnection(host, port, options) { + this.options = options || {}; + this.wpos = 0; + this.rpos = 0; + this.useCORS = (options && options.useCORS); + this.send_buf = ''; + this.recv_buf = ''; + this.transport = options.transport || TBufferedTransport; + this.protocol = options.protocol || TJSONProtocol; + this.headers = options.headers || {}; + + host = host || window.location.host; + port = port || window.location.port; + var prefix = options.https ? 'https://' : 'http://'; + var path = options.path || '/'; + + if (port === '') { + port = undefined; + } + + if (!port || port === 80 || port === '80') { + this.url = prefix + host + path; + } else { + this.url = prefix + host + ':' + port + path; + } + + //The sequence map is used to map seqIDs back to the + // calling client in multiplexed scenarios + this.seqId2Service = {}; +}; + +util.inherits(XHRConnection, EventEmitter); + +/** +* Gets the browser specific XmlHttpRequest Object. +* @returns {object} the browser XHR interface object +*/ +XHRConnection.prototype.getXmlHttpRequestObject = function() { + try { return new XMLHttpRequest(); } catch (e1) { } + try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e2) { } + try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e3) { } + + throw "Your browser doesn't support XHR."; +}; + +/** + * Sends the current XRH request if the transport was created with a URL + * and the async parameter is false. If the transport was not created with + * a URL, or the async parameter is True and no callback is provided, or + * the URL is an empty string, the current send buffer is returned. + * @param {object} async - If true the current send buffer is returned. + * @param {object} callback - Optional async completion callback + * @returns {undefined|string} Nothing or the current send buffer. + * @throws {string} If XHR fails. + */ +XHRConnection.prototype.flush = function() { + var self = this; + if (this.url === undefined || this.url === '') { + return this.send_buf; + } + + var xreq = this.getXmlHttpRequestObject(); + + if (xreq.overrideMimeType) { + xreq.overrideMimeType('application/json'); + } + + xreq.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + self.setRecvBuffer(this.responseText); + } + }; + + xreq.open('POST', this.url, true); + + Object.keys(this.headers).forEach(function(headerKey) { + xreq.setRequestHeader(headerKey, self.headers[headerKey]); + }); + + xreq.send(this.send_buf); +}; + +/** + * Sets the buffer to provide the protocol when deserializing. + * @param {string} buf - The buffer to supply the protocol. + */ +XHRConnection.prototype.setRecvBuffer = function(buf) { + this.recv_buf = buf; + this.recv_buf_sz = this.recv_buf.length; + this.wpos = this.recv_buf.length; + this.rpos = 0; + + if (Object.prototype.toString.call(buf) == "[object ArrayBuffer]") { + var data = new Uint8Array(buf); + } + var thing = new Buffer(data || buf); + + this.transport.receiver(this.__decodeCallback.bind(this))(thing); + +}; + +XHRConnection.prototype.__decodeCallback = function(transport_with_data) { + var proto = new this.protocol(transport_with_data); + try { + while (true) { + var header = proto.readMessageBegin(); + var dummy_seqid = header.rseqid * -1; + var client = this.client; + //The Multiplexed Protocol stores a hash of seqid to service names + // in seqId2Service. If the SeqId is found in the hash we need to + // lookup the appropriate client for this call. + // The client var is a single client object when not multiplexing, + // when using multiplexing it is a service name keyed hash of client + // objects. + //NOTE: The 2 way interdependencies between protocols, transports, + // connections and clients in the Node.js implementation are irregular + // and make the implementation difficult to extend and maintain. We + // should bring this stuff inline with typical thrift I/O stack + // operation soon. + // --ra + var service_name = this.seqId2Service[header.rseqid]; + if (service_name) { + client = this.client[service_name]; + delete this.seqId2Service[header.rseqid]; + } + /*jshint -W083 */ + client._reqs[dummy_seqid] = function(err, success) { + transport_with_data.commitPosition(); + var clientCallback = client._reqs[header.rseqid]; + delete client._reqs[header.rseqid]; + if (clientCallback) { + clientCallback(err, success); + } + }; + /*jshint +W083 */ + if (client['recv_' + header.fname]) { + client['recv_' + header.fname](proto, header.mtype, dummy_seqid); + } else { + delete client._reqs[dummy_seqid]; + this.emit("error", + new thrift.TApplicationException( + thrift.TApplicationExceptionType.WRONG_METHOD_NAME, + "Received a response to an unknown RPC function")); + } + } + } catch (e) { + if (e instanceof InputBufferUnderrunError) { + transport_with_data.rollbackPosition(); + } else { + throw e; + } + } +}; + +/** + * Returns true if the transport is open, XHR always returns true. + * @readonly + * @returns {boolean} Always True. + */ +XHRConnection.prototype.isOpen = function() { + return true; +}; + +/** + * Opens the transport connection, with XHR this is a nop. + */ +XHRConnection.prototype.open = function() {}; + +/** + * Closes the transport connection, with XHR this is a nop. + */ +XHRConnection.prototype.close = function() {}; + +/** + * Returns the specified number of characters from the response + * buffer. + * @param {number} len - The number of characters to return. + * @returns {string} Characters sent by the server. + */ +XHRConnection.prototype.read = function(len) { + var avail = this.wpos - this.rpos; + + if (avail === 0) { + return ''; + } + + var give = len; + + if (avail < len) { + give = avail; + } + + var ret = this.read_buf.substr(this.rpos, give); + this.rpos += give; + + //clear buf when complete? + return ret; +}; + +/** + * Returns the entire response buffer. + * @returns {string} Characters sent by the server. + */ +XHRConnection.prototype.readAll = function() { + return this.recv_buf; +}; + +/** + * Sets the send buffer to buf. + * @param {string} buf - The buffer to send. + */ +XHRConnection.prototype.write = function(buf) { + this.send_buf = buf; + this.flush(); +}; + +/** + * Returns the send buffer. + * @readonly + * @returns {string} The send buffer. + */ +XHRConnection.prototype.getSendBuffer = function() { + return this.send_buf; +}; + +/** + * Creates a new TXHRTransport object, used by Thrift clients to connect + * to Thrift HTTP based servers. + * @param {string} host - The host name or IP to connect to. + * @param {number} port - The TCP port to connect to. + * @param {XHRConnectOptions} options - The configuration options to use. + * @returns {XHRConnection} The connection object. + * @see {@link XHRConnectOptions} + */ +exports.createXHRConnection = function(host, port, options) { + return new XHRConnection(host, port, options); +}; + +exports.createXHRClient = createClient; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/binary.test.js b/src/jaegertracing/thrift/lib/nodejs/test/binary.test.js new file mode 100644 index 000000000..187cd1874 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/binary.test.js @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const test = require("tape"); +const binary = require("thrift/binary"); + +const cases = { + "Should read signed byte": function(assert) { + assert.equal(1, binary.readByte(0x01)); + assert.equal(-1, binary.readByte(0xff)); + + assert.equal(127, binary.readByte(0x7f)); + assert.equal(-128, binary.readByte(0x80)); + assert.end(); + }, + "Should write byte": function(assert) { + //Protocol simply writes to the buffer. Nothing to test.. yet. + assert.ok(true); + assert.end(); + }, + "Should read I16": function(assert) { + assert.equal(0, binary.readI16([0x00, 0x00])); + assert.equal(1, binary.readI16([0x00, 0x01])); + assert.equal(-1, binary.readI16([0xff, 0xff])); + + // Min I16 + assert.equal(-32768, binary.readI16([0x80, 0x00])); + // Max I16 + assert.equal(32767, binary.readI16([0x7f, 0xff])); + assert.end(); + }, + + "Should write I16": function(assert) { + assert.deepEqual([0x00, 0x00], binary.writeI16([], 0)); + assert.deepEqual([0x00, 0x01], binary.writeI16([], 1)); + assert.deepEqual([0xff, 0xff], binary.writeI16([], -1)); + + // Min I16 + assert.deepEqual([0x80, 0x00], binary.writeI16([], -32768)); + // Max I16 + assert.deepEqual([0x7f, 0xff], binary.writeI16([], 32767)); + assert.end(); + }, + + "Should read I32": function(assert) { + assert.equal(0, binary.readI32([0x00, 0x00, 0x00, 0x00])); + assert.equal(1, binary.readI32([0x00, 0x00, 0x00, 0x01])); + assert.equal(-1, binary.readI32([0xff, 0xff, 0xff, 0xff])); + + // Min I32 + assert.equal(-2147483648, binary.readI32([0x80, 0x00, 0x00, 0x00])); + // Max I32 + assert.equal(2147483647, binary.readI32([0x7f, 0xff, 0xff, 0xff])); + assert.end(); + }, + + "Should write I32": function(assert) { + assert.deepEqual([0x00, 0x00, 0x00, 0x00], binary.writeI32([], 0)); + assert.deepEqual([0x00, 0x00, 0x00, 0x01], binary.writeI32([], 1)); + assert.deepEqual([0xff, 0xff, 0xff, 0xff], binary.writeI32([], -1)); + + // Min I32 + assert.deepEqual( + [0x80, 0x00, 0x00, 0x00], + binary.writeI32([], -2147483648) + ); + // Max I32 + assert.deepEqual([0x7f, 0xff, 0xff, 0xff], binary.writeI32([], 2147483647)); + assert.end(); + }, + + "Should read doubles": function(assert) { + assert.equal( + 0, + binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + assert.equal( + 0, + binary.readDouble([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + assert.equal( + 1, + binary.readDouble([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + assert.equal( + 2, + binary.readDouble([0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + assert.equal( + -2, + binary.readDouble([0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + + assert.equal( + Math.PI, + binary.readDouble([0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18]) + ); + + assert.equal( + Infinity, + binary.readDouble([0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + assert.equal( + -Infinity, + binary.readDouble([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + + assert.ok( + isNaN(binary.readDouble([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])) + ); + + assert.equal( + 1 / 3, + binary.readDouble([0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]) + ); + + // Min subnormal positive double + assert.equal( + 4.9406564584124654e-324, + binary.readDouble([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) + ); + // Min normal positive double + assert.equal( + 2.2250738585072014e-308, + binary.readDouble([0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ); + // Max positive double + assert.equal( + 1.7976931348623157e308, + binary.readDouble([0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) + ); + assert.end(); + }, + + "Should write doubles": function(assert) { + assert.deepEqual( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], 0) + ); + assert.deepEqual( + [0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], 1) + ); + assert.deepEqual( + [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], 2) + ); + assert.deepEqual( + [0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], -2) + ); + + assert.deepEqual( + [0x40, 0x9, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18], + binary.writeDouble([], Math.PI) + ); + + assert.deepEqual( + [0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], Infinity) + ); + assert.deepEqual( + [0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], -Infinity) + ); + + assert.deepEqual( + [0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], NaN) + ); + + assert.deepEqual( + [0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55], + binary.writeDouble([], 1 / 3) + ); + + // Min subnormal positive double + assert.deepEqual( + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + binary.writeDouble([], 4.9406564584124654e-324) + ); + // Min normal positive double + assert.deepEqual( + [0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + binary.writeDouble([], 2.2250738585072014e-308) + ); + // Max positive double + assert.deepEqual( + [0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + binary.writeDouble([], 1.7976931348623157e308) + ); + assert.end(); + } +}; + +Object.keys(cases).forEach(function(caseName) { + test(caseName, cases[caseName]); +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/certificates.README b/src/jaegertracing/thrift/lib/nodejs/test/certificates.README new file mode 100644 index 000000000..06c507e7d --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/certificates.README @@ -0,0 +1,7 @@ +server.crt AND server.key ARE PROVIDED FOR TEST PURPOSE AND SHOULD *NEVER* BE USED IN PRODUCTION + + +Origin of the test key and cert is the folder test/keys of Apache Thrift source code distribution + +We need copies for npm deployment + diff --git a/src/jaegertracing/thrift/lib/nodejs/test/client.js b/src/jaegertracing/thrift/lib/nodejs/test/client.js new file mode 100644 index 000000000..49e3a5ec9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/client.js @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const assert = require("assert"); +const thrift = require("thrift"); +const helpers = require("./helpers"); + +const ThriftTest = require(`./${helpers.genPath}/ThriftTest`); +const ThriftTestDriver = require("./test_driver").ThriftTestDriver; +const ThriftTestDriverPromise = require("./test_driver") + .ThriftTestDriverPromise; +const SecondService = require(`./${helpers.genPath}/SecondService`); + +const program = require("commander"); + +program + .option( + "-p, --protocol <protocol>", + "Set thrift protocol (binary|compact|json) [protocol]" + ) + .option( + "-t, --transport <transport>", + "Set thrift transport (buffered|framed|http) [transport]" + ) + .option("--port <port>", "Set thrift server port number to connect", 9090) + .option("--host <host>", "Set thrift server host to connect", "localhost") + .option( + "--domain-socket <path>", + "Set thrift server unix domain socket to connect" + ) + .option("--ssl", "use SSL transport") + .option("--callback", "test with callback style functions") + .option( + "-t, --type <type>", + "Select server type (http|multiplex|tcp|websocket)", + "tcp" + ) + .option("--es6", "Use es6 code") + .option("--es5", "Use es5 code") + .parse(process.argv); + +const host = program.host; +const port = program.port; +const domainSocket = program.domainSocket; +const ssl = program.ssl; +let type = program.type; + +/* for compatibility with cross test invocation for http transport testing */ +if (program.transport === "http") { + program.transport = "buffered"; + type = "http"; +} + +const options = { + transport: helpers.transports[program.transport], + protocol: helpers.protocols[program.protocol] +}; + +if (type === "http" || type === "websocket") { + options.path = "/test"; +} + +if (type === "http") { + options.headers = { Connection: "close" }; +} + +if (ssl) { + if (type === "tcp" || type === "multiplex") { + options.rejectUnauthorized = false; + } else if (type === "http") { + options.nodeOptions = { rejectUnauthorized: false }; + options.https = true; + } else if (type === "websocket") { + options.wsOptions = { rejectUnauthorized: false }; + options.secure = true; + } +} + +let connection; +let client; +const testDriver = program.callback + ? ThriftTestDriver + : ThriftTestDriverPromise; +if (helpers.ecmaMode === "es6" && program.callback) { + console.log("ES6 does not support callback style"); + process.exit(0); +} + +if (type === "tcp" || type === "multiplex") { + if (domainSocket) { + connection = thrift.createUDSConnection(domainSocket, options); + } else { + connection = ssl + ? thrift.createSSLConnection(host, port, options) + : thrift.createConnection(host, port, options); + } +} else if (type === "http") { + if (domainSocket) { + connection = thrift.createHttpUDSConnection(domainSocket, options); + } else { + connection = thrift.createHttpConnection(host, port, options); + } +} else if (type === "websocket") { + connection = thrift.createWSConnection(host, port, options); + connection.open(); +} + +connection.on("error", function(err) { + assert(false, err); +}); + +if (type === "tcp") { + client = thrift.createClient(ThriftTest, connection); + runTests(); +} else if (type === "multiplex") { + const mp = new thrift.Multiplexer(); + client = mp.createClient("ThriftTest", ThriftTest, connection); + const secondclient = mp.createClient( + "SecondService", + SecondService, + connection + ); + + connection.on("connect", function() { + secondclient.secondtestString("Test", function(err, response) { + assert(!err); + assert.equal('testString("Test")', response); + }); + + runTests(); + }); +} else if (type === "http") { + client = thrift.createHttpClient(ThriftTest, connection); + runTests(); +} else if (type === "websocket") { + client = thrift.createWSClient(ThriftTest, connection); + runTests(); +} + +function runTests() { + testDriver(client, function(status) { + console.log(status); + if (type !== "http" && type !== "websocket") { + connection.end(); + } + if (type !== "multiplex") { + process.exit(0); + } + }); +} + +exports.expressoTest = function() {}; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/deep-constructor.test.js b/src/jaegertracing/thrift/lib/nodejs/test/deep-constructor.test.js new file mode 100644 index 000000000..504dacf0b --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/deep-constructor.test.js @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const ttypes = require("./gen-nodejs/JsDeepConstructorTest_types"); +const thrift = require("thrift"); +const test = require("tape"); +const bufferEquals = require("buffer-equals"); + +function serializeBinary(data) { + let buff; + const transport = new thrift.TBufferedTransport(null, function(msg) { + buff = msg; + }); + const prot = new thrift.TBinaryProtocol(transport); + data.write(prot); + prot.flush(); + return buff; +} + +function deserializeBinary(serialized, type) { + const t = new thrift.TFramedTransport(serialized); + const p = new thrift.TBinaryProtocol(t); + const data = new type(); + data.read(p); + return data; +} + +function serializeJSON(data) { + let buff; + const transport = new thrift.TBufferedTransport(null, function(msg) { + buff = msg; + }); + const protocol = new thrift.TJSONProtocol(transport); + protocol.writeMessageBegin("", 0, 0); + data.write(protocol); + protocol.writeMessageEnd(); + protocol.flush(); + return buff; +} + +function deserializeJSON(serialized, type) { + const transport = new thrift.TFramedTransport(serialized); + const protocol = new thrift.TJSONProtocol(transport); + protocol.readMessageBegin(); + const data = new type(); + data.read(protocol); + protocol.readMessageEnd(); + return data; +} + +function createThriftObj() { + return new ttypes.Complex({ + struct_field: new ttypes.Simple({ value: "a" }), + + struct_list_field: [ + new ttypes.Simple({ value: "b" }), + new ttypes.Simple({ value: "c" }) + ], + + struct_set_field: [ + new ttypes.Simple({ value: "d" }), + new ttypes.Simple({ value: "e" }) + ], + + struct_map_field: { + A: new ttypes.Simple({ value: "f" }), + B: new ttypes.Simple({ value: "g" }) + }, + + struct_nested_containers_field: [ + [ + { + C: [ + new ttypes.Simple({ value: "h" }), + new ttypes.Simple({ value: "i" }) + ] + } + ] + ], + + struct_nested_containers_field2: { + D: [ + { + DA: new ttypes.Simple({ value: "j" }) + }, + { + DB: new ttypes.Simple({ value: "k" }) + } + ] + }, + + list_of_list_field: [ + ["l00", "l01", "l02"], + ["l10", "l11", "l12"], + ["l20", "l21", "l22"] + ], + + list_of_list_of_list_field: [ + [ + ["m000", "m001", "m002"], + ["m010", "m011", "m012"], + ["m020", "m021", "m022"] + ], + [ + ["m100", "m101", "m102"], + ["m110", "m111", "m112"], + ["m120", "m121", "m122"] + ], + [ + ["m200", "m201", "m202"], + ["m210", "m211", "m212"], + ["m220", "m221", "m222"] + ] + ] + }); +} + +function createJsObj() { + return { + struct_field: { value: "a" }, + + struct_list_field: [{ value: "b" }, { value: "c" }], + + struct_set_field: [{ value: "d" }, { value: "e" }], + + struct_map_field: { + A: { value: "f" }, + B: { value: "g" } + }, + + struct_nested_containers_field: [ + [ + { + C: [{ value: "h" }, { value: "i" }] + } + ] + ], + + struct_nested_containers_field2: { + D: [ + { + DA: { value: "j" } + }, + { + DB: { value: "k" } + } + ] + }, + + list_of_list_field: [ + ["l00", "l01", "l02"], + ["l10", "l11", "l12"], + ["l20", "l21", "l22"] + ], + + list_of_list_of_list_field: [ + [ + ["m000", "m001", "m002"], + ["m010", "m011", "m012"], + ["m020", "m021", "m022"] + ], + [ + ["m100", "m101", "m102"], + ["m110", "m111", "m112"], + ["m120", "m121", "m122"] + ], + [ + ["m200", "m201", "m202"], + ["m210", "m211", "m212"], + ["m220", "m221", "m222"] + ] + ] + }; +} + +function assertValues(obj, assert) { + assert.equals(obj.struct_field.value, "a"); + assert.equals(obj.struct_list_field[0].value, "b"); + assert.equals(obj.struct_list_field[1].value, "c"); + assert.equals(obj.struct_set_field[0].value, "d"); + assert.equals(obj.struct_set_field[1].value, "e"); + assert.equals(obj.struct_map_field.A.value, "f"); + assert.equals(obj.struct_map_field.B.value, "g"); + assert.equals(obj.struct_nested_containers_field[0][0].C[0].value, "h"); + assert.equals(obj.struct_nested_containers_field[0][0].C[1].value, "i"); + assert.equals(obj.struct_nested_containers_field2.D[0].DA.value, "j"); + assert.equals(obj.struct_nested_containers_field2.D[1].DB.value, "k"); + assert.equals(obj.list_of_list_field[0][0], "l00"); + assert.equals(obj.list_of_list_field[0][1], "l01"); + assert.equals(obj.list_of_list_field[0][2], "l02"); + assert.equals(obj.list_of_list_field[1][0], "l10"); + assert.equals(obj.list_of_list_field[1][1], "l11"); + assert.equals(obj.list_of_list_field[1][2], "l12"); + assert.equals(obj.list_of_list_field[2][0], "l20"); + assert.equals(obj.list_of_list_field[2][1], "l21"); + assert.equals(obj.list_of_list_field[2][2], "l22"); + + assert.equals(obj.list_of_list_of_list_field[0][0][0], "m000"); + assert.equals(obj.list_of_list_of_list_field[0][0][1], "m001"); + assert.equals(obj.list_of_list_of_list_field[0][0][2], "m002"); + assert.equals(obj.list_of_list_of_list_field[0][1][0], "m010"); + assert.equals(obj.list_of_list_of_list_field[0][1][1], "m011"); + assert.equals(obj.list_of_list_of_list_field[0][1][2], "m012"); + assert.equals(obj.list_of_list_of_list_field[0][2][0], "m020"); + assert.equals(obj.list_of_list_of_list_field[0][2][1], "m021"); + assert.equals(obj.list_of_list_of_list_field[0][2][2], "m022"); + + assert.equals(obj.list_of_list_of_list_field[1][0][0], "m100"); + assert.equals(obj.list_of_list_of_list_field[1][0][1], "m101"); + assert.equals(obj.list_of_list_of_list_field[1][0][2], "m102"); + assert.equals(obj.list_of_list_of_list_field[1][1][0], "m110"); + assert.equals(obj.list_of_list_of_list_field[1][1][1], "m111"); + assert.equals(obj.list_of_list_of_list_field[1][1][2], "m112"); + assert.equals(obj.list_of_list_of_list_field[1][2][0], "m120"); + assert.equals(obj.list_of_list_of_list_field[1][2][1], "m121"); + assert.equals(obj.list_of_list_of_list_field[1][2][2], "m122"); + + assert.equals(obj.list_of_list_of_list_field[2][0][0], "m200"); + assert.equals(obj.list_of_list_of_list_field[2][0][1], "m201"); + assert.equals(obj.list_of_list_of_list_field[2][0][2], "m202"); + assert.equals(obj.list_of_list_of_list_field[2][1][0], "m210"); + assert.equals(obj.list_of_list_of_list_field[2][1][1], "m211"); + assert.equals(obj.list_of_list_of_list_field[2][1][2], "m212"); + assert.equals(obj.list_of_list_of_list_field[2][2][0], "m220"); + assert.equals(obj.list_of_list_of_list_field[2][2][1], "m221"); + assert.equals(obj.list_of_list_of_list_field[2][2][2], "m222"); +} + +function createTestCases(serialize, deserialize) { + const cases = { + "Serialize/deserialize should return equal object": function(assert) { + const tObj = createThriftObj(); + const received = deserialize(serialize(tObj), ttypes.Complex); + assert.ok(tObj !== received, "not the same object"); + assert.deepEqual(tObj, received); + assert.end(); + }, + + "Nested structs and containers initialized from plain js objects should serialize same as if initialized from thrift objects": function( + assert + ) { + const tObj1 = createThriftObj(); + const tObj2 = new ttypes.Complex(createJsObj()); + assertValues(tObj2, assert); + const s1 = serialize(tObj1); + const s2 = serialize(tObj2); + assert.ok(bufferEquals(s1, s2)); + assert.end(); + }, + + "Modifications to args object should not affect constructed Thrift object": function( + assert + ) { + const args = createJsObj(); + assertValues(args, assert); + + const tObj = new ttypes.Complex(args); + assertValues(tObj, assert); + + args.struct_field.value = "ZZZ"; + args.struct_list_field[0].value = "ZZZ"; + args.struct_list_field[1].value = "ZZZ"; + args.struct_set_field[0].value = "ZZZ"; + args.struct_set_field[1].value = "ZZZ"; + args.struct_map_field.A.value = "ZZZ"; + args.struct_map_field.B.value = "ZZZ"; + args.struct_nested_containers_field[0][0].C[0] = "ZZZ"; + args.struct_nested_containers_field[0][0].C[1] = "ZZZ"; + args.struct_nested_containers_field2.D[0].DA = "ZZZ"; + args.struct_nested_containers_field2.D[0].DB = "ZZZ"; + + assertValues(tObj, assert); + assert.end(); + }, + + "nulls are ok": function(assert) { + const tObj = new ttypes.Complex({ + struct_field: null, + struct_list_field: null, + struct_set_field: null, + struct_map_field: null, + struct_nested_containers_field: null, + struct_nested_containers_field2: null + }); + const received = deserialize(serialize(tObj), ttypes.Complex); + assert.strictEqual(tObj.struct_field, null); + assert.ok(tObj !== received); + assert.deepEqual(tObj, received); + assert.end(); + }, + + "Can make list with objects": function(assert) { + const tObj = new ttypes.ComplexList({ + struct_list_field: [new ttypes.Complex({})] + }); + const innerObj = tObj.struct_list_field[0]; + assert.ok(innerObj instanceof ttypes.Complex); + assert.strictEqual(innerObj.struct_field, null); + assert.strictEqual(innerObj.struct_list_field, null); + assert.strictEqual(innerObj.struct_set_field, null); + assert.strictEqual(innerObj.struct_map_field, null); + assert.strictEqual(innerObj.struct_nested_containers_field, null); + assert.strictEqual(innerObj.struct_nested_containers_field2, null); + assert.end(); + } + }; + return cases; +} + +function run(name, cases) { + Object.keys(cases).forEach(function(caseName) { + test(name + ": " + caseName, cases[caseName]); + }); +} + +run("binary", createTestCases(serializeBinary, deserializeBinary)); +run("json", createTestCases(serializeJSON, deserializeJSON)); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/client.js b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/client.js new file mode 100644 index 000000000..55dc70269 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/client.js @@ -0,0 +1,77 @@ +#!/usr/bin/env node + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const assert = require("assert"); +const test = require("tape"); +const thrift = require("thrift"); +const program = require("commander"); + +program + .option("--host <host>", "Set the thrift server host to connect", "localhost") + .option("--port <port>", "Set the thrift server port number to connect", 9090) + .parse(process.argv); + +const Service = require("./gen-2/second-episode/gen-nodejs/Service"); +const Types = require("types-package/first-episode/Types_types"); + +const host = program.host; +const port = program.port; + +const options = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol +}; + +const connection = thrift.createConnection(host, port, options); +const testDriver = function(client, callback) { + test("NodeJS episodic compilation client-server test", function(assert) { + const type1Object = new Types.Type1(); + type1Object.number = 42; + type1Object.message = "The answer"; + client.testEpisode(type1Object, function(err, response) { + assert.error(err, "no callback error"); + assert.equal(response.number, type1Object.number + 1); + assert.equal( + response.message, + type1Object.message + " [Hello from the server]" + ); + assert.end(); + callback("Server successfully tested"); + }); + }); +}; + +connection.on("error", function(err) { + assert(false, err); +}); + +const client = thrift.createClient(Service, connection); + +runTests(); + +function runTests() { + testDriver(client, function(status) { + console.log(status); + connection.destroy(); + }); +} + +exports.expressoTest = function() {}; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/episodic_compilation.package.json b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/episodic_compilation.package.json new file mode 100644 index 000000000..7a78b4beb --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/episodic_compilation.package.json @@ -0,0 +1,3 @@ +{ + "name": "types-package" +} diff --git a/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/server.js b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/server.js new file mode 100644 index 000000000..2917b681c --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/episodic-code-generation-test/server.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * 'License'); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const thrift = require("../../lib/thrift"); +const program = require("commander"); + +program + .option("--port <port>", "Set the thrift server port", 9090) + .parse(process.argv); + +const Service = require("./gen-2/second-episode/gen-nodejs/Service"); +const Types = require("types-package/first-episode/Types_types"); + +const port = program.port; + +const options = { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol +}; + +const ServiceHandler = { + testEpisode: function(receivedType1Object) { + const type1Object = new Types.Type1(); + type1Object.number = receivedType1Object.number + 1; + type1Object.message = + receivedType1Object.message + " [Hello from the server]"; + return type1Object; + } +}; + +const server = thrift.createServer(Service, ServiceHandler, options); +server.listen(port); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/exceptions.js b/src/jaegertracing/thrift/lib/nodejs/test/exceptions.js new file mode 100644 index 000000000..ab2798a26 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/exceptions.js @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +"use strict"; +const test = require("tape"); +const thrift = require("../lib/thrift/thrift.js"); +const InputBufferUnderrunError = require("../lib/thrift/input_buffer_underrun_error"); + +test("TApplicationException", function t(assert) { + const e = new thrift.TApplicationException(1, "foo"); + assert.ok( + e instanceof thrift.TApplicationException, + "is instanceof TApplicationException" + ); + assert.ok(e instanceof thrift.TException, "is instanceof TException"); + assert.ok(e instanceof Error, "is instanceof Error"); + assert.equal(typeof e.stack, "string", "has stack trace"); + assert.ok( + /^TApplicationException: foo/.test(e.stack), + "Stack trace has correct error name and message" + ); + assert.ok( + e.stack.indexOf("test/exceptions.js:7:11") !== -1, + "stack trace starts on correct line and column" + ); + assert.equal( + e.name, + "TApplicationException", + "has function name TApplicationException" + ); + assert.equal(e.message, "foo", 'has error message "foo"'); + assert.equal(e.type, 1, "has type 1"); + assert.end(); +}); + +test("unexpected TApplicationException ", function t(assert) { + const e = new thrift.TApplicationException(1, 100); + assert.ok( + e instanceof thrift.TApplicationException, + "is instanceof TApplicationException" + ); + assert.ok(e instanceof thrift.TException, "is instanceof TException"); + assert.ok(e instanceof Error, "is instanceof Error"); + assert.equal(typeof e.stack, "string", "has stack trace"); + assert.ok( + /^TApplicationException: 100/.test(e.stack), + "Stack trace has correct error name and message" + ); + assert.ok( + e.stack.indexOf("test/exceptions.js:7:11") !== -1, + "stack trace starts on correct line and column" + ); + assert.equal( + e.name, + "TApplicationException", + "has function name TApplicationException" + ); + assert.equal(e.message, 100, "has error message 100"); + assert.equal(e.type, 1, "has type 1"); + assert.end(); +}); + +test("TException", function t(assert) { + const e = new thrift.TException("foo"); + assert.ok(e instanceof thrift.TException, "is instanceof TException"); + assert.ok(e instanceof Error, "is instanceof Error"); + assert.equal(typeof e.stack, "string", "has stack trace"); + assert.ok( + /^TException: foo/.test(e.stack), + "Stack trace has correct error name and message" + ); + assert.ok( + e.stack.indexOf("test/exceptions.js:21:11") !== -1, + "stack trace starts on correct line and column" + ); + assert.equal(e.name, "TException", "has function name TException"); + assert.equal(e.message, "foo", 'has error message "foo"'); + assert.end(); +}); + +test("TProtocolException", function t(assert) { + const e = new thrift.TProtocolException(1, "foo"); + assert.ok( + e instanceof thrift.TProtocolException, + "is instanceof TProtocolException" + ); + assert.ok(e instanceof Error, "is instanceof Error"); + assert.equal(typeof e.stack, "string", "has stack trace"); + assert.ok( + /^TProtocolException: foo/.test(e.stack), + "Stack trace has correct error name and message" + ); + assert.ok( + e.stack.indexOf("test/exceptions.js:33:11") !== -1, + "stack trace starts on correct line and column" + ); + assert.equal( + e.name, + "TProtocolException", + "has function name TProtocolException" + ); + assert.equal(e.message, "foo", 'has error message "foo"'); + assert.equal(e.type, 1, "has type 1"); + assert.end(); +}); + +test("InputBufferUnderrunError", function t(assert) { + const e = new InputBufferUnderrunError("foo"); + assert.ok( + e instanceof InputBufferUnderrunError, + "is instanceof InputBufferUnderrunError" + ); + assert.ok(e instanceof Error, "is instanceof Error"); + assert.equal(typeof e.stack, "string", "has stack trace"); + assert.ok( + /^InputBufferUnderrunError: foo/.test(e.stack), + "Stack trace has correct error name and message" + ); + assert.ok( + e.stack.indexOf("test/exceptions.js:46:11") !== -1, + "stack trace starts on correct line and column" + ); + assert.equal( + e.name, + "InputBufferUnderrunError", + "has function name InputBufferUnderrunError" + ); + assert.equal(e.message, "foo", 'has error message "foo"'); + assert.end(); +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/header.test.js b/src/jaegertracing/thrift/lib/nodejs/test/header.test.js new file mode 100644 index 000000000..efd7f81d5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/header.test.js @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const TFramedTransport = require("../lib/thrift/framed_transport"); +const THeaderTransport = require("../lib/thrift/header_transport"); +const THeaderProtocol = require("../lib/thrift/header_protocol"); +const thrift = require("../lib/thrift"); +const fs = require("fs"); +const test = require("tape"); +const path = require("path"); + +const headerPayload = fs.readFileSync( + path.join(__dirname, "test_header_payload") +); + +const cases = { + "Should read headers from payload": function(assert) { + const transport = new TFramedTransport(); + transport.inBuf = Buffer.from(headerPayload); + + const headers = transport.readHeaders(); + assert.equals(headers.Parent, "shoobar"); + assert.equals(headers.Trace, "abcde"); + assert.end(); + }, + "Should read headers when reading message begin": function(assert) { + const transport = new TFramedTransport(); + transport.inBuf = Buffer.from(headerPayload); + const protocol = new THeaderProtocol(transport); + const result = protocol.readMessageBegin(); + + const headers = transport.getReadHeaders(); + assert.equals(headers.Parent, "shoobar"); + assert.equals(headers.Trace, "abcde"); + assert.equals(result.fname, "add"); + assert.equals(result.mtype, thrift.Thrift.MessageType.CALL); + assert.end(); + }, + "Should be able to write headers": function(assert) { + const writeTransport = new TFramedTransport(); + writeTransport.setProtocolId(THeaderTransport.SubprotocolId.BINARY); + writeTransport.setWriteHeader("Hihihihi", "hohohoho"); + writeTransport.setWriteHeader("boobooboo", "fooshoopoo"); + writeTransport.setWriteHeader("a", "z"); + writeTransport.writeHeaders(); + const writeBuffer = writeTransport.outBuffers[0]; + + const readTransport = new TFramedTransport(); + readTransport.inBuf = writeBuffer; + readTransport.readHeaders(); + + const headers = readTransport.getReadHeaders(); + assert.equals(headers.Hihihihi, "hohohoho"); + assert.equals(headers.boobooboo, "fooshoopoo"); + assert.equals(headers.a, "z"); + assert.end(); + } +}; + +Object.keys(cases).forEach(function(caseName) { + test(caseName, cases[caseName]); +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/helpers.js b/src/jaegertracing/thrift/lib/nodejs/test/helpers.js new file mode 100644 index 000000000..f3c27b3d1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/helpers.js @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +"use strict"; +const thrift = require("../lib/thrift"); + +module.exports.transports = { + buffered: thrift.TBufferedTransport, + framed: thrift.TFramedTransport +}; + +module.exports.protocols = { + json: thrift.TJSONProtocol, + binary: thrift.TBinaryProtocol, + compact: thrift.TCompactProtocol, + header: thrift.THeaderProtocol +}; + +module.exports.ecmaMode = process.argv.includes("--es6") ? "es6" : "es5"; +module.exports.genPath = process.argv.includes("--es6") + ? "gen-nodejs-es6" + : "gen-nodejs"; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/int64.test.js b/src/jaegertracing/thrift/lib/nodejs/test/int64.test.js new file mode 100644 index 000000000..27ad28c00 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/int64.test.js @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const Int64 = require("node-int64"); +const JSONInt64 = require("json-int64"); +const i64types = require("./gen-nodejs-es6/Int64Test_types.js"); +const test = require("tape"); + +const cases = { + "should correctly generate Int64 constants": function(assert) { + const EXPECTED_SMALL_INT64_AS_NUMBER = 42; + const EXPECTED_SMALL_INT64 = new Int64(42); + const EXPECTED_MAX_JS_SAFE_INT64 = new Int64(Number.MAX_SAFE_INTEGER); + const EXPECTED_MIN_JS_SAFE_INT64 = new Int64(Number.MIN_SAFE_INTEGER); + const EXPECTED_MAX_JS_SAFE_PLUS_ONE_INT64 = new Int64("0020000000000000"); // hex-encoded + const EXPECTED_MIN_JS_SAFE_MINUS_ONE_INT64 = new Int64("ffe0000000000000"); // hex-encoded 2's complement + const EXPECTED_MAX_SIGNED_INT64 = new Int64("7fffffffffffffff"); // hex-encoded + const EXPECTED_MIN_SIGNED_INT64 = new Int64("8000000000000000"); // hex-encoded 2's complement + const EXPECTED_INT64_LIST = [ + EXPECTED_SMALL_INT64, + EXPECTED_MAX_JS_SAFE_INT64, + EXPECTED_MIN_JS_SAFE_INT64, + EXPECTED_MAX_JS_SAFE_PLUS_ONE_INT64, + EXPECTED_MIN_JS_SAFE_MINUS_ONE_INT64, + EXPECTED_MAX_SIGNED_INT64, + EXPECTED_MIN_SIGNED_INT64 + ]; + + assert.ok(EXPECTED_SMALL_INT64.equals(i64types.SMALL_INT64)); + assert.ok(EXPECTED_MAX_JS_SAFE_INT64.equals(i64types.MAX_JS_SAFE_INT64)); + assert.ok(EXPECTED_MIN_JS_SAFE_INT64.equals(i64types.MIN_JS_SAFE_INT64)); + assert.ok( + EXPECTED_MAX_JS_SAFE_PLUS_ONE_INT64.equals( + i64types.MAX_JS_SAFE_PLUS_ONE_INT64 + ) + ); + assert.ok( + EXPECTED_MIN_JS_SAFE_MINUS_ONE_INT64.equals( + i64types.MIN_JS_SAFE_MINUS_ONE_INT64 + ) + ); + assert.ok(EXPECTED_MAX_SIGNED_INT64.equals(i64types.MAX_SIGNED_INT64)); + assert.ok(EXPECTED_MIN_SIGNED_INT64.equals(i64types.MIN_SIGNED_INT64)); + assert.equal( + EXPECTED_SMALL_INT64_AS_NUMBER, + i64types.SMALL_INT64.toNumber() + ); + assert.equal( + Number.MAX_SAFE_INTEGER, + i64types.MAX_JS_SAFE_INT64.toNumber() + ); + assert.equal( + Number.MIN_SAFE_INTEGER, + i64types.MIN_JS_SAFE_INT64.toNumber() + ); + + for (let i = 0; i < EXPECTED_INT64_LIST.length; ++i) { + assert.ok(EXPECTED_INT64_LIST[i].equals(i64types.INT64_LIST[i])); + } + + for (let i = 0; i < EXPECTED_INT64_LIST.length; ++i) { + const int64Object = EXPECTED_INT64_LIST[i]; + assert.ok( + i64types.INT64_2_INT64_MAP[ + JSONInt64.toDecimalString(int64Object) + ].equals(int64Object) + ); + } + + assert.end(); + } +}; + +Object.keys(cases).forEach(function(caseName) { + test(caseName, cases[caseName]); +}); diff --git a/src/jaegertracing/thrift/lib/nodejs/test/server.crt b/src/jaegertracing/thrift/lib/nodejs/test/server.crt new file mode 100644 index 000000000..8a5ef3c3a --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/server.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENzCCAx+gAwIBAgIJAOYfYfw7NCOcMA0GCSqGSIb3DQEBBQUAMIGxMQswCQYD +VQQGEwJVUzERMA8GA1UECAwITWFyeWxhbmQxFDASBgNVBAcMC0ZvcmVzdCBIaWxs +MScwJQYDVQQKDB5UaGUgQXBhY2hlIFNvZnR3YXJlIEZvdW5kYXRpb24xFjAUBgNV +BAsMDUFwYWNoZSBUaHJpZnQxEjAQBgNVBAMMCWxvY2FsaG9zdDEkMCIGCSqGSIb3 +DQEJARYVZGV2QHRocmlmdC5hcGFjaGUub3JnMB4XDTE0MDQwNzE4NTgwMFoXDTIy +MDYyNDE4NTgwMFowgbExCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEU +MBIGA1UEBwwLRm9yZXN0IEhpbGwxJzAlBgNVBAoMHlRoZSBBcGFjaGUgU29mdHdh +cmUgRm91bmRhdGlvbjEWMBQGA1UECwwNQXBhY2hlIFRocmlmdDESMBAGA1UEAwwJ +bG9jYWxob3N0MSQwIgYJKoZIhvcNAQkBFhVkZXZAdGhyaWZ0LmFwYWNoZS5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqE9TE9wEXp5LRtLQVDSGQ +GV78+7ZtP/I/ZaJ6Q6ZGlfxDFvZjFF73seNhAvlKlYm/jflIHYLnNOCySN8I2Xw6 +L9MbC+jvwkEKfQo4eDoxZnOZjNF5J1/lZtBeOowMkhhzBMH1Rds351/HjKNg6ZKg +2Cldd0j7HbDtEixOLgLbPRpBcaYrLrNMasf3Hal+x8/b8ue28x93HSQBGmZmMIUw +AinEu/fNP4lLGl/0kZb76TnyRpYSPYojtS6CnkH+QLYnsRREXJYwD1Xku62LipkX +wCkRTnZ5nUsDMX6FPKgjQFQCWDXG/N096+PRUQAChhrXsJ+gF3NqWtDmtrhVQF4n +AgMBAAGjUDBOMB0GA1UdDgQWBBQo8v0wzQPx3EEexJPGlxPK1PpgKjAfBgNVHSME +GDAWgBQo8v0wzQPx3EEexJPGlxPK1PpgKjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 +DQEBBQUAA4IBAQBGFRiJslcX0aJkwZpzTwSUdgcfKbpvNEbCNtVohfQVTI4a/oN5 +U+yqDZJg3vOaOuiAZqyHcIlZ8qyesCgRN314Tl4/JQ++CW8mKj1meTgo5YFxcZYm +T9vsI3C+Nzn84DINgI9mx6yktIt3QOKZRDpzyPkUzxsyJ8J427DaimDrjTR+fTwD +1Dh09xeeMnSa5zeV1HEDyJTqCXutLetwQ/IyfmMBhIx+nvB5f67pz/m+Dv6V0r3I +p4HCcdnDUDGJbfqtoqsAATQQWO+WWuswB6mOhDbvPTxhRpZq6AkgWqv4S+u3M2GO +r5p9FrBgavAw5bKO54C0oQKpN/5fta5l6Ws0 +-----END CERTIFICATE----- diff --git a/src/jaegertracing/thrift/lib/nodejs/test/server.js b/src/jaegertracing/thrift/lib/nodejs/test/server.js new file mode 100644 index 000000000..7402094bc --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/server.js @@ -0,0 +1,137 @@ +#!/usr/bin/env node + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * 'License'); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fs = require("fs"); +const path = require("path"); +const thrift = require("../lib/thrift"); +const program = require("commander"); +const helpers = require("./helpers"); + +program + .option( + "-p, --protocol <protocol>", + "Set thrift protocol (binary|compact|json)", + "binary" + ) + .option( + "-t, --transport <transport>", + "Set thrift transport (buffered|framed|http)", + "buffered" + ) + .option("--ssl", "use ssl transport") + .option("--port <port>", "Set thrift server port", 9090) + .option("--domain-socket <path>", "Set thift server unix domain socket") + .option( + "-t, --type <type>", + "Select server type (http|multiplex|tcp|websocket)", + "tcp" + ) + .option("--callback", "test with callback style functions") + .option("--es6", "Use es6 code") + .option("--es5", "Use es5 code") + .parse(process.argv); + +const ThriftTest = require(`./${helpers.genPath}/ThriftTest`); +const SecondService = require(`./${helpers.genPath}/SecondService`); +const { ThriftTestHandler } = require("./test_handler"); + +const port = program.port; +const domainSocket = program.domainSocket; +const ssl = program.ssl; + +let type = program.type; +if (program.transport === "http") { + program.transport = "buffered"; + type = "http"; +} + +let options = { + transport: helpers.transports[program.transport], + protocol: helpers.protocols[program.protocol] +}; + +if (type === "http" || type === "websocket") { + options.handler = ThriftTestHandler; + options.processor = ThriftTest; + + options = { + services: { "/test": options }, + cors: { + "*": true + } + }; +} + +let processor; +if (type === "multiplex") { + const SecondServiceHandler = { + secondtestString: function(thing, result) { + console.log('testString("' + thing + '")'); + result(null, 'testString("' + thing + '")'); + } + }; + + processor = new thrift.MultiplexedProcessor(); + + processor.registerProcessor( + "ThriftTest", + new ThriftTest.Processor(ThriftTestHandler) + ); + + processor.registerProcessor( + "SecondService", + new SecondService.Processor(SecondServiceHandler) + ); +} + +if (ssl) { + if ( + type === "tcp" || + type === "multiplex" || + type === "http" || + type === "websocket" + ) { + options.tls = { + key: fs.readFileSync(path.resolve(__dirname, "server.key")), + cert: fs.readFileSync(path.resolve(__dirname, "server.crt")) + }; + } +} + +let server; +if (type === "tcp") { + server = thrift.createServer(ThriftTest, ThriftTestHandler, options); +} else if (type === "multiplex") { + server = thrift.createMultiplexServer(processor, options); +} else if (type === "http" || type === "websocket") { + server = thrift.createWebServer(options); +} + +if (domainSocket) { + server.listen(domainSocket); +} else if ( + type === "tcp" || + type === "multiplex" || + type === "http" || + type === "websocket" +) { + server.listen(port); +} diff --git a/src/jaegertracing/thrift/lib/nodejs/test/server.key b/src/jaegertracing/thrift/lib/nodejs/test/server.key new file mode 100644 index 000000000..263cfce59 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCqE9TE9wEXp5LR +tLQVDSGQGV78+7ZtP/I/ZaJ6Q6ZGlfxDFvZjFF73seNhAvlKlYm/jflIHYLnNOCy +SN8I2Xw6L9MbC+jvwkEKfQo4eDoxZnOZjNF5J1/lZtBeOowMkhhzBMH1Rds351/H +jKNg6ZKg2Cldd0j7HbDtEixOLgLbPRpBcaYrLrNMasf3Hal+x8/b8ue28x93HSQB +GmZmMIUwAinEu/fNP4lLGl/0kZb76TnyRpYSPYojtS6CnkH+QLYnsRREXJYwD1Xk +u62LipkXwCkRTnZ5nUsDMX6FPKgjQFQCWDXG/N096+PRUQAChhrXsJ+gF3NqWtDm +trhVQF4nAgMBAAECggEAW/y52YYW6ypROGbZ94DQpFV0kLO7qT8q0Ksxw5sPNaIt +fEPRIymDa8ikyHWJS5Oxmw84wo5jnJV26jaLmwe2Lupq7Xf1lqej8f5LJtuv7cQR +xfzp1vM65KJFFJHp6WqjGqJ6HSSZOpVDsnQYcXQjQCdpyAmaSWd3p+FqYSZ1mQmD +bFNI7jqpczWSZhTdotQ7p7Hn9TVCehflP3yGIB3bQ+wCcCB85dOBz201L+YgaIck +Sz43A4NvWaQIRLRDw7s9GW4jY5T0Jv282WIeAlVpVxLIwu48r4R4yGTIx9Ydowvq +57+Y5iPPjAXxu0V9t00oS3bYxDaKh2DUfc/5zowq8QKBgQDYNVPXmaG0aIH4vjQ9 +7fRdw/UDkYcQbn6CnglQOu77/S8ogQzpKCVJgJgkZNqOVtQMEPzekGEcLTbje1gU +8Bky2k+PL9UwbFy0emnOVh4rqrNXHsRvJcehNT/PRb5hjF3MUMFV/0iD4b+naFaE +jrSWiZ2ZXj2qfwAK52GFbtOuBQKBgQDJYQuGiY0r22E4waJmCSKczoBT3cwlVzWj +V2ljgA9RHLNTVkvNNYQLGu2qngFrtwpeaSnsMDerVG4wKAQWyCnYzxVrlnC4uDrJ +HXuFEltBWi9Ffbgfsnd3749AT0oBP1NT2tMleguyf5DFgjCR3VRJLdrVaaZ8row/ +LqKcFMqnOwKBgB+OIO99l7E584Y3VG6ZdSneOLtNmRXX2pT7tcZE465ZdHGH7Dd3 +SYHhx9K/+Xn+yDH+pLli/xlarAEldmSP6k2WuTfftlC78AfTOfAId5zN7CDR9791 +Fx67I9X/itq33tS8EIuZl57P6uXm/4GXRloWOa8xpvRkVsBApuYPl8t1AoGATQDS +y2sllDObBXzlgGbV2WgNIgSZ311toTv3jJiXQsjauW8yJRHln+l4H9mzaWDgkiFc +ang1kUoDqF5k0eFQPxtQcYdhKwEnWWfwp33RbzfxA32DPnubuzzbZhfrkHaKgnIW +cyor9uFYlm2l7ODZLfJez2RKyTplXnOSsmQw6akCgYAz3dj9Hskyj+HVJ+ht1OcE +c7ai/ESkSA7Vajp0tjJp0EKjW/zq8DvUSXOtcdnJgkKycFluLwbmnaN4txBds1C1 +Qr8Rt2sUCCBNZe1L6DHe3XBdbkJe9sgZVNTjtUSQrzy8UhvsCqG4YWeCu07Szcbc +rdPUV9/uQkdx8VrShxlD8A== +-----END PRIVATE KEY----- diff --git a/src/jaegertracing/thrift/lib/nodejs/test/test-cases.js b/src/jaegertracing/thrift/lib/nodejs/test/test-cases.js new file mode 100644 index 000000000..02c566fbf --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/test-cases.js @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +"use strict"; + +const helpers = require("./helpers"); +const ttypes = require(`./${helpers.genPath}/ThriftTest_types`); +const Int64 = require("node-int64"); + +//all Languages in UTF-8 +/*jshint -W100 */ +const stringTest = (module.exports.stringTest = + "Afrikaans, Alemannisch, Aragonés, العربية, مصرى, " + + "Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, " + + "Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, " + + "বাংলা, Brezhoneg, Bosanski, Català, Mìng-dĕ̤ng-ngṳ̄, Нохчийн, " + + "Cebuano, ᏣᎳᎩ, Česky, Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ, Чӑвашла, Cymraeg, " + + "Dansk, Zazaki, ދިވެހިބަސް, Ελληνικά, Emiliàn e rumagnòl, English, " + + "Esperanto, Español, Eesti, Euskara, فارسی, Suomi, Võro, Føroyskt, " + + "Français, Arpetan, Furlan, Frysk, Gaeilge, 贛語, Gàidhlig, Galego, " + + "Avañe'ẽ, ગુજરાતી, Gaelg, עברית, हिन्दी, Fiji Hindi, Hrvatski, " + + "Kreyòl ayisyen, Magyar, Հայերեն, Interlingua, Bahasa Indonesia, " + + "Ilokano, Ido, Íslenska, Italiano, 日本語, Lojban, Basa Jawa, " + + "ქართული, Kongo, Kalaallisut, ಕನ್ನಡ, 한국어, Къарачай-Малкъар, " + + "Ripoarisch, Kurdî, Коми, Kernewek, Кыргызча, Latina, Ladino, " + + "Lëtzebuergesch, Limburgs, Lingála, ລາວ, Lietuvių, Latviešu, Basa " + + "Banyumasan, Malagasy, Македонски, മലയാളം, मराठी, مازِرونی, Bahasa " + + "Melayu, Nnapulitano, Nedersaksisch, नेपाल भाषा, Nederlands, " + + "Norsk (nynorsk), Norsk (bokmål), Nouormand, Diné bizaad, " + + "Occitan, Иронау, Papiamentu, Deitsch, Polski, پنجابی, پښتو, " + + "Norfuk / Pitkern, Português, Runa Simi, Rumantsch, Romani, Română, " + + "Русский, Саха тыла, Sardu, Sicilianu, Scots, Sámegiella, Simple " + + "English, Slovenčina, Slovenščina, Српски / Srpski, Seeltersk, " + + "Svenska, Kiswahili, தமிழ், తెలుగు, Тоҷикӣ, ไทย, Türkmençe, Tagalog, " + + "Türkçe, Татарча/Tatarça, Українська, اردو, Tiếng Việt, Volapük, " + + "Walon, Winaray, 吴语, isiXhosa, ייִדיש, Yorùbá, Zeêuws, 中文, " + + "Bân-lâm-gú, 粵語"); +/*jshint +W100 */ + +const specialCharacters = (module.exports.specialCharacters = + 'quote: " backslash:' + + " forwardslash-escaped: / " + + " backspace: \b formfeed: \f newline: \n return: \r tab: " + + ' now-all-of-them-together: "\\/\b\n\r\t' + + " now-a-bunch-of-junk: !@#$%&()(&%$#{}{}<><><" + + ' char-to-test-json-parsing: ]] "]] \\" }}}{ [[[ '); + +const mapTestInput = (module.exports.mapTestInput = { + a: "123", + "a b": "with spaces ", + same: "same", + "0": "numeric key", + longValue: stringTest, + stringTest: "long key" +}); + +const simple = [ + ["testVoid", undefined], + ["testString", "Test"], + ["testString", ""], + ["testString", stringTest], + ["testString", specialCharacters], + ["testBool", true], + ["testBool", false], + ["testByte", 1], + ["testByte", 0], + ["testByte", -1], + ["testByte", -127], + ["testI32", -1], + ["testDouble", -5.2098523], + ["testDouble", 7.012052175215044], + ["testEnum", ttypes.Numberz.ONE], + ["testI64", 5], + ["testI64", -5], + ["testI64", 734359738368], + ["testI64", -734359738368], + ["testI64", new Int64(new Buffer([0, 0x20, 0, 0, 0, 0, 0, 1]))], // 2^53+1 + [ + "testI64", + new Int64(new Buffer([0xff, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])) + ], // -2^53-1 + ["testTypedef", 69] +]; + +const mapout = {}; +for (let i = 0; i < 5; ++i) { + mapout[i] = i - 10; +} + +const deep = [ + [ + "testList", + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + ] +]; + +const deepUnordered = [ + ["testMap", mapout], + ["testSet", [1, 2, 3]], + ["testStringMap", mapTestInput] +]; + +const out = new ttypes.Xtruct({ + string_thing: "Zero", + byte_thing: 1, + i32_thing: -3, + i64_thing: 1000000 +}); + +const out2 = new ttypes.Xtruct2(); +out2.byte_thing = 1; +out2.struct_thing = out; +out2.i32_thing = 5; + +const crazy = new ttypes.Insanity({ + userMap: { "5": 5, "8": 8 }, + xtructs: [ + new ttypes.Xtruct({ + string_thing: "Goodbye4", + byte_thing: 4, + i32_thing: 4, + i64_thing: 4 + }), + new ttypes.Xtruct({ + string_thing: "Hello2", + byte_thing: 2, + i32_thing: 2, + i64_thing: 2 + }) + ] +}); + +const crazy2 = new ttypes.Insanity({ + userMap: { "5": 5, "8": 8 }, + xtructs: [ + { + string_thing: "Goodbye4", + byte_thing: 4, + i32_thing: 4, + i64_thing: 4 + }, + { + string_thing: "Hello2", + byte_thing: 2, + i32_thing: 2, + i64_thing: 2 + } + ] +}); + +const insanity = { + "1": { "2": crazy, "3": crazy }, + "2": { "6": { userMap: {}, xtructs: [] } } +}; + +module.exports.simple = simple; +module.exports.deep = deep; +module.exports.deepUnordered = deepUnordered; + +module.exports.out = out; +module.exports.out2 = out2; +module.exports.crazy = crazy; +module.exports.crazy2 = crazy2; +module.exports.insanity = insanity; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/testAll.sh b/src/jaegertracing/thrift/lib/nodejs/test/testAll.sh new file mode 100755 index 000000000..3ae88b369 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/testAll.sh @@ -0,0 +1,151 @@ +#! /bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +if [ -n "${1}" ]; then + COVER=${1}; +fi + +DIR="$( cd "$( dirname "$0" )" && pwd )" + +EPISODIC_DIR=${DIR}/episodic-code-generation-test + +THRIFT_FILES_DIR=${DIR}/../../../test + +THRIFT_COMPILER=${DIR}/../../../compiler/cpp/thrift + +ISTANBUL="$DIR/../../../node_modules/istanbul/lib/cli.js" + +REPORT_PREFIX="${DIR}/../coverage/report" + +COUNT=0 + +export NODE_PATH="${DIR}:${DIR}/../lib:${NODE_PATH}" + +testServer() +{ + echo " [ECMA $1] Testing $2 Client/Server with protocol $3 and transport $4 $5"; + RET=0 + if [ -n "${COVER}" ]; then + ${ISTANBUL} cover ${DIR}/server.js --dir ${REPORT_PREFIX}${COUNT} --handle-sigint -- --type $2 -p $3 -t $4 $5 & + COUNT=$((COUNT+1)) + else + node ${DIR}/server.js --${1} --type $2 -p $3 -t $4 $5 & + fi + SERVERPID=$! + sleep 0.1 + if [ -n "${COVER}" ]; then + ${ISTANBUL} cover ${DIR}/client.js --dir ${REPORT_PREFIX}${COUNT} -- --${1} --type $2 -p $3 -t $4 $5 || RET=1 + COUNT=$((COUNT+1)) + else + node ${DIR}/client.js --${1} --type $2 -p $3 -t $4 $5 || RET=1 + fi + kill -2 $SERVERPID || RET=1 + wait $SERVERPID + return $RET +} + +testEpisodicCompilation() +{ + RET=0 + if [ -n "${COVER}" ]; then + ${ISTANBUL} cover ${EPISODIC_DIR}/server.js --dir ${REPORT_PREFIX}${COUNT} --handle-sigint & + COUNT=$((COUNT+1)) + else + node ${EPISODIC_DIR}/server.js & + fi + SERVERPID=$! + sleep 0.1 + if [ -n "${COVER}" ]; then + ${ISTANBUL} cover ${EPISODIC_DIR}/client.js --dir ${REPORT_PREFIX}${COUNT} || RET=1 + COUNT=$((COUNT+1)) + else + node ${EPISODIC_DIR}/client.js || RET=1 + fi + kill -2 $SERVERPID || RET=1 + wait $SERVERPID + return $RET +} + + +TESTOK=0 + +# generating Thrift code + +${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/ThriftTest.thrift +${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/JsDeepConstructorTest.thrift +${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/Int64Test.thrift +mkdir ${DIR}/gen-nodejs-es6 +${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/ThriftTest.thrift +${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/JsDeepConstructorTest.thrift +${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/Int64Test.thrift + +# generate episodic compilation test code +TYPES_PACKAGE=${EPISODIC_DIR}/node_modules/types-package + +# generate the first episode +mkdir --parents ${EPISODIC_DIR}/gen-1/first-episode +${THRIFT_COMPILER} -o ${EPISODIC_DIR}/gen-1/first-episode --gen js:node,thrift_package_output_directory=first-episode ${THRIFT_FILES_DIR}/Types.thrift + +# create a "package" from the first episode and "install" it, the episode file must be at the module root +mkdir --parents ${TYPES_PACKAGE}/first-episode +cp --force ${EPISODIC_DIR}/episodic_compilation.package.json ${TYPES_PACKAGE}/package.json +cp --force ${EPISODIC_DIR}/gen-1/first-episode/gen-nodejs/Types_types.js ${TYPES_PACKAGE}/first-episode/ +cp --force ${EPISODIC_DIR}/gen-1/first-episode/gen-nodejs/thrift.js.episode ${TYPES_PACKAGE} + +# generate the second episode +mkdir --parents ${EPISODIC_DIR}/gen-2/second-episode +${THRIFT_COMPILER} -o ${EPISODIC_DIR}/gen-2/second-episode --gen js:node,imports=${TYPES_PACKAGE} ${THRIFT_FILES_DIR}/Service.thrift +if [ -f ${EPISODIC_DIR}/gen-2/second-episode/Types_types.js ]; then + TESTOK=1 +fi + +# unit tests + +node ${DIR}/binary.test.js || TESTOK=1 +node ${DIR}/int64.test.js || TESTOK=1 +node ${DIR}/deep-constructor.test.js || TESTOK=1 + +# integration tests + +for type in tcp multiplex websocket http +do + for protocol in compact binary json + do + for transport in buffered framed + do + for ecma_version in es5 es6 + do + testServer $ecma_version $type $protocol $transport || TESTOK=1 + testServer $ecma_version $type $protocol $transport --ssl || TESTOK=1 + testServer $ecma_version $type $protocol $transport --callback || TESTOK=1 + done + done + done +done + +# episodic compilation test +testEpisodicCompilation + +if [ -n "${COVER}" ]; then + ${ISTANBUL} report --dir "${DIR}/../coverage" --include "${DIR}/../coverage/report*/coverage.json" lcov cobertura html + rm -r ${DIR}/../coverage/report*/* + rmdir ${DIR}/../coverage/report* +fi + +exit $TESTOK diff --git a/src/jaegertracing/thrift/lib/nodejs/test/test_driver.js b/src/jaegertracing/thrift/lib/nodejs/test/test_driver.js new file mode 100644 index 000000000..7c9a91914 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/test_driver.js @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * 'License'); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// This is the Node.js test driver for the standard Apache Thrift +// test service. The driver invokes every function defined in the +// Thrift Test service with a representative range of parameters. +// +// The ThriftTestDriver function requires a client object +// connected to a server hosting the Thrift Test service and +// supports an optional callback function which is called with +// a status message when the test is complete. + +const test = require("tape"); + +const helpers = require("./helpers"); +const ttypes = require(`./${helpers.genPath}/ThriftTest_types`); +const TException = require("thrift").Thrift.TException; +const Int64 = require("node-int64"); +const testCases = require("./test-cases"); + +exports.ThriftTestDriver = function(client, callback) { + test( + "NodeJS Style Callback Client Tests", + { skip: helpers.ecmaMode === "es6" }, + function(assert) { + const checkRecursively = makeRecursiveCheck(assert); + + function makeAsserter(assertionFn) { + return function(c) { + const fnName = c[0]; + const expected = c[1]; + client[fnName](expected, function(err, actual) { + assert.error(err, fnName + ": no callback error"); + assertionFn(actual, expected, fnName); + }); + }; + } + + testCases.simple.forEach( + makeAsserter(function(a, e, m) { + if (a instanceof Int64) { + const e64 = e instanceof Int64 ? e : new Int64(e); + assert.deepEqual(a.buffer, e64.buffer, m); + } else { + assert.equal(a, e, m); + } + }) + ); + testCases.deep.forEach(makeAsserter(assert.deepEqual)); + testCases.deepUnordered.forEach( + makeAsserter(makeUnorderedDeepEqual(assert)) + ); + + const arr = []; + for (let i = 0; i < 256; ++i) { + arr[i] = 255 - i; + } + let buf = new Buffer(arr); + client.testBinary(buf, function(err, response) { + assert.error(err, "testBinary: no callback error"); + assert.equal(response.length, 256, "testBinary"); + assert.deepEqual(response, buf, "testBinary(Buffer)"); + }); + buf = new Buffer(arr); + client.testBinary(buf.toString("binary"), function(err, response) { + assert.error(err, "testBinary: no callback error"); + assert.equal(response.length, 256, "testBinary"); + assert.deepEqual(response, buf, "testBinary(string)"); + }); + + client.testMapMap(42, function(err, response) { + const expected = { + "4": { "1": 1, "2": 2, "3": 3, "4": 4 }, + "-4": { "-4": -4, "-3": -3, "-2": -2, "-1": -1 } + }; + assert.error(err, "testMapMap: no callback error"); + assert.deepEqual(expected, response, "testMapMap"); + }); + + client.testStruct(testCases.out, function(err, response) { + assert.error(err, "testStruct: no callback error"); + checkRecursively(testCases.out, response, "testStruct"); + }); + + client.testNest(testCases.out2, function(err, response) { + assert.error(err, "testNest: no callback error"); + checkRecursively(testCases.out2, response, "testNest"); + }); + + client.testInsanity(testCases.crazy, function(err, response) { + assert.error(err, "testInsanity: no callback error"); + checkRecursively(testCases.insanity, response, "testInsanity"); + }); + + client.testInsanity(testCases.crazy2, function(err, response) { + assert.error(err, "testInsanity2: no callback error"); + checkRecursively(testCases.insanity, response, "testInsanity2"); + }); + + client.testException("TException", function(err, response) { + assert.ok( + err instanceof TException, + "testException: correct error type" + ); + assert.ok(!response, "testException: no response"); + }); + + client.testException("Xception", function(err, response) { + assert.ok( + err instanceof ttypes.Xception, + "testException: correct error type" + ); + assert.ok(!response, "testException: no response"); + assert.equal(err.errorCode, 1001, "testException: correct error code"); + assert.equal( + "Xception", + err.message, + "testException: correct error message" + ); + }); + + client.testException("no Exception", function(err, response) { + assert.error(err, "testException: no callback error"); + assert.ok(!response, "testException: no response"); + }); + + client.testOneway(0, function(err, response) { + assert.error(err, "testOneway: no callback error"); + assert.strictEqual(response, undefined, "testOneway: void response"); + }); + + checkOffByOne(function(done) { + client.testI32(-1, function(err, response) { + assert.error(err, "checkOffByOne: no callback error"); + assert.equal(-1, response); + assert.end(); + done(); + }); + }, callback); + } + ); + + // ES6 does not support callback style + if (helpers.ecmaMode === "es6") { + checkOffByOne(done => done(), callback); + } +}; + +exports.ThriftTestDriverPromise = function(client, callback) { + test("Promise Client Tests", function(assert) { + const checkRecursively = makeRecursiveCheck(assert); + + function makeAsserter(assertionFn) { + return function(c) { + const fnName = c[0]; + const expected = c[1]; + client[fnName](expected) + .then(function(actual) { + assertionFn(actual, expected, fnName); + }) + .catch(() => assert.fail("fnName")); + }; + } + + testCases.simple.forEach( + makeAsserter(function(a, e, m) { + if (a instanceof Int64) { + const e64 = e instanceof Int64 ? e : new Int64(e); + assert.deepEqual(a.buffer, e64.buffer, m); + } else { + assert.equal(a, e, m); + } + }) + ); + testCases.deep.forEach(makeAsserter(assert.deepEqual)); + testCases.deepUnordered.forEach( + makeAsserter(makeUnorderedDeepEqual(assert)) + ); + + client + .testStruct(testCases.out) + .then(function(response) { + checkRecursively(testCases.out, response, "testStruct"); + }) + .catch(() => assert.fail("testStruct")); + + client + .testNest(testCases.out2) + .then(function(response) { + checkRecursively(testCases.out2, response, "testNest"); + }) + .catch(() => assert.fail("testNest")); + + client + .testInsanity(testCases.crazy) + .then(function(response) { + checkRecursively(testCases.insanity, response, "testInsanity"); + }) + .catch(() => assert.fail("testInsanity")); + + client + .testInsanity(testCases.crazy2) + .then(function(response) { + checkRecursively(testCases.insanity, response, "testInsanity2"); + }) + .catch(() => assert.fail("testInsanity2")); + + client + .testException("TException") + .then(function() { + assert.fail("testException: TException"); + }) + .catch(function(err) { + assert.ok(err instanceof TException); + }); + + client + .testException("Xception") + .then(function() { + assert.fail("testException: Xception"); + }) + .catch(function(err) { + assert.ok(err instanceof ttypes.Xception); + assert.equal(err.errorCode, 1001); + assert.equal("Xception", err.message); + }); + + client + .testException("no Exception") + .then(function(response) { + assert.equal(undefined, response); //void + }) + .catch(() => assert.fail("testException")); + + client + .testOneway(0) + .then(function(response) { + assert.strictEqual(response, undefined, "testOneway: void response"); + }) + .catch(() => assert.fail("testOneway: should not reject")); + + checkOffByOne(function(done) { + client + .testI32(-1) + .then(function(response) { + assert.equal(-1, response); + assert.end(); + done(); + }) + .catch(() => assert.fail("checkOffByOne")); + }, callback); + }); +}; + +// Helper Functions +// ========================================================= + +function makeRecursiveCheck(assert) { + return function(map1, map2, msg) { + const equal = checkRecursively(map1, map2); + + assert.ok(equal, msg); + + // deepEqual doesn't work with fields using node-int64 + function checkRecursively(map1, map2) { + if (typeof map1 !== "function" && typeof map2 !== "function") { + if (!map1 || typeof map1 !== "object") { + //Handle int64 types (which use node-int64 in Node.js JavaScript) + if ( + typeof map1 === "number" && + typeof map2 === "object" && + map2.buffer && + map2.buffer instanceof Buffer && + map2.buffer.length === 8 + ) { + const n = new Int64(map2.buffer); + return map1 === n.toNumber(); + } else { + return map1 == map2; + } + } else { + return Object.keys(map1).every(function(key) { + return checkRecursively(map1[key], map2[key]); + }); + } + } + } + }; +} + +function checkOffByOne(done, callback) { + const retry_limit = 30; + const retry_interval = 100; + let test_complete = false; + let retrys = 0; + + /** + * redo a simple test after the oneway to make sure we aren't "off by one" -- + * if the server treated oneway void like normal void, this next test will + * fail since it will get the void confirmation rather than the correct + * result. In this circumstance, the client will throw the exception: + * + * Because this is the last test against the server, when it completes + * the entire suite is complete by definition (the tests run serially). + */ + done(function() { + test_complete = true; + }); + + //We wait up to retry_limit * retry_interval for the test suite to complete + function TestForCompletion() { + if (test_complete && callback) { + callback("Server successfully tested!"); + } else { + if (++retrys < retry_limit) { + setTimeout(TestForCompletion, retry_interval); + } else if (callback) { + callback( + "Server test failed to complete after " + + (retry_limit * retry_interval) / 1000 + + " seconds" + ); + } + } + } + + setTimeout(TestForCompletion, retry_interval); +} + +function makeUnorderedDeepEqual(assert) { + return function(actual, expected, name) { + assert.equal(actual.length, expected.length, name); + for (const k in actual) { + let found = false; + for (const k2 in expected) { + if (actual[k] === expected[k2]) { + found = true; + } + } + if (!found) { + assert.fail("Unexpected value " + actual[k] + " with key " + k); + } + } + }; +} diff --git a/src/jaegertracing/thrift/lib/nodejs/test/test_handler.js b/src/jaegertracing/thrift/lib/nodejs/test/test_handler.js new file mode 100644 index 000000000..317a7c810 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/test_handler.js @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * 'License'); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//This is the server side Node test handler for the standard +// Apache Thrift test service. +const helpers = require("./helpers"); +const ttypes = require(`./${helpers.genPath}/ThriftTest_types`); +const TException = require("thrift").Thrift.TException; + +function makeSyncHandler() { + return function(thing) { + return thing; + }; +} + +const syncHandlers = { + testVoid: testVoid, + testMapMap: testMapMap, + testInsanity: testInsanity, + testMulti: testMulti, + testException: testException, + testMultiException: testMultiException, + testOneway: testOneway +}; + +function makeAsyncHandler(label) { + return function(thing, result) { + thing = syncHandlers[label](thing); + result(null, thing); + }; +} + +const asyncHandlers = { + testVoid: testVoidAsync, + testMulti: testMultiAsync, + testException: testExceptionAsync, + testMultiException: testMultiExceptionAsync, + testOneway: testOnewayAsync +}; + +const identityHandlers = [ + "testString", + "testBool", + "testByte", + "testI32", + "testI64", + "testDouble", + "testBinary", + "testStruct", + "testNest", + "testMap", + "testStringMap", + "testSet", + "testList", + "testEnum", + "testTypedef" +]; + +function testVoid() { + //console.log('testVoid()'); +} + +function testVoidAsync(result) { + result(testVoid()); +} + +function testMapMap() { + const mapmap = []; + const pos = []; + const neg = []; + for (let i = 1; i < 5; i++) { + pos[i] = i; + neg[-i] = -i; + } + mapmap[4] = pos; + mapmap[-4] = neg; + + return mapmap; +} + +function testInsanity(argument) { + //console.log('testInsanity('); + //console.log(argument); + //console.log(')'); + + const first_map = []; + const second_map = []; + + first_map[ttypes.Numberz.TWO] = argument; + first_map[ttypes.Numberz.THREE] = argument; + + const looney = new ttypes.Insanity(); + second_map[ttypes.Numberz.SIX] = looney; + + const insane = []; + insane[1] = first_map; + insane[2] = second_map; + + //console.log('insane result:'); + //console.log(insane); + return insane; +} + +function testMulti(arg0, arg1, arg2) { + //console.log('testMulti()'); + + const hello = new ttypes.Xtruct(); + hello.string_thing = "Hello2"; + hello.byte_thing = arg0; + hello.i32_thing = arg1; + hello.i64_thing = arg2; + return hello; +} + +function testMultiAsync(arg0, arg1, arg2, arg3, arg4, arg5, result) { + const hello = testMulti(arg0, arg1, arg2, arg3, arg4, arg5); + result(null, hello); +} + +function testException(arg) { + //console.log('testException('+arg+')'); + if (arg === "Xception") { + const x = new ttypes.Xception(); + x.errorCode = 1001; + x.message = arg; + throw x; + } else if (arg === "TException") { + throw new TException(arg); + } else { + return; + } +} + +function testExceptionAsync(arg, result) { + //console.log('testException('+arg+')'); + if (arg === "Xception") { + const x = new ttypes.Xception(); + x.errorCode = 1001; + x.message = arg; + result(x); + } else if (arg === "TException") { + result(new TException(arg)); + } else { + result(null); + } +} + +function testMultiException(arg0, arg1) { + //console.log('testMultiException(' + arg0 + ', ' + arg1 + ')'); + if (arg0 === "Xception") { + const x = new ttypes.Xception(); + x.errorCode = 1001; + x.message = "This is an Xception"; + throw x; + } else if (arg0 === "Xception2") { + const x2 = new ttypes.Xception2(); + x2.errorCode = 2002; + x2.struct_thing = new ttypes.Xtruct(); + x2.struct_thing.string_thing = "This is an Xception2"; + throw x2; + } + + const res = new ttypes.Xtruct(); + res.string_thing = arg1; + return res; +} + +function testMultiExceptionAsync(arg0, arg1, result) { + //console.log('testMultiException(' + arg0 + ', ' + arg1 + ')'); + if (arg0 === "Xception") { + const x = new ttypes.Xception(); + x.errorCode = 1001; + x.message = "This is an Xception"; + result(x); + } else if (arg0 === "Xception2") { + const x2 = new ttypes.Xception2(); + x2.errorCode = 2002; + x2.struct_thing = new ttypes.Xtruct(); + x2.struct_thing.string_thing = "This is an Xception2"; + result(x2); + } else { + const res = new ttypes.Xtruct(); + res.string_thing = arg1; + result(null, res); + } +} + +//console.log('testOneway(' + sleepFor + ') => JavaScript (like Rust) never sleeps!'); +function testOneway() {} + +function testOnewayAsync(sleepFor) { + testOneway(sleepFor); +} + +identityHandlers.forEach(function(label) { + syncHandlers[label] = makeSyncHandler(label); + asyncHandlers[label] = makeAsyncHandler(label); +}); + +["testMapMap", "testInsanity"].forEach(function(label) { + asyncHandlers[label] = makeAsyncHandler(label); +}); + +exports.ThriftTestHandler = asyncHandlers; diff --git a/src/jaegertracing/thrift/lib/nodejs/test/test_header_payload b/src/jaegertracing/thrift/lib/nodejs/test/test_header_payload Binary files differnew file mode 100644 index 000000000..22d5ef7a2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/nodejs/test/test_header_payload |