From 0c124bb94f86eead61ef1c65dc6b38f5f60076f9 Mon Sep 17 00:00:00 2001 From: T Jake Luciani Date: Sat, 8 Jan 2011 03:49:16 +0000 Subject: [PATCH] THRIFT-1033: node.js target and lib git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1056613 13f79535-47bb-0310-9956-ffa450edef68 --- compiler/cpp/src/generate/t_js_generator.cc | 410 ++++++++++++++++---- lib/nodejs/README.md | 60 +++ lib/nodejs/examples/Makefile | 18 + lib/nodejs/examples/README.md | 29 ++ lib/nodejs/examples/client.js | 49 +++ lib/nodejs/examples/server.js | 39 ++ lib/nodejs/examples/user.thrift | 27 ++ lib/nodejs/lib/thrift/binary_parser.js | 274 +++++++++++++ lib/nodejs/lib/thrift/connection.js | 150 +++++++ lib/nodejs/lib/thrift/index.js | 26 ++ lib/nodejs/lib/thrift/protocol.js | 337 ++++++++++++++++ lib/nodejs/lib/thrift/server.js | 51 +++ lib/nodejs/lib/thrift/thrift.js | 130 +++++++ lib/nodejs/lib/thrift/transport.js | 87 +++++ lib/nodejs/package.json | 27 ++ 15 files changed, 1643 insertions(+), 71 deletions(-) create mode 100644 lib/nodejs/README.md create mode 100644 lib/nodejs/examples/Makefile create mode 100644 lib/nodejs/examples/README.md create mode 100644 lib/nodejs/examples/client.js create mode 100644 lib/nodejs/examples/server.js create mode 100644 lib/nodejs/examples/user.thrift create mode 100644 lib/nodejs/lib/thrift/binary_parser.js create mode 100644 lib/nodejs/lib/thrift/connection.js create mode 100644 lib/nodejs/lib/thrift/index.js create mode 100644 lib/nodejs/lib/thrift/protocol.js create mode 100644 lib/nodejs/lib/thrift/server.js create mode 100644 lib/nodejs/lib/thrift/thrift.js create mode 100644 lib/nodejs/lib/thrift/transport.js create mode 100644 lib/nodejs/package.json diff --git a/compiler/cpp/src/generate/t_js_generator.cc b/compiler/cpp/src/generate/t_js_generator.cc index bb1033cf..4aa68116 100644 --- a/compiler/cpp/src/generate/t_js_generator.cc +++ b/compiler/cpp/src/generate/t_js_generator.cc @@ -41,9 +41,18 @@ class t_js_generator : public t_oop_generator { const std::map& parsed_options, const std::string& option_string) : t_oop_generator(program) { - (void) parsed_options; (void) option_string; - out_dir_base_ = "gen-js"; + + std::map::const_iterator iter; + + iter = parsed_options.find("node"); + gen_node_ = (iter != parsed_options.end()); + + if (gen_node_) { + out_dir_base_ = "gen-nodejs"; + } else { + out_dir_base_ = "gen-js"; + } } /** @@ -64,6 +73,8 @@ class t_js_generator : public t_oop_generator { void generate_xception (t_struct* txception); void generate_service (t_service* tservice); + std::string render_recv_throw(std::string var); + std::string render_recv_return(std::string var); std::string render_const_value(t_type* type, t_const_value* value); @@ -72,7 +83,7 @@ class t_js_generator : public t_oop_generator { * Structs! */ void generate_js_struct(t_struct* tstruct, bool is_exception); - void generate_js_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false); + void generate_js_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false, bool is_exported=true); void generate_js_struct_reader(std::ofstream& out, t_struct* tstruct); void generate_js_struct_writer(std::ofstream& out, t_struct* tstruct); void generate_js_function_helpers(t_function* tfunction); @@ -147,7 +158,7 @@ class t_js_generator : public t_oop_generator { std::string js_includes(); std::string declare_field(t_field* tfield, bool init=false, bool obj=false); - std::string function_signature(t_function* tfunction, std::string prefix=""); + std::string function_signature(t_function* tfunction, std::string prefix="", bool include_callback=false); std::string argument_list(t_struct* tstruct); std::string type_to_enum(t_type* ttype); @@ -180,6 +191,20 @@ class t_js_generator : public t_oop_generator { return pieces; } + std::string js_type_namespace(t_program* p) { + if (gen_node_) { + return "ttypes."; + } + return js_namespace(p); + } + + std::string js_export_namespace(t_program* p) { + if (gen_node_) { + return "exports."; + } + return js_namespace(p); + } + std::string js_namespace(t_program* p) { std::string ns = p->get_namespace("js"); if (ns.size() > 0) { @@ -192,6 +217,11 @@ class t_js_generator : public t_oop_generator { private: + /** + * True iff we should generate NodeJS-friendly RPC services. + */ + bool gen_node_; + /** * File streams */ @@ -219,12 +249,16 @@ void t_js_generator::init_generator() { // Print header f_types_ << autogen_comment() << - js_includes(); + js_includes() << endl; + if (gen_node_) { + f_types_ << "var ttypes = module.exports = {};" << endl; + } string pns; //setup the namespace + // TODO should the namespace just be in the directory structure for node? vector ns_pieces = js_namespace_pieces( program_ ); if( ns_pieces.size() > 0){ f_types_ << "var " << ns_pieces[0] << " = {}"<get_program())<get_name()<<" = { "<get_program())<get_name()<<" = { "< constants = tenum->get_constants(); vector::iterator c_iter; @@ -298,7 +334,7 @@ void t_js_generator::generate_const(t_const* tconst) { string name = tconst->get_name(); t_const_value* value = tconst->get_value(); - f_types_ << js_namespace(program_) << name << " = "; + f_types_ << js_type_namespace(program_) << name << " = "; f_types_ << render_const_value(type, value) << endl; } @@ -340,7 +376,7 @@ string t_js_generator::render_const_value(t_type* type, t_const_value* value) { } else if (type->is_enum()) { out << value->get_integer(); } else if (type->is_struct() || type->is_xception()) { - out << "new " << js_namespace(type->get_program()) << type->get_name() << "({" << endl; + out << "new " << js_type_namespace(type->get_program()) << type->get_name() << "({" << endl; indent_up(); const vector& fields = ((t_struct*)type)->get_members(); vector::const_iterator f_iter; @@ -441,16 +477,34 @@ void t_js_generator::generate_js_struct(t_struct* tstruct, */ void t_js_generator::generate_js_struct_definition(ofstream& out, t_struct* tstruct, - bool is_exception) { + bool is_exception, + bool is_exported) { const vector& members = tstruct->get_members(); vector::const_iterator m_iter; - out << js_namespace(tstruct->get_program()) << tstruct->get_name() <<" = function(args){\n"; + indent_up(); + if (gen_node_) { + if (is_exported) { + out << "var " << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = " << + "module.exports." << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = function(args){\n"; + } else { + out << "var " << js_namespace(tstruct->get_program()) << tstruct->get_name() << " = function(args){\n"; + } + } else { + out << js_namespace(tstruct->get_program()) << tstruct->get_name() <<" = function(args){\n"; + } + + if (gen_node_ && is_exception) { + out << indent() << "Thrift.TException.call(this, \"" << + js_namespace(tstruct->get_program()) << tstruct->get_name() << "\")" << endl; + out << indent() << "this.name = \"" << + js_namespace(tstruct->get_program()) << tstruct->get_name() << "\"" << endl; + } //members with arguments for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { - string dval = declare_field(*m_iter,true,true); + string dval = declare_field(*m_iter,false,true); t_type* t = get_true_type((*m_iter)->get_type()); if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) { dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value()); @@ -487,12 +541,20 @@ void t_js_generator::generate_js_struct_definition(ofstream& out, out << "}\n"; if (is_exception) { - out << "for (var property in Thrift.Exception)"<get_program())<get_name()<<"[property] = Thrift.Exception[property]"<get_program()) << + tstruct->get_name() << ", Thrift.TException)" << endl; + } else { + out << "for (var property in Thrift.TException)"<get_program())<get_name()<<"[property] = Thrift.TException[property]"<get_program())<get_name() <<".prototype = {}\n"; + if (!gen_node_) { + //init prototype + out << js_namespace(tstruct->get_program())<get_name() <<".prototype = {}\n"; + } generate_js_struct_reader(out, tstruct); @@ -632,10 +694,23 @@ void t_js_generator::generate_service(t_service* tservice) { string f_service_name = get_out_dir()+service_name_+".js"; f_service_.open(f_service_name.c_str()); + f_service_ << + autogen_comment() << + js_includes() << endl; + + if (gen_node_) { + f_service_ << + "var ttypes = require('./" + program_->get_name() + "_types.js');" << endl; + } + generate_service_helpers(tservice); generate_service_interface(tservice); generate_service_client(tservice); + if (gen_node_) { + generate_service_processor(tservice); + } + f_service_.close(); } @@ -645,7 +720,45 @@ void t_js_generator::generate_service(t_service* tservice) { * @param tservice The service to generate a server for. */ void t_js_generator::generate_service_processor(t_service* tservice) { - (void) tservice; + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + f_service_ << + "var " << js_namespace(tservice->get_program()) << service_name_ << "Processor = " << + "exports.Processor = function(handler) "; + + scope_up(f_service_); + + f_service_ << indent() << "this._handler = handler" << endl; + + scope_down(f_service_); + + // Generate the server implementation + indent(f_service_) << + js_namespace(tservice->get_program()) << service_name_ << "Processor.prototype.process = function(input, output) "; + + scope_up(f_service_); + + f_service_ << indent() << "var r = input.readMessageBegin()" << endl + << indent() << "if (this['process_' + r.fname]) {" << endl + << indent() << " return this['process_' + r.fname].call(this, r.rseqid, input, output)" << endl + << indent() << "} else {" << endl + << indent() << " input.skip(Thrift.Type.STRUCT)" << endl + << indent() << " input.readMessageEnd()" << endl + << indent() << " var x = new Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, 'Unknown function ' + r.fname)" << endl + << indent() << " output.writeMessageBegin(r.fname, Thrift.MessageType.Exception, r.rseqid)" << endl + << indent() << " x.write(output)" << endl + << indent() << " output.writeMessageEnd()" << endl + << indent() << " output.flush()" << endl + << indent() << "}" << endl; + + scope_down(f_service_); + f_service_ << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } } /** @@ -655,8 +768,70 @@ void t_js_generator::generate_service_processor(t_service* tservice) { */ void t_js_generator::generate_process_function(t_service* tservice, t_function* tfunction) { - (void) tservice; - (void) tfunction; + indent(f_service_) << + js_namespace(tservice->get_program()) << service_name_ << "Processor.prototype.process_" + tfunction->get_name() + " = function(seqid, input, output) "; + + scope_up(f_service_); + + string argsname = js_namespace(program_)+ service_name_ + "_" + tfunction->get_name() + "_args"; + string resultname = js_namespace(program_)+ service_name_ + "_" + tfunction->get_name() + "_result"; + + f_service_ << + indent() << "var args = new " << argsname << "()" << endl << + indent() << "args.read(input)" << endl << + indent() << "input.readMessageEnd()" << endl; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << + indent() << "var result = new " << resultname << "()" << endl; + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector& fields = arg_struct->get_members(); + vector::const_iterator f_iter; + + f_service_ << + indent() << "this._handler." << tfunction->get_name() << "("; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << ")" << endl; + scope_down(f_service_); + f_service_ << endl; + return; + } + + if (!first) { + f_service_ << ", "; + } + f_service_ << "function(success) {" << endl; + indent_up(); + + f_service_ << + indent() << "result.success = success" << endl << + indent() << "output.writeMessageBegin(\"" << tfunction->get_name() << + "\", Thrift.MessageType.REPLY, seqid)" << endl << + indent() << "result.write(output)" << endl << + indent() << "output.writeMessageEnd()" << endl << + indent() << "output.flush()" << endl; + + indent_down(); + indent(f_service_) << "})" << endl; + + scope_down(f_service_); + f_service_ << endl; } /** @@ -675,7 +850,7 @@ void t_js_generator::generate_service_helpers(t_service* tservice) { t_struct* ts = (*f_iter)->get_arglist(); string name = ts->get_name(); ts->set_name(service_name_ + "_" + name); - generate_js_struct_definition(f_service_, ts, false); + generate_js_struct_definition(f_service_, ts, false, false); generate_js_function_helpers(*f_iter); ts->set_name(name); } @@ -700,7 +875,7 @@ void t_js_generator::generate_js_function_helpers(t_function* tfunction) { result.append(*f_iter); } - generate_js_struct_definition(f_service_, &result, false); + generate_js_struct_definition(f_service_, &result, false, false); } /** @@ -727,16 +902,31 @@ void t_js_generator::generate_service_rest(t_service* tservice) { void t_js_generator::generate_service_client(t_service* tservice) { string extends = ""; - f_service_ << - js_namespace(tservice->get_program()) << service_name_ << "Client = function(input, output) {"<get_program()) << service_name_ << "Client = " << + "exports.Client = function(output, pClass) {"<get_program()) << service_name_ << "Client = function(input, output) {"<get_name(); // Open function - f_service_ << js_namespace(tservice->get_program())<get_program())<is_oneway()) { + if (!gen_node_ && !(*f_iter)->is_oneway()) { f_service_ << indent(); if (!(*f_iter)->get_returntype()->is_void()) { f_service_ << "return "; @@ -802,11 +998,20 @@ void t_js_generator::generate_service_client(t_service* tservice) { indent_up(); + std::string outputVar; + if (gen_node_) { + f_service_ << + indent() << "var output = new this.pClass(this.output);" << endl; + outputVar = "output"; + } else { + outputVar = "this.output"; + } + std::string argsname = js_namespace(program_)+ service_name_ + "_" + (*f_iter)->get_name() + "_args"; // Serialize the request header f_service_ << - indent() << "this.output.writeMessageBegin('" << (*f_iter)->get_name() << "', Thrift.MessageType.CALL, this.seqid)" << endl; + indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() << "', Thrift.MessageType.CALL, this.seqid)" << endl; f_service_ << indent() << "var args = new " << argsname << "()" << endl; @@ -818,9 +1023,14 @@ void t_js_generator::generate_service_client(t_service* tservice) { // Write to the stream f_service_ << - indent() << "args.write(this.output)" << endl << - indent() << "this.output.writeMessageEnd()" << endl << - indent() << "return this.output.getTransport().flush()" << endl; + indent() << "args.write(" << outputVar << ")" << endl << + indent() << outputVar << ".writeMessageEnd()" << endl; + + if (gen_node_) { + f_service_ << indent() << "return this.output.flush()" << endl; + } else { + f_service_ << indent() << "return this.output.getTransport().flush()" << endl; + } indent_down(); @@ -830,66 +1040,88 @@ void t_js_generator::generate_service_client(t_service* tservice) { if (!(*f_iter)->is_oneway()) { std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_" + (*f_iter)->get_name() + "_result"; - t_struct noargs(program_); - t_function recv_function((*f_iter)->get_returntype(), - string("recv_") + (*f_iter)->get_name(), - &noargs); - // Open function - f_service_ << - endl << js_namespace(tservice->get_program())<get_program())<get_name() << " = function(input,mtype,rseqid){" << endl; + } else { + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + // Open function + f_service_ << + endl << js_namespace(tservice->get_program())<get_returntype()->is_void()) { - f_service_ << - indent() << "if (null != result.success ) {" << endl << - indent() << " return result.success" << endl << - indent() << "}" << endl; - } - t_struct* xs = (*f_iter)->get_xceptions(); const std::vector& xceptions = xs->get_members(); vector::const_iterator x_iter; for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { f_service_ << indent() << "if (null != result." << (*x_iter)->get_name() << ") {" << endl << - indent() << " throw result." << (*x_iter)->get_name() << endl << + indent() << " " << render_recv_throw("result." + (*x_iter)->get_name()) << endl << indent() << "}" << endl; } - // Careful, only return _result if not a void function - if ((*f_iter)->get_returntype()->is_void()) { - indent(f_service_) << - "return" << endl; - } else { + // Careful, only return result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { f_service_ << - indent() << "throw \"" << (*f_iter)->get_name() << " failed: unknown result\"" << endl; + indent() << "if (null != result.success ) {" << endl << + indent() << " " << render_recv_return("result.success") << endl << + indent() << "}" << endl; + f_service_ << + indent() << render_recv_throw("\"" + (*f_iter)->get_name() + " failed: unknown result\"") << endl; + } else { + if (gen_node_) { + indent(f_service_) << "callback(null)" << endl; + } else { + indent(f_service_) << "return" << endl; + } } // Close function @@ -901,6 +1133,22 @@ void t_js_generator::generate_service_client(t_service* tservice) { } +std::string t_js_generator::render_recv_throw(std::string var) { + if (gen_node_) { + return "return callback(" + var + ");"; + } else { + return "throw " + var; + } +} + +std::string t_js_generator::render_recv_return(std::string var) { + if (gen_node_) { + return "return callback(null, " + var + ");"; + } else { + return "return " + var; + } +} + /** * Deserializes a field of any type. */ @@ -925,7 +1173,7 @@ void t_js_generator::generate_deserialize_field(ofstream &out, } else if (type->is_container()) { generate_deserialize_container(out, type, name); } else if (type->is_base_type() || type->is_enum()) { - indent(out) << "var rtmp = input."; + indent(out) << name << " = input."; if (type->is_base_type()) { t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); @@ -961,11 +1209,12 @@ void t_js_generator::generate_deserialize_field(ofstream &out, } else if (type->is_enum()) { out << "readI32()"; } - out << endl; - - out <get_name().c_str(), type->get_name().c_str()); @@ -982,7 +1231,7 @@ void t_js_generator::generate_deserialize_struct(ofstream &out, t_struct* tstruct, string prefix) { out << - indent() << prefix << " = new " << js_namespace(tstruct->get_program())<get_name() << "()" << endl << + indent() << prefix << " = new " << js_type_namespace(tstruct->get_program())<get_name() << "()" << endl << indent() << prefix << ".read(input)" << endl; } @@ -1082,9 +1331,9 @@ void t_js_generator::generate_deserialize_map_element(ofstream &out, t_field fval(tmap->get_val_type(), val); indent(out) << - declare_field(&fkey, true, false) << endl; + declare_field(&fkey, false, false) << endl; indent(out) << - declare_field(&fval, true, false) << endl; + declare_field(&fval, false, false) << endl; generate_deserialize_field(out, &fkey); generate_deserialize_field(out, &fval); @@ -1252,9 +1501,12 @@ void t_js_generator::generate_serialize_container(ofstream &out, string viter = tmp("viter"); indent(out) << "for(var "<is_set()) { @@ -1262,9 +1514,12 @@ void t_js_generator::generate_serialize_container(ofstream &out, indent(out) << "for(var "<is_list()) { @@ -1272,9 +1527,12 @@ void t_js_generator::generate_serialize_container(ofstream &out, indent(out) << "for(var "<is_struct() || type->is_xception()) { if (obj) { - result += " = new " +js_namespace(type->get_program()) + type->get_name() + "()"; + result += " = new " +js_type_namespace(type->get_program()) + type->get_name() + "()"; } else { result += " = null"; } } + } else { + result += " = null"; } return result; } @@ -1383,7 +1643,8 @@ string t_js_generator::declare_field(t_field* tfield, bool init, bool obj) { * @return String of rendered function definition */ string t_js_generator::function_signature(t_function* tfunction, - string prefix) { + string prefix, + bool include_callback) { string str; @@ -1402,6 +1663,12 @@ string t_js_generator::function_signature(t_function* tfunction, str += (*f_iter)->get_name(); } + if (include_callback) { + if (!fields.empty()) { + str += ","; + } + str += "callback"; + } str += ")"; return str; @@ -1469,5 +1736,6 @@ string t_js_generator ::type_to_enum(t_type* type) { } -THRIFT_REGISTER_GENERATOR(js, "Javascript", "") +THRIFT_REGISTER_GENERATOR(js, "Javascript", +" node: Generate node.js compatible code.\n") diff --git a/lib/nodejs/README.md b/lib/nodejs/README.md new file mode 100644 index 00000000..2832c1bb --- /dev/null +++ b/lib/nodejs/README.md @@ -0,0 +1,60 @@ +# 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. + + +NOTE: you must use the framed thrift transport, TFramedTransport in most +implementations, on the server side. Using a popular example, this is enabled +by default in Cassandra 0.7 (but configuration must be changed in Cassandra +0.6.x and earlier). + +## Install + + npm install thrift + +## Thrift Compiler + +You can compile nodejs sources by running the following: + + thrift --gen js:node thrift_file + +## Cassandra Client Example: + +Here is a Cassandra example: + + 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(); + }); + +## Custom client and server example + +An example based on the one shown on the Thrift front page is included in the examples/ folder. diff --git a/lib/nodejs/examples/Makefile b/lib/nodejs/examples/Makefile new file mode 100644 index 00000000..1930279a --- /dev/null +++ b/lib/nodejs/examples/Makefile @@ -0,0 +1,18 @@ +# 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 diff --git a/lib/nodejs/examples/README.md b/lib/nodejs/examples/README.md new file mode 100644 index 00000000..a87581fb --- /dev/null +++ b/lib/nodejs/examples/README.md @@ -0,0 +1,29 @@ +# 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 + +#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 + + + diff --git a/lib/nodejs/examples/client.js b/lib/nodejs/examples/client.js new file mode 100644 index 00000000..c83b3423 --- /dev/null +++ b/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/lib/nodejs/examples/server.js b/lib/nodejs/examples/server.js new file mode 100644 index 00000000..3b8c0464 --- /dev/null +++ b/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, success) { + console.log("server stored:", user.uid); + users[user.uid] = user; + success(); + }, + + retrieve: function(uid, success) { + console.log("server retrieved:", uid); + success(users[uid]); + }, +}); + +server.listen(9090); diff --git a/lib/nodejs/examples/user.thrift b/lib/nodejs/examples/user.thrift new file mode 100644 index 00000000..ee260e5a --- /dev/null +++ b/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/lib/nodejs/lib/thrift/binary_parser.js b/lib/nodejs/lib/thrift/binary_parser.js new file mode 100644 index 00000000..2aeda46d --- /dev/null +++ b/lib/nodejs/lib/thrift/binary_parser.js @@ -0,0 +1,274 @@ +/* + * 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 sys = require('sys'); +var chr = String.fromCharCode; + +var p = exports.BinaryParser = function( bigEndian, allowExceptions ){ + this.bigEndian = bigEndian || true; + this.allowExceptions = allowExceptions; +}; +p.bigEndian = true; + +var IBuffer = exports.BinaryParser.IBuffer = function( bigEndian, buffer ){ + this.bigEndian = bigEndian || 1; + this.buffer = []; + this.setBuffer( buffer ); +}; + +IBuffer.prototype.setBuffer = function( data ){ + if( data ){ + for( var l, i = l = data.length, b = this.buffer = new Array( l ); i; b[l - i] = data[--i] ); + this.bigEndian && b.reverse(); + } +}; + +IBuffer.prototype.hasNeededBits = function( neededBits ){ + return this.buffer.length >= -( -neededBits >> 3 ); +}; + +IBuffer.prototype.checkBuffer = function( neededBits ){ + if( !this.hasNeededBits( neededBits ) ) { + console.log("missing: " + neededBits + " " + this.buffer.length); + throw new Error( "checkBuffer::missing bytes" ); + } +}; + +IBuffer.prototype.readBits = function( start, length ){ + //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) + function shl( a, b ){ + for( ; b--; a = ( ( a %= 0x7fffffff + 1 ) & 0x40000000 ) == 0x40000000 ? a * 2 : ( a - 0x40000000 ) * 2 + 0x7fffffff + 1 ); + return a; + } + if( start < 0 || length <= 0 ) + return 0; + this.checkBuffer( start + length ); + for( var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - ( start >> 3 ) - 1, lastByte = this.buffer.length + ( -( start + length ) >> 3 ), diff = curByte - lastByte, sum = ( ( this.buffer[ curByte ] >> offsetRight ) & ( ( 1 << ( diff ? 8 - offsetRight : length ) ) - 1 ) ) + ( diff && ( offsetLeft = ( start + length ) % 8 ) ? ( this.buffer[ lastByte++ ] & ( ( 1 << offsetLeft ) - 1 ) ) << ( diff-- << 3 ) - offsetRight : 0 ); diff; sum += shl( this.buffer[ lastByte++ ], ( diff-- << 3 ) - offsetRight ) ); + return sum; +}; + +p.warn = function( msg ){ + if( this.allowExceptions ) + throw new Error( msg ); + return 1; +}; +p.decodeFloat = function( data, precisionBits, exponentBits ){ + var b = new this.IBuffer( this.bigEndian, data ); + b.checkBuffer( precisionBits + exponentBits + 1 ); + var bias = Math.pow( 2, exponentBits - 1 ) - 1, signal = b.readBits( precisionBits + exponentBits, 1 ), exponent = b.readBits( precisionBits, exponentBits ), significand = 0, + divisor = 2, curByte = b.buffer.length + ( -precisionBits >> 3 ) - 1; + do{ + for( var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); + }while( precisionBits -= startBit ); + return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 ); +}; +p.decodeInt = function( data, bits, signed, forceBigEndian ){ + //console.log("decodeInt: ", data, bits, signed); + var b = new this.IBuffer( this.bigEndian||forceBigEndian, data ), x = b.readBits( 0, bits ), max = Math.pow( 2, bits ); + return signed && x >= max / 2 ? x - max : x; +}; +p.encodeFloat = function( data, precisionBits, exponentBits ){ + var bias = Math.pow( 2, exponentBits - 1 ) - 1, minExp = -bias + 1, maxExp = bias, minUnnormExp = minExp - precisionBits, + status = isNaN( n = parseFloat( data ) ) || n == -Infinity || n == +Infinity ? n : 0, + exp = 0, len = 2 * bias + 1 + precisionBits + 3, bin = new Array( len ), + signal = ( n = status !== 0 ? 0 : n ) < 0, n = Math.abs( n ), intPart = Math.floor( n ), floatPart = n - intPart, + i, lastBit, rounded, j, result; + for( i = len; i; bin[--i] = 0 ); + for( i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor( intPart / 2 ) ); + for( i = bias + 1; floatPart > 0 && i; ( bin[++i] = ( ( floatPart *= 2 ) >= 1 ) - 0 ) && --floatPart ); + for( i = -1; ++i < len && !bin[i]; ); + if( bin[( lastBit = precisionBits - 1 + ( i = ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - ( exp = minExp - 1 ) ) ) + 1] ){ + if( !( rounded = bin[lastBit] ) ){ + for( j = lastBit + 2; !rounded && j < len; rounded = bin[j++] ); + } + for( j = lastBit + 1; rounded && --j >= 0; ( bin[j] = !bin[j] - 0 ) && ( rounded = 0 ) ); + } + for( i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i]; ); + if( ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ) + ++i; + else if( exp < minExp ){ + exp != bias + 1 - len && exp < minUnnormExp && this.warn( "encodeFloat::float underflow" ); + i = bias + 1 - ( exp = minExp - 1 ); + } + if( intPart || status !== 0 ){ + this.warn( intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status ); + exp = maxExp + 1; + i = bias + 2; + if( status == -Infinity ) + signal = 1; + else if( isNaN( status ) ) + bin[i] = 1; + } + for( n = Math.abs( exp + bias ), j = exponentBits + 1, result = ""; --j; result = ( n % 2 ) + result, n = n >>= 1 ); + for( n = 0, j = 0, i = ( result = ( signal ? "1" : "0" ) + result + bin.slice( i, i + precisionBits ).join( "" ) ).length, r = []; i; j = ( j + 1 ) % 8 ){ + n += ( 1 << j ) * result.charAt( --i ); + if( j == 7 ){ + r[r.length] = n; + n = 0; + } + } + if (n) { + r[r.length] = n; + } + return new Buffer( this.bigEndian ? r.reverse() : r ); +}; +p.encodeInt = function( data, bits, signed, forceBigEndian ){ + var max = Math.pow( 2, bits ); + ( data >= max || data < -( max / 2 ) ) && this.warn( "encodeInt::overflow" ) && ( data = 0 ); + data < 0 && ( data += max ); + for( var r = []; data; r[r.length] = data % 256, data = Math.floor( data / 256 ) ); + for( bits = -( -bits >> 3 ) - r.length; bits--; r[r.length] = 0 ); + return new Buffer((this.bigEndian||forceBigEndian) ? r.reverse() : r ); +}; +p.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; +p.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; +p.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; +p.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; +p.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; +p.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; +p.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; +p.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; +p.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; +p.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; +p.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; +p.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; +p.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; +p.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; +p.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; +p.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; +p.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; +p.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; +p.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; +p.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; + +// Factor out the encode so it can be shared by add_header and push_int32 +p.encode_int32 = function(number) { + var a, b, c, d, unsigned; + unsigned = (number < 0) ? (number + 0x100000000) : number; + a = Math.floor(unsigned / 0xffffff); + unsigned &= 0xffffff; + b = Math.floor(unsigned / 0xffff); + unsigned &= 0xffff; + c = Math.floor(unsigned / 0xff); + unsigned &= 0xff; + d = Math.floor(unsigned); + return chr(a) + chr(b) + chr(c) + chr(d); +}; + +p.encode_int64 = function(number) { + var a, b, c, d, e, f, g, h, unsigned; + unsigned = (number < 0) ? (number + 0x10000000000000000) : number; + a = Math.floor(unsigned / 0xffffffffffffff); + unsigned &= 0xffffffffffffff; + b = Math.floor(unsigned / 0xffffffffffff); + unsigned &= 0xffffffffffff; + c = Math.floor(unsigned / 0xffffffffff); + unsigned &= 0xffffffffff; + d = Math.floor(unsigned / 0xffffffff); + unsigned &= 0xffffffff; + e = Math.floor(unsigned / 0xffffff); + unsigned &= 0xffffff; + f = Math.floor(unsigned / 0xffff); + unsigned &= 0xffff; + g = Math.floor(unsigned / 0xff); + unsigned &= 0xff; + h = Math.floor(unsigned); + return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h); +}; + +/** + UTF8 methods +**/ + +// Take a raw binary string and return a utf8 string +p.decode_utf8 = function(a) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < a.length ) { + c = a.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if((c > 191) && (c < 224)) { + c2 = a.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = a.charCodeAt(i+1); + c3 = a.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; +}; + +// Encode a cstring correctly +p.encode_cstring = function(s) { + return unescape(encodeURIComponent(s)) + p.fromByte(0); +}; + +// Take a utf8 string and return a binary string +p.encode_utf8 = function(s) { + var a=""; + for (var n=0; n< s.length; n++) { + var c=s.charCodeAt(n); + if (c<128) { + a += String.fromCharCode(c); + } else if ((c>127)&&(c<2048)) { + a += String.fromCharCode( (c>>6) | 192) ; + a += String.fromCharCode( (c&63) | 128); + } else { + a += String.fromCharCode( (c>>12) | 224); + a += String.fromCharCode( ((c>>6) & 63) | 128); + a += String.fromCharCode( (c&63) | 128); + } + } + return a; +}; + +p.hprint = function(s) { + for (var i=0; i= frameLeft) { + data.copy(frame, framePos, 0, frameLeft); + data = data.slice(frameLeft, data.length); + + frameLeft = 0; + callback(frame); + } else if (data.length) { + data.copy(frame, framePos, 0, data.length); + frameLeft -= data.length; + framePos += data.length; + data = data.slice(data.length, data.length); + } + } + }; +} + +var Connection = exports.Connection = function(stream, options) { + var self = this; + EventEmitter.call(this); + + this.connection = stream; + this.offline_queue = []; + this.options = options || {}; + this.connected = false; + + this.connection.addListener("connect", function() { + self.connected = true; + this.setTimeout(self.options.timeout || 0); + this.setNoDelay(); + this.frameLeft = 0; + this.framePos = 0; + this.frame = null; + + self.offline_queue.forEach(function(data) { + self.connection.write(data); + }); + + self.emit("connect"); + }); + + this.connection.addListener("error", function(err) { + self.emit("error", err); + }); + + // Add a close listener + this.connection.addListener("close", function() { + self.emit("close"); + }); + + this.connection.addListener("timeout", function() { + self.emit("timeout"); + }); + + this.connection.addListener("data", int32FramedReceiver(function(data) { + // console.log(typeof(data)); + var input = new TBinaryProtocol(new TMemoryBuffer(data)); + var r = input.readMessageBegin(); + // console.log(r); + self.client['recv_' + r.fname](input, r.mtype, r.rseqid); + // self.emit("data", data); + })); +} +sys.inherits(Connection, EventEmitter); + +exports.createConnection = function(host, port, options) { + var stream = net.createConnection(port, host); + var connection = new Connection(stream, options); + connection.host = host; + connection.port = port; + + return connection; +} + +exports.createClient = function(cls, connection) { + if (cls.Client) { + cls = cls.Client; + } + var client = new cls(new TMemoryBuffer(undefined, function(buf) { + connection.write(buf); + }), TBinaryProtocol); + + // TODO clean this up + connection.client = client; + + return client; +} + +Connection.prototype.end = function() { + this.connection.end(); +} + +Connection.prototype.write = function(buf) { + // TODO: optimize this better, allocate one buffer instead of both: + var msg = new Buffer(buf.length + 4); + BinaryParser.fromInt(buf.length).copy(msg, 0, 0, 4); + buf.copy(msg, 4, 0, buf.length); + if (!this.connected) { + this.offline_queue.push(msg); + } else { + this.connection.write(msg); + } +} diff --git a/lib/nodejs/lib/thrift/index.js b/lib/nodejs/lib/thrift/index.js new file mode 100644 index 00000000..f4fa3cd1 --- /dev/null +++ b/lib/nodejs/lib/thrift/index.js @@ -0,0 +1,26 @@ +/* + * 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 connection = require('./connection'); +exports.Connection = connection.Connection; +exports.createClient = connection.createClient; +exports.createConnection = connection.createConnection; + +exports.createServer = require('./server').createServer; diff --git a/lib/nodejs/lib/thrift/protocol.js b/lib/nodejs/lib/thrift/protocol.js new file mode 100644 index 00000000..f333bb99 --- /dev/null +++ b/lib/nodejs/lib/thrift/protocol.js @@ -0,0 +1,337 @@ +/* + * 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 sys = require('sys'), + Thrift = require('./thrift'), + Type = Thrift.Type; + +var BinaryParser = require('./binary_parser').BinaryParser; +BinaryParser.bigEndian = true; + +var UNKNOWN = 0, + INVALID_DATA = 1, + NEGATIVE_SIZE = 2, + SIZE_LIMIT = 3, + BAD_VERSION = 4; + +var TProtocolException = function(type, message) { + Error.call(this, message); + this.name = 'TProtocolException'; + this.type = type; +} +sys.inherits(TProtocolException, Error); + +var TBinaryProtocol = exports.TBinaryProtocol = function(trans, strictRead, strictWrite) { + this.trans = trans; + this.strictRead = (strictRead !== undefined ? strictRead : false); + this.strictWrite = (strictWrite !== undefined ? strictWrite : true); +} + +TBinaryProtocol.prototype.flush = function() { + return this.trans.flush(); +} + +// NastyHaxx. JavaScript forces hex constants to be +// positive, converting this into a long. If we hardcode the int value +// instead it'll stay in 32 bit-land. + +var VERSION_MASK = -65536, // 0xffff0000 + VERSION_1 = -2147418112, // 0x80010000 + TYPE_MASK = 0x000000ff; + +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); + } +} + +TBinaryProtocol.prototype.writeMessageEnd = function() { +} + +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) { + console.log('write set', 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(byte) { + this.trans.write(BinaryParser.fromByte(byte)); +} + +TBinaryProtocol.prototype.writeI16 = function(i16) { + this.trans.write(BinaryParser.fromShort(i16)); +} + +TBinaryProtocol.prototype.writeI32 = function(i32) { + this.trans.write(BinaryParser.fromInt(i32)); +} + +TBinaryProtocol.prototype.writeI64 = function(i64) { + this.trans.write(BinaryParser.fromLong(i64)); +} + +TBinaryProtocol.prototype.writeDouble = function(dub) { + this.trans.write(BinaryParser.fromDouble(dub)); +} + +TBinaryProtocol.prototype.writeString = function(str) { + this.writeI32(str.length); + this.trans.write(str); +} + +TBinaryProtocol.prototype.readMessageBegin = function() { + var sz = this.readI32(); + var type, name, seqid; + + if (sz < 0) { + var version = sz & VERSION_MASK; + if (version != VERSION_1) { + console.log("BAD: " + version); + throw TProtocolException(BAD_VERSION, "Bad version in readMessageBegin: " + sz); + } + type = sz & TYPE_MASK; + name = this.readString(); + seqid = this.readI32(); + } else { + if (this.strictRead) { + throw TProtocolException(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 byte = this.readByte(); + if (byte == 0) { + return false; + } + return true; +} + +TBinaryProtocol.prototype.readByte = function() { + var buff = this.trans.read(1); + return BinaryParser.toByte(buff); +} + +TBinaryProtocol.prototype.readI16 = function() { + var buff = this.trans.read(2); + return BinaryParser.toShort(buff); +} + +TBinaryProtocol.prototype.readI32 = function() { + var buff = this.trans.read(4); + // console.log("read buf: "); + // console.log(buff); + // console.log("result: " + BinaryParser.toInt(buff)); + return BinaryParser.toInt(buff); +} + +TBinaryProtocol.prototype.readI64 = function() { + var buff = this.trans.read(8); + return BinaryParser.toLong(buff); +} + +TBinaryProtocol.prototype.readDouble = function() { + var buff = this.trans.read(8); + return BinaryParser.toFloat(buff); +} + +TBinaryProtocol.prototype.readBinary = function() { + var len = this.readI32(); + return this.trans.read(len); +} + +TBinaryProtocol.prototype.readString = function() { + var r = this.readBinary().toString('binary'); + // console.log("readString: " + r); + return r; +} + +TBinaryProtocol.prototype.getTransport = function() { + return this.trans; +} + +TBinaryProtocol.prototype.skip = function(type) { + // console.log("skip: " + type); + switch (type) { + case Type.STOP: + return; + 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 r = this.readMapBegin(); + for (var i = 0; i < r.size; ++i) { + this.skip(r.ktype); + this.skip(r.vtype); + } + this.readMapEnd(); + break; + case Type.SET: + var r = this.readSetBegin(); + for (var i = 0; i < r.size; ++i) { + this.skip(r.etype); + } + this.readSetEnd(); + break; + case Type.LIST: + var r = this.readListBegin(); + for (var i = 0; i < r.size; ++i) { + this.skip(r.etype); + } + this.readListEnd(); + break; + default: + throw Error("Invalid type: " + type); + } +} diff --git a/lib/nodejs/lib/thrift/server.js b/lib/nodejs/lib/thrift/server.js new file mode 100644 index 00000000..43a7967a --- /dev/null +++ b/lib/nodejs/lib/thrift/server.js @@ -0,0 +1,51 @@ +/* + * 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 sys = require('sys'), + net = require('net'); + +var BinaryParser = require('./binary_parser').BinaryParser, + TMemoryBuffer = require('./transport').TMemoryBuffer, + TBinaryProtocol = require('./protocol').TBinaryProtocol, + int32FramedReceiver = require('./connection').int32FramedReceiver; + +exports.createServer = function(cls, handler) { + if (cls.Processor) { + cls = cls.Processor; + } + var processor = new cls(handler); + + return net.createServer(function(stream) { + stream.on('data', int32FramedReceiver(function(data) { + var input = new TBinaryProtocol(new TMemoryBuffer(data)); + var output = new TBinaryProtocol(new TMemoryBuffer(undefined, function(buf) { + // TODO: optimize this better, allocate one buffer instead of both: + var msg = new Buffer(buf.length + 4); + BinaryParser.fromInt(buf.length).copy(msg, 0, 0, 4); + buf.copy(msg, 4, 0, buf.length); + stream.write(msg); + })); + + processor.process(input, output); + })); + + stream.on('end', function() { + stream.end(); + }); + }); +} diff --git a/lib/nodejs/lib/thrift/thrift.js b/lib/nodejs/lib/thrift/thrift.js new file mode 100644 index 00000000..73f772b2 --- /dev/null +++ b/lib/nodejs/lib/thrift/thrift.js @@ -0,0 +1,130 @@ +/* + * 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 sys = require('sys'); + +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, +} + +var TException = exports.TException = function(message) { + Error.call(this, message); + this.name = 'TException'; +} +sys.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 +} + +var TApplicationException = exports.TApplicationException = function(type, message) { + TException.call(this, message); + this.type = type || TApplicationExceptionType.UNKNOWN; + this.name = 'TApplicationException'; +} +sys.inherits(TApplicationException, TException); + +TApplicationException.prototype.read = function(input) { + var ftype + var fid + var ret = input.readStructBegin('TApplicationException') + + while(1){ + + ret = input.readFieldBegin() + + if(ret.ftype == Type.STOP) + break + + var fid = ret.fid + + switch(fid){ + case 1: + if( ret.ftype == Type.STRING ){ + ret = input.readString() + this.message = ret.value + } else { + ret = input.skip(ret.ftype) + } + + break + case 2: + if( ret.ftype == Type.I32 ){ + ret = input.readI32() + this.type = ret.value + } 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() +} diff --git a/lib/nodejs/lib/thrift/transport.js b/lib/nodejs/lib/thrift/transport.js new file mode 100644 index 00000000..0c10ef0a --- /dev/null +++ b/lib/nodejs/lib/thrift/transport.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 TMemoryBuffer = exports.TMemoryBuffer = function(buffer, flushCallback) { + if (buffer !== undefined) { + this.recv_buf = buffer; + } else { + this.recv_buf = new Buffer(0); + } + this.recv_buf_sz = this.recv_buf.length; + this.send_buf = []; + this.rpos = 0; + this.flushCallback = flushCallback; +} + +TMemoryBuffer.prototype.isOpen = function() { + // TODO + return true; +} + +TMemoryBuffer.prototype.open = function() { +} + +TMemoryBuffer.prototype.close = function() { +} + +TMemoryBuffer.prototype.read = function(len) { + var avail = this.recv_buf_sz - this.rpos; + // console.log("avail: " + avail); + + if(avail == 0) + return new Buffer(0); + + var give = len + + if(avail < len) { + console.log("asked for: " + len); + throw new Error("asked for too much"); + give = avail + } + + // console.log(this.rpos + "," + give); + var ret = this.recv_buf.slice(this.rpos,this.rpos + give) + this.rpos += give + // console.log(ret); + + //clear buf when complete? + return ret + +} + +TMemoryBuffer.prototype.readAll = function() { + return this.recv_buf; +} + +TMemoryBuffer.prototype.write = function(buf) { + // TODO + if (typeof(buf) === "string") { + for (var i = 0; i < buf.length; ++i) { + this.send_buf.push(buf.charCodeAt(i)); + } + } else { + for (var i = 0; i < buf.length; ++i) { + this.send_buf.push(buf[i]); + } + } +} + +TMemoryBuffer.prototype.flush = function() { + this.flushCallback(new Buffer(this.send_buf)); + this.send_buf = []; +} diff --git a/lib/nodejs/package.json b/lib/nodejs/package.json new file mode 100644 index 00000000..e6ebca94 --- /dev/null +++ b/lib/nodejs/package.json @@ -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. + */ +{ + "name": "thrift", + "description": "node-thrift", + "version": "0.6.0", + "author": "Apache Thrift", + "directories" : { "lib" : "./lib/thrift" }, + "main": "./lib/thrift", + "engines": { "node": ">= 0.2.4" }, +} -- 2.17.1