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 ++++++++++++++----- .../{the_chat.html => index.html} | 23 ++++++--- 3 files changed, 96 insertions(+), 21 deletions(-) rename examples/json_rpc_chat/{the_chat.html => index.html} (83%) diff --git a/contrib/client_side/jsonrpc/jsonrpc.js b/contrib/client_side/jsonrpc/jsonrpc.js index 5e2a2975..0090f652 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 1fb7fad4..85cd7612 100644 --- a/examples/json_rpc_chat/chat.cpp +++ b/examples/json_rpc_chat/chat.cpp @@ -41,48 +41,55 @@ class chat : public cppcms::rpc::json_rpc_server { 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 @@ class chat : public cppcms::rpc::json_rpc_server { 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/the_chat.html b/examples/json_rpc_chat/index.html similarity index 83% rename from examples/json_rpc_chat/the_chat.html rename to examples/json_rpc_chat/index.html index a8315e1e..2b795d56 100644 --- a/examples/json_rpc_chat/the_chat.html +++ b/examples/json_rpc_chat/index.html @@ -8,8 +8,16 @@ Chat Room