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
This commit is contained in:
Roger Meier 2011-04-06 21:30:53 +00:00
parent 54af25e3d3
commit 08b309997f
4 changed files with 269 additions and 126 deletions

View File

@ -47,6 +47,9 @@ class t_js_generator : public t_oop_generator {
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 @@ class t_js_generator : public t_oop_generator {
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() {
@ -226,6 +229,11 @@ class t_js_generator : public t_oop_generator {
*/
bool gen_node_;
/**
* True if we should generate services that use jQuery ajax (async/sync).
*/
bool gen_jquery_;
/**
* File streams
*/
@ -341,11 +349,11 @@ void t_js_generator::generate_enum(t_enum* tenum) {
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 @@ void t_js_generator::generate_service_client(t_service* tservice) {
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 @@ void t_js_generator::generate_service_client(t_service* tservice) {
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 @@ void t_js_generator::generate_service_client(t_service* tservice) {
"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 @@ void t_js_generator::generate_service_client(t_service* tservice) {
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 @@ string t_js_generator::function_signature(t_function* tfunction,
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 @@ string t_js_generator::function_signature(t_function* tfunction,
/**
* 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 @@ string t_js_generator::argument_list(t_struct* tstruct) {
}
result += (*f_iter)->get_name();
}
if (include_callback) {
if (!fields.empty()) {
result += ", ";
}
result += "callback";
}
return result;
}
@ -1761,6 +1775,7 @@ string t_js_generator ::type_to_enum(t_type* type) {
}
THRIFT_REGISTER_GENERATOR(js, "Javascript",
THRIFT_REGISTER_GENERATOR(js, "Javascript",
" jquery: Generate jQuery compatible code.\n"
" node: Generate node.js compatible code.\n")

View File

@ -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" />

View File

@ -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>

View File

@ -56,7 +56,7 @@ var Thrift = {
var length = 0;
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
length++;
length++;
}
}
@ -75,8 +75,8 @@ var Thrift = {
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 @@ Thrift.Transport.prototype = {
//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 @@ Thrift.Transport.prototype = {
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 @@ Thrift.Protocol.prototype = {
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 @@ Thrift.Protocol.prototype = {
return r;
},
readMessageEnd: function() {
},
@ -617,7 +665,6 @@ Thrift.Protocol.prototype = {
r.ftype = ftype;
r.fid = fid;
return r;
},
@ -632,7 +679,6 @@ Thrift.Protocol.prototype = {
},
readMapBegin: function(keyType, valType, size) {
var map = this.rstack.pop();
var r = {};
@ -652,14 +698,12 @@ Thrift.Protocol.prototype = {
},
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 @@ Thrift.Protocol.prototype = {
return this.readI32();
},
readI32: function(f) {
if (f === undefined) {
f = this.rstack[this.rstack.length - 1];
@ -753,5 +796,4 @@ Thrift.Protocol.prototype = {
skip: function(type) {
throw 'skip not supported yet';
}
};