What? YAWF - yet another web framework? But this is mine(!) ... and it's very small - a Simple Embeddable C++ Webframework for Qt!
1. The need
I have discussed this topic on this blog earlier*, so you shouldn't be surprised to hear about it again: a C++ web framework. You'd think there isn't such thing? Wrong! From my own research and from the comments of friendly internauts I collected a quite a list of possible frameworks:
And recently I found even another one:
- CppCMS
Do you see its cool logo? It says that programming in C++ is a good thing, as it isn't hard on the environment, and thus can maybe even stop the global warning ;). I must admit that I never seen C++ from that angle! Interesting point, isn't it? Ok, when you think of all that multicores and figths about cache lines then C++ does quite naturally comes to mind with its very direct memory control, doesn't it? Kind of "lean programming" :).
It's rather a wide range of possiblities, so you'd think the world doesn't need another framework, and least at all by yours truly. That's what I thought too, untill I stumbled upon a rather simple problem. Currently I develop a Qt application that runs in the background (call it demon, service, headless, etc.) and would like to have an admin interface for it. Of course I could write a standalone GUI client connecting to the application's command socket but that's so nineties! Only a web interface is fashionable these days. Thus I need a small webapp for the admin console.
So at first then, I thought I'd use some open source code from the above list. But all of them would either not integrate well with Qt events, or require 3rd party includes and dependencies, and gwan would even have to be started as an external server! Well, I didn't use gwan before and it does sound like a border case usage for it, just imagine all that debugging of its servlet compiler in run-time...
Then you may ask: isn't there any HTTP server in the Qt framework? Yes indeed, I found one, but it's an extension of the framework (they call it an option I think...) and it's running as Windows service, not as an additional interface to an existing binary. So we've got the same case as with gwan. BTW, why hasn't C++ a standard HTTP server class? Go has one, Python has one, Javascript has one, but C++ - nope! Well, that's not completely true, CINDER framework has one, and cpp-netlib got one as well, but I wanted to avoid big includes and dependencies like Boost.
But hey, Qt has a couple of HTTP support classes, so I thought: boy, that cannot be that hard! And I just hacked my own mini HTTP server.
2. The design
Of course that gives me an opportunity to design a web framework the way it should be always designed at last :-), because all the others seem to be somehow overcomplex or overcomplicated :-). So let's go down to the brass tacks then!
First we have to decide on the name - let simply try YAWF(4q), and in best software giant tradition I'll use an internal codename, for example "CutieW".
The design will be simple, plain and oldskool, as I need something working and that fast. Besides, never underestimate the power of KISS!
The basic ingredients are the Qt classes for TCP socket handling and HTTP request and response parsing. Then we'll have request handler objects with a process() method. The handler objects (kind of servlets for the Java minded) will have to be derived from a superclass (how uncool, I know) and statically instatiated, so that they can register themself with the server in the startup phase. They register with a name string**, for example "user" or "page", so they can be bound to an URI (see below for the example).
Let's sum it up: there will be a server executable, it will set the URL path configuration in startup phase, each path wil get its own handler. When a request arrives, a fresh instance of the handler will be created, this the handler classes need a clone() method for this.
The server will thus find the matching handler prototype for the URI, clone it, and then invoke its process() method. As you see, there's 1 to 1 relation betweem URI and the handler object in the current implementation, but an extension to "multiple dispatch" isn't difficult, and I'll program it when I have some spare time.
Now to the general usage pattern of YAWF4q. In Clojure you'd write the following:
(defn display []In cpp-netlib this:
(html [:h1 "hello!"]))
(defroutes myroutes
(GET "/" [] (display)))
(defonce server (run-jetty #'myroutes
{:join? false
:port 8080}))
struct hello_world {
void operator()(server::request const &request,
server::response &response) {
// your code here ...
}
} handler;
server server_(argv[1], argv[2], handler);But in yawf4q it's all pretty plain, non-clever, and almost boring:
server_.run();
#include "CuteHttpServer.hpp" // the cutieThe server just starts, finds the config file (using a default, hardcoded name), sets the routes, and waits for requests. If you don't define any handlers, a simple "I'm alive" response is sent back.
int main(int argc, char* argv[])
{
// your application:
QApplication* qtAppl = new QApplication(argc, argv);
// the webserver on port 3100
ibkrj::yawf4q::CuteHttpServer testServer(3100);
string error;
if(!testServer.start(error))
return -1;
// run Qt
qtAppl->exec();
}
3. The example
How to write your handlers? Look, you have to derive them from the TestHandlerBase class. First let us implement the handler of the index page:
class TestHandlerBaseOK that's rather much boilerplate, as we need to supply the name string plus the clone() and process() methods, so maybe I should provide a macro like this:
: public MiniHttpHandler
{
public:
TestHandlerBase()
: MiniHttpHandler("TestHandlerBase") {};
// base overrides
virtual MiniHttpHandler* clone() { return new TestHandlerBase; }
virtual void process(AnalyzerRequest& reqData, std::string& respData);
} g_handler1;
// --- OR maybe a MACRO?:I don't know, as a matter of fact I hate macros in frameworks! But as an alternative maybe it isn't that bad. Another possibility would be using CRTP, as to automatically add the clone() method, but wouldn't you say this would be somehow too clever when compared with the rest of the design?
class TestHandlerBase
: public MiniHttpHandler
{
public:
BASE_TEST_HANDLER_FUNC(TestHandlerBase)
... your functions ...
} g_xxx;
And here is the processing method:
void TestHandlerBase::process(AnalyzerRequest& reqData, std::string& respData)OK this doesn't look very cool, but the view component isn't yet there, wait a little. What this handler does is to ask for some user data. Now the second handler follows, which will processes the submitted user data:
{
respData = " TestHandlerBase alive!!!! ";
respData =
"<html><head>"
"<title>TestHandlerBase is alive!</title>"
"</head><body>";
respData.append("<h1> Input your user data: </h1><p></p>");
respData.append(testform); // HTTP form for UserName, UserMail and Text, action="person"
respData += "</body></html>";
}
class TestHandlerPerson
: public MiniHttpHandler
{
public:
BASE_TEST_HANDLER_FUNC(TestHandlerPerson); // here process() is already defined
} g_handler2;
///////////// impl:
void TestHandlerPerson::process(AnalyzerRequest& reqData, std::string& respData)
{
respData =
"<html><head>"
"<title>TestHandlerBase is alive!</title>"
"</head><body>";
respData.append("<h2> Thank you for your data! </h2>");
// parse the data for display:
map<string,string>& data = reqData.formularData;
map<string,string>::iterator iter;
respData.append(
"<table border=\"1\" width=\"70%\"><tr>"
"<th><b>Field</b></th><th><b>Value</b></th>"
"</tr>");
for(iter = data.begin(); iter != data.end(); iter )
{
respData.append("<tr><td>").append(iter->first).append("</td>");
respData.append("<td>").append(iter->second).append("</td></tr>");
}
respData.append("</table>");
respData.append("<p></p> <a href=\"/index.html\">Back</a><br>");
respData += "</body></html>";
}
Rather grotty servlet or .jsp style here, isn't it? Mixing presentation with code?!
Don't despair, you can use a template. Yes, let's be modern! I chose the mustache :{{ template syntax. I decided for mustache, well, because it's pretty new, generates much buzz, and isn't looking too bad. For those who don't know mustache, it is a simple HTML templating syntax with values encoded like this:
{{value}}and sections, which can be rendered multiple times, if they receive a list as input data:
{{#section_X}}So there's no need to code anly loops in the template. YAWF contains an implementation of mustache :{{, which seems to render correctly all the examples I found in the online mustache docs. The implementation is based on a simple idea: the template is parsed into a linear sequence of "nodes", and the data items passed to the template take care of rendering and walking the node list. Have a look at the source code*** if you want.
{{value1}} => {{value2}}
{{/section_X}}
So our response handler would now look like this:
// using templating:It maybe doesn't look much better than the plain HTML code, but here we could save the template in file, and read it in at the runtime. And we've got the View part of the MVC pattern. The handler objects stand for "C", but so far don't have support for "M" in YAWF4q.
#include "CuteMstchData.hpp"
void TestHandlerPerson::process(CuteSrvRequest& reqData, std::string& respData)
{
string templ =
"<html><head><title>TestHandlerBase is alive!</title>"
"</head> <body> <h2> Thank you for your data! </h2>"
"<table border=\"1\" width=\"70%\">"
"<tr><th><b>Field</b></th> <th><b>Value</b></th></tr>"
"{{#datatable}}"
"<tr><td> {{field}} </td> <td> {{value}} </td></tr>"
"{{/datatable}}"
"</table><p></p>"
"<a href=\"/index.html\">Back</a><br>"
"</body></html>";
// convert data to JSON represenation:
map<string,string>& inpData = reqData.formularData;
map<string,string>::iterator iter;
int i;
ibkrj::yawf4q::CuteMstchValMap templData;
for(iter = inpData.begin(), i = 0; iter != inpData.end(); iter++, i++)
{
templData.addToList("datatable", i, "field", iter->first);
templData.addToList("datatable", i, "value", iter->second);
}
// display page with data (the View of MVC)
respData = CuteHttpHandler::renderThis(templData, templ);
}
Now, as last part of the puzzle, we have to configure the server paths, but this is simple:
#
# test configuration
#
index.html$ :: TestHandlerBase
person/.*$ :: TestHandlerPerson
You see, regular expressions are supported. Now let it run baby! The first handler gives us:
Then we willingly give our data:
And see the results:
4. Summing up
At the moment the code*** is only very basic, as I haven't got time to work on it a much as I'd like. I wrote it on my private time, as my customer did decide in favor of a general, grand-scheme, system management solution. So as for today, I wrote only a single test, that one which was described above, and tested it with Visual C++ on Windows. YAWF isn't more than a toy just now as many features are simply missing, for example I'd like to have a built in support for configurable error pages to round the things off. And as next thing, I'd like to add the Model component using my CuteWorkers framework (I hope I'll come to write a post about Qt-workers too).
But there's much more that could done, like:
- HTTPS support in the sever (that shouldn't be very difficult as Qt offers QSslSocket and other classes)
- cookies and session data handling
- dynamic loading and caching of the templates
- dynamic loading of the servelts vial DLL (kind of what ASP is doing)
- a "Clojure example" like dynamic interface
and so on, and so on. Look at my TODO list in the github repository. Nonetheless it's simple (KISS!), it's pluggable, it doesn't have any dependecies other than Qt. Requirements fullfilled!
--
* check out these previous posts: servlets-in-c.html, c-servlets-again.html, c-server-pages.html
** we cannot forget that C++ hasn't much to offer in terms of introspection. But we won't give up automatic registration and detection of handler prototypes!
*** You can find the code at Github: https://github.com/mrkkrj/yawf4q
9 comments:
I know at least two more based on Qt: QtWUI and Creole. Both of them seem to be dead, though. There is also the Qt Web Client experiment in Qt Labs, which seems dead too.
Cautionary tale: the first version of Wt (witty) was based on Qt but they moved to Boost after they found the Qt threading model did not fit their needs.
FYI: Wt mixes with Qt pretty straightforward. There's an example (wtwithqt) that demonstrates how the event loops can interact.
@Pau
Well, one time they say they didn't like Qt's signals, but the other time that the licence model wasn't right. I guess the second is true, as prevoiusly you had to pay for Qt, and if you already have to pay for Wt it's not that cool.
@wdu
correct, I've seen elsewhere an example of Boost & Qt slot/signal mechanisms working together... But for my purpose it's a little too big, and it isn't free (think so).
Nice write-up, I like what you've done -- it's OOP, which more people might be able to grok than the GP I use in cpp-netlib. :)
Also, the correct link for cpp-netlib is http://github.com/cpp-netlib/cpp-netlib. The Sourceforge project is now "defunct". I should really delete that project on Sourceforge... ;)
@Dean Michael
Oops, don't know how I couls mix the links up! Will correct ASAP.
Concerning OOP vs GP - if you are working with Qt you feel like a prisoner in the 90-ties, not much modern C++ :(
Great post, I've been after that?!?
Agustin
Hi Marek,
Nice post this one...
I have been developing a C++ web application framework (ffead-cpp) lately and have tried to implement whatever i thought would be useful.
I believe in keeping things simple, and this simplicity has driven the development until now. It would be great if you could please review the framework and provide an honest opinion on the same.
I'd love to hear your thoughts on Restbed.
* GNU License.
* Asynchronous RESTful framework.
* HTTP 1.0/1.1 compliant.
* Aiming for HTTP 2.0 compliance.
https://github.com/corvusoft/restbed.
We need to add testing capabilities for Web services.
SOAP / REST / JSON Web services testing and automation can be done with SRJTester with following capabilities:
1. Automated testing of SOAP / REST / JSON Web services
2. Support for https/http/Client/server certificate and many more features
3. Customization as per your requirements
We can be reached for customizing this tool for your web services testing requirements
Post a Comment