From 7da94a4efbad27c83b141107064a517e42353603 Mon Sep 17 00:00:00 2001
From: Artyom Beilis
Date: Sun, 11 Dec 2011 09:02:40 +0000
Subject: [PATCH] Created a simple JsonRPC based chat server example, shows:
- Asynchronous JsonRPC handling on both client and server side
- Errors and timeout handling
---
contrib/client_side/jsonrpc/jsonrpc.js | 44 +++++++++++++++-
examples/json_rpc_chat/chat.cpp | 50 ++++++++++++++-----
examples/json_rpc_chat/index.html | 91 ++++++++++++++++++++++++++++++++++
examples/json_rpc_chat/the_chat.html | 80 ------------------------------
4 files changed, 170 insertions(+), 95 deletions(-)
create mode 100644 examples/json_rpc_chat/index.html
delete mode 100644 examples/json_rpc_chat/the_chat.html
diff --git a/contrib/client_side/jsonrpc/jsonrpc.js b/contrib/client_side/jsonrpc/jsonrpc.js
index 5e2a297..0090f65 100644
--- a/contrib/client_side/jsonrpc/jsonrpc.js
+++ b/contrib/client_side/jsonrpc/jsonrpc.js
@@ -24,6 +24,33 @@
/// - id - JSONRPC id. It should be null for notification methods
/// and it should be some integer or string for function methods
///
+/// Each method given in the constructor would have following properties:
+///
+/// on_error(e) - Returned error, where e.type is one of 'transport', 'protocol', 'response' and
+/// e.error is the error object.
+/// on_result(r) - Returned method result, or on_result() - for notifications.
+///
+/// For example
+///
+/// var rpc = new JsonRPC('/chat',['getValue','getStatistics'],['updateValue']);
+///
+/// // Asynchronouse method
+///
+/// rpc.getValue.on_error = function(e) { alert('Error:' + e.error); }
+/// rpc.getValue.on_result = function(r) { alert(r); }
+///
+/// rpc.getValue();
+///
+/// // Synchronous method
+///
+/// // not setting callbacks or setting on_error and on_result to null
+/// // makes them synchronous rpc calls. For example;
+///
+/// alert(rpc.getStatistics());
+/// rpc.updateValue(10);
+///
+///
+
function JsonRPC(uri,function_methods,notification_methods) {
if(!(this instanceof JsonRPC))
return new JsonRPC(uri,function_methods,notification_methods);
@@ -75,7 +102,13 @@ JsonRPC.prototype.syncCall = function(name,id,params) {
if(xhr.status!=200)
throw Error('Invalid response:' + xhr.status);
if(id!=null) {
- var response = JSON.parse(xhr.responseText);
+ var response = null;
+ try {
+ response = JSON.parse(xhr.responseText);
+ }
+ catch(e) {
+ throw Error('Invalid JSON-RPC response');
+ }
if(response.error != null)
throw Error(response.error);
return response.result;
@@ -95,7 +128,14 @@ JsonRPC.prototype.asyncCall = function(name,id,params,on_result,on_error) {
return;
if(xhr.status==200) {
if(id!=null) {
- var response = JSON.parse(xhr.responseText);
+ var response = null;
+ try {
+ response = JSON.parse(xhr.responseText);
+ }
+ catch(e) {
+ on_error({'type' : 'protocol', 'error' : 'invalid response'});
+ return;
+ }
if(response.error != null) {
on_error({'type': 'response', 'error' : response.error });
}
diff --git a/examples/json_rpc_chat/chat.cpp b/examples/json_rpc_chat/chat.cpp
index 1fb7fad..85cd761 100644
--- a/examples/json_rpc_chat/chat.cpp
+++ b/examples/json_rpc_chat/chat.cpp
@@ -41,48 +41,55 @@ public:
cppcms::rpc::json_rpc_server(srv),
timer_(srv.get_io_service())
{
+ // Our main methods
bind("post",cppcms::rpc::json_method(&chat::post,this),notification_role);
bind("get",cppcms::rpc::json_method(&chat::get,this),method_role);
+
+ // Add timeouts to the system
last_wake_ = time(0);
on_timer(booster::system::error_code());
}
+
+ // Handle new message call
void post(std::string const &author,std::string const &message)
{
cppcms::json::value obj;
obj["author"]=author;
obj["message"]=message;
messages_.push_back(obj);
- last_wake_ = time(0);
broadcast(messages_.size()-1);
}
void on_timer(booster::system::error_code const &e)
{
if(e) return; // cancelation
- if(last_wake_ - time(0) > 10) {
+
+ // check idle connections for more then 10 seconds
+ if(time(0) - last_wake_ > 10) {
broadcast(messages_.size());
- last_wake_ = time(0);
}
+ // restart timer
timer_.expires_from_now(booster::ptime::seconds(1));
timer_.async_wait(boost::bind(&chat::on_timer,booster::intrusive_ptr(this),_1));
- std::cout << "Status: \n"
- << "Waiters: " << waiters_.size() << '\n'
- << "Messages:" << messages_.size() <<'\n'
- << "[";
- for(size_t i=0;i call=release_call();
waiters_.insert(call);
+
+ // set disconnect callback
call->context().async_on_peer_reset(
boost::bind(
&chat::remove_context,
@@ -93,36 +100,53 @@ public:
return_error("Invalid position");
}
}
+
+ // handle client disconnect
void remove_context(booster::shared_ptr call)
{
waiters_.erase(call);
}
+
void broadcast(size_t from)
{
+ // update timeout
+ last_wake_ = time(0);
+ // Prepare response
+ cppcms::json::value response = make_response(from);
+ // Send it to everybody
for(waiters_type::iterator waiter=waiters_.begin();waiter!=waiters_.end();++waiter) {
booster::shared_ptr call = *waiter;
- call->return_result(make_response(from));
+ call->return_result(response);
}
waiters_.clear();
}
+
+ // Prepare response to the client
cppcms::json::value make_response(size_t n)
{
cppcms::json::value v;
+ // Small optimization
v=cppcms::json::array();
cppcms::json::array &ar = v.array();
-
ar.reserve(messages_.size() - n);
+
+ // prepare all messages
for(size_t i=n;i messages_;
+
+ // long poll requests
typedef std::set > waiters_type;
waiters_type waiters_;
+
+ // timer for resetting idle requests
booster::aio::deadline_timer timer_;
time_t last_wake_;
};
diff --git a/examples/json_rpc_chat/index.html b/examples/json_rpc_chat/index.html
new file mode 100644
index 0000000..2b795d5
--- /dev/null
+++ b/examples/json_rpc_chat/index.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+ Chat Room
+
+
+
+
+
+
+Chat room
+
+
+
+
+
+
diff --git a/examples/json_rpc_chat/the_chat.html b/examples/json_rpc_chat/the_chat.html
deleted file mode 100644
index a8315e1..0000000
--- a/examples/json_rpc_chat/the_chat.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
- Chat Room
-
-
-
-
-
-
-Chat room
-
-
-
-