CppCMS meets Comet
One of the major requirements for framework refactoring was support of Comet. Now, with introduction of asynchronous request handling and persistent application servers it becomes reality.
Client Side
There is a HTML source of simple chat client, that uses Dojo toolkit. It does following:
Submits new messages to the server application by posting form using XHR:
function send_data() { var kw = { url : "/chat/post", form : "theform" }; dojo.xhrPost(kw); dojo.byId("message").value=""; return false; }Receives new messages from the server using long poll via XHR:
var message_count = 0; function read_data() { dojo.xhrGet( { url: "/chat/get/" + message_count, timeout: 120000, handleAs: "text", load: function(response, ioArgs) { dojo.byId("messages").innerHTML = response + '<br/>' + dojo.byId("messages").innerHTML; message_count++; read_data(); return response; }, error: function(response,ioArgs) { read_data(); return response; } }); } dojo.addOnLoad(read_data);
So, the client side is quite simple (however error handling should be quite better).
Server Side
First we create our long running asynchronous application, that receives two kinds
for requests: "/post" – with new data, and "/get/NN" – receive message nuber NN, we assign these calls to two member functions post and get.
class chat : public cppcms::application {
public:
chat(cppcms::service &srv) : cppcms::application(srv)
{
dispatcher().assign("^/post$",&chat::post,this);
dispatcher().assign("^/get/(\\d+)$",&chat::get,this,1);
}
Now, this class includes two data members:
private:
std::vector<std::string> messages_;
std::vector<cppcms::intrusive_ptr<cppcms::http::context> > waiters_;
The history of all chat messages – messages_ and all pending get requests
that can’t be satisfied, because the message still not exists – waiters_
Each, "waiter" is actually pointer to request/response context that can be used for message transport.
Now, when new message arrives, post member function is called:
void post()
{
if(request().request_method()=="POST") {
if(request().post().find("message")!=request().post().end()) {
messages_.push_back(request().post().find("message")->second);
broadcast();
}
}
release_context()->async_complete_response();
}
If the requested message was found, it is added to messages_ list and all waiters are notified using broadcast() member function.
At the end, the current request context is released and completed.
The broadcasting is done as following:
void broadcast()
{
for(unsigned i=0;i<waiters_.size();i++) {
waiters_[i]->response().set_plain_text_header();
waiters_[i]->response().out() << messages_.back();
waiters_[i]->async_complete_response();
waiters_[i]=0;
}
waiters_.clear();
}
For each pending request the last message is written and the request closed. After that, all pending request are cleaned.
When get request arrives, it is handled by get(std::string no) member function, first of all
we check if requested message exists, if so we just return it to user.
unsigned pos=atoi(no.c_str());
if(pos < messages_.size()) {
response().set_plain_text_header();
response().out()<<messages_[pos];
release_context()->async_complete_response();
}
Otherwise, if the requested message is the last one, that does not exists, we
add the request context to pending list waiters
else if(pos == messages_.size()) {
waiters_.push_back(release_context());
}
If requested message it too late – probably client error, we just set status to "404 Not Found" and return the response.
else {
response().status(404);
release_context()->async_complete_response();
}
No, all we need to do is to add application to the main running loop under script name "/char" and start the service.
cppcms::service service(argc,argv); cppcms::intrusive_ptr<chat> app=new chat(service); service.applications_pool().mount(app,"/chat"); service.run();
Summary
So, the simple chat service was written with about 50 lines of C++ code and about same amount of JavaScript code.
I must admit, that it is too simplistic and not efficient, for example: if new client connects it receives all messages one by one and not as bulk (can be easily fixed), I do not handle timeouts and disconnects. But the general idea is quite clear:
- Asynchronous long running application that handles all request is created.
- It manages all outstanding request and uses them for server side push.
This is actually a base for future development of tools like XML-RPC and JSON-RPC that allow client to call asynchronously server side objects, it can be used for implementation of any other Comet protocols.
Comments
Hello!
I'm start writing a qooxdoo server JSON-RPC that integrates in CppCms 0.0.4. I'm not expertize in web development…
You plan add JSON-RPC support in future?
http://qooxdoo.org/documentation/0.8/rpc_server_writer_guide
If you integrate qooxdo server RPC as add-on to your project, CppCms+qooxdo will support develop "GUI Like" web applications better than Wt.
Thanks you very much for your work.
I'll be back!
It would be the native type of RPC supported by CppCMS. Actually the next version of CppCMS (not 0.0.x series) uses JSON as its data format for communication and configuration.
JSON-RPC would be the major RPC for CppCMS.
I would suggest to take a look on
refactoringbranch offrameworkproject in SVN.It supports new features like JSON and Comet that do not exist in CppCMS 0.0.x serices.
I still hand’t written JSON-RPC API but it is the top priority feature that would be developed very soon.
Hello!
I read your Coding Standards guide lines.
I want to suggest you two points (with low importance):
1) Smart Pointers: Deprecation of auto_ptr. http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=403
2) Rename cppcms::application::cout because cout means "console" out AFAIK. And if I want use std::cout and "using namespace std;"… Do you like for example "wout"? (web out).
Thank you!
Carry ON!
(sorry if this place is not adequate for this suggestions)
Hi,
I would suggest to read the updated one for next CppCMS version:
http://art-blog.no-ip.info/wikipp/en/page/cppcms_1x_coding_standards
First of all I do not agree that
auto_ptrshould be deprecated. When C++0x comes probablymove_ptrandshared_ptrshould be used more but… Until C++0x would be supported by all major compilers this project would release two or three major versions ;).Today, in modern C++ it is the only smart pointer that has clear move semantics that is very useful for factory pattern.
In any case… I'm beginning to support backward compatible ABI, and this means that I can’t use any of
boost::*classes includingshared_ptrin API. Thus,auto_ptris the only smart pointer I can use.It does not exist any more in CppCMS 1.x. Instead you call
out()member function ofhttp::responseclass. In any case, it is generally called by template rendering interface and not directly.Hi!
Yes. You are right!. But if I want to response for example a QOOXDOO-RPC (a variation of standard JSON-RPC) I have to use "out()" member function or another primitive method. Right?
Another question: Are you developing a new JSON parser/writer? If not, what library you are using?
Thanks!
I think you whould not have to… Only create json-response.
I had already written one. I didn’t want to use existing because I have strict requirements of ABI compatibility.
Take a look on this code: json.h, json.cpp
You can send a trackback to following url:
Add Comment:
You can write your messages using Markdown syntax.
You must enable JavaScript in order to post comments.