THRIFT-1087 Nonblocking asynchronous JS services
Patch: Henrique Mendonca
git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1089637 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/src/generate/t_js_generator.cc b/compiler/cpp/src/generate/t_js_generator.cc
index 9def5da..fa06b05 100644
--- a/compiler/cpp/src/generate/t_js_generator.cc
+++ b/compiler/cpp/src/generate/t_js_generator.cc
@@ -47,6 +47,9 @@
iter = parsed_options.find("node");
gen_node_ = (iter != parsed_options.end());
+
+ iter = parsed_options.find("jquery");
+ gen_jquery_ = (iter != parsed_options.end());
if (gen_node_) {
out_dir_base_ = "gen-nodejs";
@@ -160,7 +163,7 @@
std::string render_includes();
std::string declare_field(t_field* tfield, bool init=false, bool obj=false);
std::string function_signature(t_function* tfunction, std::string prefix="", bool include_callback=false);
- std::string argument_list(t_struct* tstruct);
+ std::string argument_list(t_struct* tstruct, bool include_callback=false);
std::string type_to_enum(t_type* ttype);
std::string autogen_comment() {
@@ -227,6 +230,11 @@
bool gen_node_;
/**
+ * True if we should generate services that use jQuery ajax (async/sync).
+ */
+ bool gen_jquery_;
+
+ /**
* File streams
*/
std::ofstream f_types_;
@@ -341,11 +349,11 @@
vector<t_enum_value*>::iterator c_iter;
for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
int value = (*c_iter)->get_value();
- f_types_ << "'" << (*c_iter)->get_name() << "' : " << value;
- if (c_iter != constants.end()-1)
+ f_types_ << "'" << (*c_iter)->get_name() << "' : " << value;
+ if (c_iter != constants.end()-1) {
f_types_ << ",";
-
- f_types_ << endl;
+ }
+ f_types_ << endl;
}
f_types_ << "};"<<endl;
@@ -990,9 +998,11 @@
const vector<t_field*>& fields = arg_struct->get_members();
vector<t_field*>::const_iterator fld_iter;
string funname = (*f_iter)->get_name();
+ string arglist = argument_list(arg_struct);
// Open function
- f_service_ << js_namespace(tservice->get_program())<<service_name_<<"Client.prototype." << function_signature(*f_iter, "", gen_node_) << " {" << endl;
+ f_service_ << js_namespace(tservice->get_program())<<service_name_<<"Client.prototype." <<
+ function_signature(*f_iter, "", gen_node_ || gen_jquery_) << " {" << endl;
indent_up();
@@ -1000,21 +1010,14 @@
f_service_ <<
indent() << "this.seqid += 1;" << endl <<
indent() << "this._reqs[this.seqid] = callback;" << endl;
+ } else if (gen_jquery_) {
+ f_service_ <<
+ indent() << "if (callback === undefined) {" << endl;
+ indent_up();
}
- indent(f_service_) << indent() <<
- "this.send_" << funname << "(";
-
- bool first = true;
- for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
- if (first) {
- first = false;
- } else {
- f_service_ << ", ";
- }
- f_service_ << (*fld_iter)->get_name();
- }
- f_service_ << ");" << endl;
+ f_service_ << indent() <<
+ "this.send_" << funname << "(" << arglist << ");" << endl;
if (!gen_node_ && !(*f_iter)->is_oneway()) {
f_service_ << indent();
@@ -1025,12 +1028,28 @@
"this.recv_" << funname << "();" << endl;
}
+ if (gen_jquery_) {
+ indent_down();
+ f_service_ << indent() << "} else {" << endl;
+ indent_up();
+ f_service_ << indent() << "var postData = this.send_" << funname <<
+ "(" << arglist << (arglist.empty() ? "" : ", ") << "true);" << endl;
+ f_service_ << indent() << "return this.output.getTransport()" << endl;
+ indent_up();
+ f_service_ << indent() << ".jqRequest(this, postData, arguments, this.recv_" << funname << ");" << endl;
+ indent_down();
+ indent_down();
+ f_service_ << indent() << "}" << endl;
+ }
+
indent_down();
f_service_ << "};" << endl << endl;
+
+ // Send function
f_service_ << js_namespace(tservice->get_program())<<service_name_ <<
- "Client.prototype.send_" << function_signature(*f_iter) << " {" <<endl;
+ "Client.prototype.send_" << function_signature(*f_iter, "", gen_jquery_) << " {" <<endl;
indent_up();
@@ -1065,7 +1084,11 @@
if (gen_node_) {
f_service_ << indent() << "return this.output.flush();" << endl;
} else {
- f_service_ << indent() << "return this.output.getTransport().flush();" << endl;
+ if (gen_jquery_) {
+ f_service_ << indent() << "return this.output.getTransport().flush(callback);" << endl;
+ } else {
+ f_service_ << indent() << "return this.output.getTransport().flush();" << endl;
+ }
}
@@ -1675,25 +1698,7 @@
str = prefix + tfunction->get_name() + " = function(";
-
- //Need to create js function arg inputs
- const vector<t_field*> &fields = tfunction->get_arglist()->get_members();
- vector<t_field*>::const_iterator f_iter;
-
- for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
-
- if(f_iter != fields.begin())
- str += ", ";
-
- str += (*f_iter)->get_name();
- }
-
- if (include_callback) {
- if (!fields.empty()) {
- str += ", ";
- }
- str += "callback";
- }
+ str += argument_list(tfunction->get_arglist(), include_callback);
str += ")";
return str;
@@ -1702,7 +1707,8 @@
/**
* Renders a field list
*/
-string t_js_generator::argument_list(t_struct* tstruct) {
+string t_js_generator::argument_list(t_struct* tstruct,
+ bool include_callback) {
string result = "";
const vector<t_field*>& fields = tstruct->get_members();
@@ -1716,6 +1722,14 @@
}
result += (*f_iter)->get_name();
}
+
+ if (include_callback) {
+ if (!fields.empty()) {
+ result += ", ";
+ }
+ result += "callback";
+ }
+
return result;
}
@@ -1761,6 +1775,7 @@
}
-THRIFT_REGISTER_GENERATOR(js, "Javascript",
+THRIFT_REGISTER_GENERATOR(js, "Javascript",
+" jquery: Generate jQuery compatible code.\n"
" node: Generate node.js compatible code.\n")
diff --git a/lib/js/test/build.xml b/lib/js/test/build.xml
index c1a17e8..843ec6b 100644
--- a/lib/js/test/build.xml
+++ b/lib/js/test/build.xml
@@ -66,7 +66,7 @@
</not>
</condition>
You need libthrift*.jar and libthrift*test.jar located at
- ${thrift.java.dir}
+ ${thrift.java.dir}/build
Did you compile Thrift Java library and its test suite by "ant compile-test"?
</fail>
<fail>
@@ -101,7 +101,7 @@
<javac srcdir="${src}" destdir="${build}" classpathref="libs.classpath" />
</target>
- <target name="jstest" description="" depends="compile">
+ <target name="jstest" description="" depends="compile, lint">
<jar jarfile="${jar.file}" basedir="${build}"/>
</target>
@@ -117,14 +117,14 @@
<arg line="--gen java ${thrift.dir}/test/ThriftTest.thrift" />
</exec>
<exec executable="${thrift.compiler}" failonerror="true">
- <arg line="--gen js ${thrift.dir}/test/ThriftTest.thrift" />
+ <arg line="--gen js:jquery ${thrift.dir}/test/ThriftTest.thrift" />
</exec>
</target>
<!-- @TODO QUnit tests as part of the testsuite-->
<target name="test" description="run test suite" depends="init, generate, resolve, lint"/>
- <target name="lint" description="code quality checks" depends="gjslint, jslint, generate"/>
+ <target name="lint" description="code quality checks" depends="generate, gjslint, jslint"/>
<target name="jslint">
<taskdef uri="antlib:com.googlecode.jslint4java" resource="com/googlecode/jslint4java/antlib.xml" classpathref="libs.classpath" />
diff --git a/lib/js/test/test.html b/lib/js/test/test.html
index 4615f88..f99da01 100644
--- a/lib/js/test/test.html
+++ b/lib/js/test/test.html
@@ -27,7 +27,7 @@
<script src="gen-js/ThriftTest.js" type="text/javascript" charset="utf-8"></script>
<!-- jQuery -->
- <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" charset="utf-8"></script>
+ <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.2.js" charset="utf-8"></script>
<!-- json2 -->
<script type="text/javascript" src="json2.js" charset="utf-8"></script>
@@ -39,9 +39,9 @@
<script type="text/javascript" charset="utf-8">
//<![CDATA[
$(document).ready(function(){
- var transport = new Thrift.Transport("/service")
- var protocol = new Thrift.Protocol(transport)
- var client = new ThriftTest.ThriftTestClient(protocol)
+ var transport = new Thrift.Transport("/service");
+ var protocol = new Thrift.Protocol(transport);
+ var client = new ThriftTest.ThriftTestClient(protocol);
module("Base Types");
@@ -69,11 +69,6 @@
});
test("I32", function() {
equals(client.testI32(Math.pow(2,30)), Math.pow(2,30));
-
- /*
- how to test things like that?
- equals(client.testI32(Math.pow(2,60)), Math.pow(2,60));
- */
});
test("I64", function() {
equals(client.testI64(Math.pow(2,60)), Math.pow(2,60));
@@ -174,9 +169,6 @@
equals(JSON.stringify(mapMapTestOutput), JSON.stringify(mapMapTestExpectedResult))
});
- test("testMulti", function() {
- });
-
module("Exception");
@@ -204,7 +196,7 @@
client.testException("ApplicationException");
} catch(e) {
ok(true); //@HACK: ignore faulty java server response for exceptions
- //equals(e.message, "ApplicationException");
+ //equals(e.message, "ApplicationException");
}
});
@@ -217,63 +209,157 @@
ok(res);
});
+
+ //////////////////////////////////
+ //Run same tests asynchronously
+ jQuery.ajaxSetup({ timeout: 0 });
+ $(document).ajaxError( function() { QUnit.start(); } );
+
+ module("Async Manual");
+
+ test("testI32", function() {
+ expect( 2 );
+ QUnit.stop();
+
+ var transport = new Thrift.Transport();
+ var protocol = new Thrift.Protocol(transport);
+ var client = new ThriftTest.ThriftTestClient(protocol);
+
+ var jqxhr = jQuery.ajax({
+ url: "/service",
+ data: client.send_testI32(Math.pow(-2,31)),
+ type: "POST",
+ cache: false,
+ dataType: "text",
+ success: function(res){
+ transport.setRecvBuffer( res );
+ equals(client.recv_testI32(), Math.pow(-2,31));
+ },
+ error: function() { ok(false); },
+ complete: function() {
+ ok(true);
+ QUnit.start();
+ }
+ });
+ });
+
+
+ test("testI64", function() {
+ expect( 2 );
+ QUnit.stop();
+
+ var transport = new Thrift.Transport();
+ var protocol = new Thrift.Protocol(transport);
+ var client = new ThriftTest.ThriftTestClient(protocol);
+
+ jQuery.ajax({
+ url: "/service",
+ data: client.send_testI64(Math.pow(-2,61)),
+ type: "POST",
+ cache: false,
+ dataType: "text",
+ success: function(res){
+ transport.setRecvBuffer( res );
+ equals(client.recv_testI64(), Math.pow(-2,61));
+ },
+ error: function() { ok(false); },
+ complete: function() {
+ ok(true);
+ QUnit.start();
+ }
+ });
+ });
+
+
+
+ module("Async");
+
+ test("Double", function() {
+ expect( 1 );
+
+ QUnit.stop();
+ client.testDouble(3.14159265, function(result) {
+ equals(result, 3.14159265);
+ QUnit.start();
+ });
+ });
+
+ test("Byte", function() {
+ expect( 1 );
+
+ QUnit.stop();
+ client.testByte(0x01, function(result) {
+ equals(result, 0x01);
+ QUnit.start();
+ });
+ });
+
+ test("I32", function() {
+ expect( 3 );
+
+ QUnit.stop();
+ client.testI32(Math.pow(2,30), function(result) {
+ equals(result, Math.pow(2,30));
+ QUnit.start();
+ });
+
+ QUnit.stop();
+ var jqxhr = client.testI32(Math.pow(-2,31), function(result) {
+ equals(result, Math.pow(-2,31));
+ });
+
+ jqxhr.success(function(result) {
+ equals(result, Math.pow(-2,31));
+ QUnit.start();
+ });
+ });
+
+ test("I64", function() {
+ expect( 4 );
+
+ QUnit.stop();
+ client.testI64(Math.pow(2,60), function(result) {
+ equals(result, Math.pow(2,60));
+ QUnit.start();
+ });
+
+ QUnit.stop();
+ client.testI64(Math.pow(-2,61), function(result) {
+ equals(result, Math.pow(-2,61));
+ })
+ .error( function(e) { ok(false); } )
+ .success(function(result) {
+ equals(result, Math.pow(-2,61));
+ })
+ .complete(function() {
+ ok(true);
+ QUnit.start();
+ });
+ });
+
+ test("Xception", function() {
+ expect( 2 );
+
+ QUnit.stop();
+
+ var dfd = client.testException("Xception", function(result) {
+ ok(false);
+ QUnit.start();
+ })
+ .error(function(e){
+ equals(e.errorCode, 1001);
+ equals(e.message, "Xception");
+ QUnit.start();
+ });
+ });
+
+
});
//]]>
</script>
</head>
<body>
-
- <script type="text/javascript" charset="utf-8">
- //<![CDATA[
-
- //////////////////////////////////
- //Run same tests asynchronously
-/*
- var transport = new Thrift.Transport()
- var protocol = new Thrift.Protocol(transport)
- var client = new ThriftTest.ThriftTestClient(protocol)
-
- document.write("<h2>Asynchronous Example<\/h2>")
- jQuery.ajax({
- url: "/service",
- data: client.send_testI32(Math.pow(2,30)),
- type: "POST",
- cache: false,
- success: function(res){
- var _transport = new Thrift.Transport()
- var _protocol = new Thrift.Protocol(_transport)
- var _client = new ThriftTest.ThriftTestClient(_protocol)
-
- _transport.setRecvBuffer( res )
-
- var v = _client.recv_testI32()
- $("#body").append("client.testI32() => "+(v == Math.pow(2,30))+"<br/>")
-
- }
- })
-
- jQuery.ajax({
- url: "/service",
- data: client.send_testI64(Math.pow(2,60)),
- type: "POST",
- cache: false,
- success: function(res){
- var _transport = new Thrift.Transport()
- var _protocol = new Thrift.Protocol(_transport)
- var _client = new ThriftTest.ThriftTestClient(_protocol)
-
- _transport.setRecvBuffer( res )
-
- var v = _client.recv_testI64()
- $("#body").append("client.testI64() => "+(v == Math.pow(2,60))+"<br/>")
-
- }
- })
-*/
-
- //]]>
- </script>
<h1 id="qunit-header">Thrift Javascript Bindings: Unit Test (<a href="https://svn.apache.org/repos/asf/thrift/trunk/test/ThriftTest.thrift">ThriftTest.thrift</a>)</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
diff --git a/lib/js/thrift.js b/lib/js/thrift.js
index b8ca2c0..c7a46fd 100644
--- a/lib/js/thrift.js
+++ b/lib/js/thrift.js
@@ -56,7 +56,7 @@
var length = 0;
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
- length++;
+ length++;
}
}
@@ -75,8 +75,8 @@
Thrift.TException = {};
Thrift.TException.prototype = {
initialize: function(message, code) {
- this.message = message;
- this.code = (code === null) ? 0 : code;
+ this.message = message;
+ this.code = (code === null) ? 0 : code;
}
};
@@ -186,19 +186,16 @@
//Gets the browser specific XmlHttpRequest Object
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 the XmlHttpRequest object.";
-
},
- flush: function() {
-
+ flush: function(async) {
//async mode
- if (this.url === undefined || this.url === '') {
+ if (async || this.url === undefined || this.url === '') {
return this.send_buf;
}
@@ -225,6 +222,54 @@
this.rpos = 0;
},
+ jqRequest: function(client, postData, args, recv_method) {
+ if (typeof jQuery === 'undefined' ||
+ typeof jQuery.Deferred === 'undefined') {
+ throw 'Thrift.js requires jQuery 1.5+ to use asynchronous requests';
+ }
+
+ // Deferreds
+ var deferred = jQuery.Deferred();
+ var completeDfd = jQuery._Deferred();
+ var dfd = deferred.promise();
+ dfd.success = dfd.done;
+ dfd.error = dfd.fail;
+ dfd.complete = completeDfd.done;
+
+ var jqXHR = jQuery.ajax({
+ url: this.url,
+ data: postData,
+ type: 'POST',
+ cache: false,
+ dataType: 'text',
+ context: this,
+ success: this.jqResponse,
+ error: function(xhr, status, e) {
+ deferred.rejectWith(client, jQuery.merge([e], xhr.tArgs));
+ },
+ complete: function(xhr, status) {
+ completeDfd.resolveWith(client, [xhr, status]);
+ }
+ });
+
+ deferred.done(jQuery.makeArray(args).pop()); //pop callback from args
+ jqXHR.tArgs = args;
+ jqXHR.tClient = client;
+ jqXHR.tRecvFn = recv_method;
+ jqXHR.tDfd = deferred;
+ return dfd;
+ },
+
+ jqResponse: function(responseData, textStatus, jqXHR) {
+ this.setRecvBuffer(responseData);
+ try {
+ var value = jqXHR.tRecvFn.call(jqXHR.tClient);
+ jqXHR.tDfd.resolveWith(jqXHR, jQuery.merge([value], jqXHR.tArgs));
+ } catch (ex) {
+ jqXHR.tDfd.rejectWith(jqXHR, jQuery.merge([ex], jqXHR.tArgs));
+ }
+ },
+
setRecvBuffer: function(buf) {
this.recv_buf = buf;
this.recv_buf_sz = this.recv_buf.length;
@@ -531,7 +576,11 @@
this.rstack = [];
this.rpos = [];
- this.robj = eval(this.transport.readAll());
+ if (typeof jQuery !== 'undefined') {
+ this.robj = jQuery.parseJSON(this.transport.readAll());
+ } else {
+ this.robj = eval(this.transport.readAll());
+ }
var r = {};
var version = this.robj.shift();
@@ -551,7 +600,6 @@
return r;
},
-
readMessageEnd: function() {
},
@@ -617,7 +665,6 @@
r.ftype = ftype;
r.fid = fid;
-
return r;
},
@@ -632,7 +679,6 @@
},
readMapBegin: function(keyType, valType, size) {
-
var map = this.rstack.pop();
var r = {};
@@ -652,14 +698,12 @@
},
readListBegin: function(elemType, size) {
-
var list = this.rstack[this.rstack.length - 1];
var r = {};
r.etype = Thrift.Protocol.RType[list.shift()];
r.size = list.shift();
-
this.rpos.push(this.rstack.length);
this.rstack.push(list);
@@ -698,7 +742,6 @@
return this.readI32();
},
-
readI32: function(f) {
if (f === undefined) {
f = this.rstack[this.rstack.length - 1];
@@ -753,5 +796,4 @@
skip: function(type) {
throw 'skip not supported yet';
}
-
};