Click here to Skip to main content
Click here to Skip to main content

A C++ Embedded Web Server

By , 1 Jun 2010
 
webem.gif

Introduction

Do you have a web page or two? Nothing fancy, perhaps, but a neat demonstration of what can be achieved with a handful of HTML tags? Do you have a sophisticated C++ Windows desktop application which now needs to be controlled and monitored remotely? So, without learning a whole new technology, let's give your application its own web page!

Webem is a web server that you can embed in your C++ application. It makes it easy to implement a browser GUI accessible from anywhere.

Webem is based on a minimally modified version of the boost::asio web server, and permits HTML code to execute C++ methods. Although you do not need to look at the server code to use Webem, you will need to download and use the BOOST libraries in your projects. I suggest that if you have never used BOOST before, then Webem is probably not for you.

Background

The available embedded C++ web servers are a challenge to use, and tend not to be Windows friendly. They are not the kind of thing you want to get into just to add the ability to monitor your lab application from your cell phone.

I have tried Wt (http://www.webtoolkit.eu/wt) but was defeated by the installation and learning curve.

I recently began using Webio by John Bartas. I liked the concept and it worked well.

However, I still found it overly complicated to use and the server code hard to understand. I wanted something easier to use, based on a well known web server that had only been slightly modified.

A lot of the complexity of Webio is caused by using an HML compiler to hide the HTML pages that control the appearance of the GUI inside a file system embedded inside the application code. I prefer to have the HTML pages outside in plain view where I can adjust the GUI without recompiling the application.

I learned a lot trying to adjust Webio to my taste, and eventually was ready to build my own exactly to my requirements.

Using the Code

You build your application's GUI exactly like you build a web site - create pages using HTML all starting from index.html.

Now you need to make the HTML invoke your C++ methods. There are two things you can do:

  • Create include methods which generate HTML to be included in the web pages
  • Create action methods called by webem when the user clicks on buttons in the web page
  • Create "web controls", a combination of the previous two where an include method generates a form which invokes an action method when the user clicks on a button - the "Calendar" example application shows how to create a control to display and update a database table.

"Hello, World"

Step 1: Create the web page. This can be as elaborate as you like, but let's keep it simple for our first "hello, world" application:

The Webem Embedded Web server says:  <!--#webem hello -->

The text in angle brackets tells webem where you want to include text from the application, and "hello" is the code for the particular application method that must be invoked to provide the included text.

Step 2: Create the class which says hello:

/// An application class which says hello
class cHello
{
public:
	char * DisplayHTML()
	{
		return "Hello World";
	}
};

Step 3: Initialize Webem, telling it the address and port to listen for browser requests and where to find the index.html that begins the web page.

// Initialize web server.
http::server::cWebem theServer(
		"0.0.0.0",				// address
               "1570",					// port
	         ".\\");					// document root 

Step 4: Register the application method with webem:

cHello hello;

// register application method
// Whenever server sees <!--#webem hello -->
// call cHello::DisplayHTML() and include the HTML returned

theServer.RegisterIncludeCode( "hello",
	boost::bind(
	&cHello::DisplayHTML,	// member function
	&hello ) );		// instance of class

Step 4: Finally, you are ready to start the server running.

// run the server
theServer.Run();

A Formal Hello

Let's create a more polite program which addresses the world by name (CodeProject is, after all, Canadian.)

Step 1: Create the website:

What is your name, please?
<form action=name.webem>
<input name=yourname /><input value="Enter" type=submit />
</form>

The Webem Embedded Web server says: <!--#webem hello -->

The form provides a field to enter the user's name, and a button to submit the entered name to the server. The form attribute "action=name.webem" ensures that the webem server will call the application method registered with "name" to process the input.

Step 2: Create the application class:

/// An application class which says hello to the identified user

class cHelloForm
{
string UserName;
http::server::cWebem& myWebem;

public:
	cHelloForm( http::server::cWebem& webem ) :
	  myWebem( webem )
	{
		myWebem.RegisterIncludeCode( "hello",
		boost::bind(
		&cHelloForm::DisplayHTML,	// member function
		this ) );			// instance of class
		myWebem.RegisterActionCode( "name",
		boost::bind(
		&cHelloForm::Action,	// member function
		this ) );			// instance of class
	}
	char * DisplayHTML()
	{
		static char buf[1000];
		if( UserName.length() )
		sprintf_s( buf, 999,
			"Hello, %s", UserName.c_str() );
		else
			buf[0] = '\0';
		return buf;
	}
	char * Action()
	{
		UserName = myWebem.FindValue("yourname");
		return "/index.html";
	}
};

The application class stores a reference to the webem server. This allows it to look after registering its own methods with the server when it is constructed, and to call the cWebem class FindValue() to extract the value of the field entered into the form.

The application class must register two methods, one to save the entered user name when the submit button is clicked, one to display the stored user name when the web page is assembled and sent to the browser.

The action methods must return the webpage which should be displayed in response to the click on the submit button.

Note that all action methods are invoked by Webem before the include methods, so that the web page always displays updated data.

Step 3: Construct webem, construct the application class and run the server:

// Initialize web server
http::server::cWebem theServer(
	"0.0.0.0",				// address
	"1570",					// port
	".\\");					// document root

// Initialize application code
cHelloForm hello( theServer );

// run the server
theServer.Run();

You may need to run the server in another thread, perhaps so your application can continue logging data from a lab device. To do this, modify the call to server::run:

boost::thread* pThread = new boost::thread(
boost::bind(
&http::server::server::run,		// member function
&theServer ) );			// instance of class

Webem Controls

Webem controls are classes which look after the details of displaying and manipulating application data in standard ways, so that the application programmer does not need to be concerned with all the details of generating HTML text.

The demo application uses a webem control to list the contents of a SQLITE database table with the ability to add or delete records. There is a screenshot at the top of this article.

Unicode

Webem supports Unicode application include functions, which means that your code can generate and have displayed Chinese, Cyrillic, even Klingon characters. Write an include funcion that returns a wide character string UTF-16 encoded, register it using the RegisterIncludeCodeW() function ( instead of RegisterIncludeCode() ) and Webem will convert it to UTF-8 encoding before sending it back to the browser. Like this:

class cHello
{
public:
	/**
	Hello to the wide world, returning a wide character UTF-32 encoded string 
         with chinese characters

	*/
	wchar_t * DisplayWWHello()
	{
		return L"Hello Wide World.  
                  Here are some chinese characters: \x751f\x4ea7\x8bbe\x7f6e";
	}
};

...

	theServer.RegisterIncludeCodeW( "wwwhello",
		boost::bind(
		&cHello::DisplayWWHello,	// member function
		&hello ) );			// instance of class

If you wonder why UTF-8 and UTF-16 are necessary, check out my blog article, World Wide Characters.

Points of Interest

This section describes how webem is integrated with the server. It is not necessary to read this in order to use webem.

The boost::asio HTTP server invokes the method:

request_handler::handle_request( const request& req, reply& rep)

This is where the browser requests are parsed and the new page assembled to be sent back to the browser. We need to override this method in a specialization of the request handler so that, in turn, the registered application methods can be invoked.

void cWebemRequestHandler::handle_request( const request& req, reply& rep)
{
	// check for webem action request
	request req_modified = req;
	myWebem.CheckForAction( req_modified.uri );

	// call base method to do normal handling
	request_handler::handle_request( req_modified, rep);

	// Find and include any special cWebem strings
	myWebem.Include( rep.content );
}

Unfortunately, the boost::asio server, although otherwise very elegantly designed and implemented, has not been designed with inheritance in mind. In order to allow the server to invoke the webem request handler, I have made minimal changes to the boost code:

  • Made request_handler::handle_request method virtual so specialized override will be called
  • Changed server constructor to accept a reference to the request_handler it will use.
  • Changed order of server attributes so that the connection handler will not be initialized before the request handler.

Handling Post Requests

The boost:asio server did not handle post requests, often used for entering passwords. Adding this feature and getting it to work for different browsers (FireFox, Internet Explorer and Chrome) required detailed modifications to the asio request handler, which I will not go into here.

History

  • 2008 Sep 12
    • First release
  • 2008 Sep 15
    • Moved server construction into cWebem constructor, which simplifies application code
    • Provided two simpler examples, "Hello World" and "Formal Hello"
  • 2008 Sep 29
    • Fixed quotes in code fragments (I hope!)
  • 2009 Mar 9
    • Fixed include of modified server code in cWebem.h; fixed build errors in release configuration
  • 2009 May 5
  • 2010 May 30
    • Bug Fix: missing HTML input field value would use previous field value
    • Feature: handle post requests from browser, useful for entering passwords
    • Feature: Support for Unicode application include functions, useful for Chinese characters
    • Documentation: Examples in folder under source code, doxygen generated source documentation

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

ravenspoint
Founder Raven's Point Consulting
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
NewsSimple button actionsmemberravenspoint11 Apr '13 - 6:20 
Sometimes you may need to run an action when the user clicks a 'button' but you do not need to pass any parameters. In such cases setting up an html form can seem like too much trouble and rather limiting in the format you can display. So I have added click action requests.
 
If you add this to your html file
 
<a href="http:/index.html/webem_name">button_label</a>
 
Then when the user clicks on "button_label" then webem will invoke the function registered to "name" and then display the page at index.html.
 
To obtain the code for this feature, and other changes and fixes since the article was written, go the fossil repository at http://speakertorobots.com/cgi-bin/webem.cgi/home[^]
QuestionVC6 project ?memberFlaviu28 Nov '11 - 20:34 
Do you have an VC6 project of this web server ? If yes, could you please give me, or, better, could you update your project ?
AnswerRe: VC6 project ?memberravenspoint9 Nov '11 - 1:49 
I do not have a VC6 project. You could try creating one from the source code. However VC6 is now extremely old, and it is possible that the compiler will not handle the current boost libraries. You also should realize that the VC6 STL library has problems. If you are doing serious, advanced coding then you need to upgrade.
GeneralRe: VC6 project ?memberKochise24 May '13 - 4:45 
Serious coding ? Professional applications were made using VC6, so I don't see quite well where VC6 fails. STL implementation ? Replaceable. Compiler's optimization level ? Link VC6's IDE to Microsoft's free VS2003 compiler. C/C++ remains C/C++ and new Visual Studio's incarnations will not revolutionize coding and send men into the moon anytime soon.
 
Kudo for recreating the whole VC6 project from scratch, no other choice.
 
Kochise
 
In Code we trust !
QuestionCompiling Webem on LinuxmemberLiquidL28 Oct '11 - 19:21 
How would one compile Webem under Linux? I have boost installed and am eagerly interested in getting this to work. Any tips appreciated.
 
Regards,
JM
AnswerRe: Compiling Webem on Linuxmemberravenspoint29 Oct '11 - 2:07 
Thank you for your interest in WEBEM. I am not aware of any problems compiling under linux. Have you attempted to do so? Did you hit a problem of some kind? James
GeneralRe: Compiling Webem on LinuxmemberLiquidL31 Oct '11 - 8:15 
Hi,
Not thus far. I am working on changing the cUTF class to be Linux specific. Then, I'll try to make a library out of the base code, and then link then examples to that library. I'll post back when I get it working!
Thanks for a very nice project.
GeneralRe: Compiling Webem on Linuxmemberravenspoint31 Oct '11 - 10:04 
Ah yes, cUTF is a wrapper for
WideCharToMultiByte()
so this will be a problem for a UNIX port. If you get a nice solution, please post it.
 
James
GeneralRe: Compiling Webem on LinuxmemberLiquidL1 Nov '11 - 13:34 
James,
I did get it working in Linux. Let me know how I can contribute back to this project. I'm thinking of separating the Windows and Linux code with #ifdefs.
Also had to make some minor changes to get rid of warnings that g++ was generating. Right now I have the webemHello example working on Linux.
 
JM
GeneralRe: Compiling Webem on Linuxmemberravenspoint2 Nov '11 - 3:48 
JM,
 
The first thing to do is get a copy of the latest code. Have you done this? Instructions for doing so are here:
 
http://66.199.140.183/cgi-bin/webem.cgi/wiki?name=download[^]
 
If you are going to contribute changes, then you'd best clone the repository.
 
When you are happy with your changes, check them into your copy of the repository.
 
If everything looks good, send me a private email ( there is 'email' link below this message ) with your email address and I will provide you with permission to push your checkin.
 
James
GeneralRe: Compiling Webem on LinuxmemberLiquidL8 Nov '11 - 7:09 
Yes I did get the latest copy.
 
I have also fixed some other minor bugs and also added JavaScript support. Since this is going to be a 'low-powered' webserver, JS allows the webserver to 'push' processing to the client side which I think is a great advantage.
 
I have been busy actually doing the website coding which is a new paradigm for me since my experience is mostly in the embedded field. Soon as I get a chance I'll email you some diffs of the bugfixes so you can take a look and see if you want to apply them.
 
Cheers.
GeneralRe: Compiling Webem on Linuxmemberravenspoint8 Nov '11 - 8:56 
This is all way too much to take in at one time.
 
We need to split it up into manageable chunks. The first thing, as always, is to tackle the bugs. If you have found some bugs, please report them. It would be helpful if you started a new thread for each one, if you would like to use this forum. You could also open tickets on the repository, if you wish. Send me a private email with your name and email address, and I will provide you with the required access.

James Bremner
GeneralRe: Compiling Webem on Linux [modified]memberLiquidL11 Nov '11 - 7:37 
Ok, I'll email you my info and didn't mean to overwhelm you.

modified 11 Nov '11 - 14:15.

GeneralRe: Compiling Webem on Linuxmemberkarciarz15 Jul '12 - 8:51 
Hi, where did you post your changes for Linux?
Jarek

GeneralRe: Compiling Webem on Linuxmemberravenspoint18 Jul '12 - 8:06 
LiguidL said that he had changes to allow webem to compile on Unix. I provided him with access to the repository so he could submit the changes - but he never did so.
BugCSS Support [modified]memberJustin Russell Howard27 Oct '11 - 5:21 
For some reason there's no CSS support within this code. To fix this two things need to be done:
 
1) Edit mime_types.cpp, add a css entry and migrate the UTF-8 hard coding:
 
struct mapping
{
  const char* extension;
  const char* mime_type;
} mappings[] =
{
  { "gif", "image/gif" },
  { "htm", "text/html;charset=UTF-8" },
  { "html", "text/html;charset=UTF-8" },
  { "jpg", "image/jpeg" },
  { "png", "image/png" },
  { "css", "text/css" },
  { 0, 0 } // Marks end of list.
};
 
2) Remove the UTF-8 hard coding from cWebem.cpp:
 
void cWebemRequestHandler::handle_request( const request& req, reply& rep)
{
	// check for webem action request
	request req_modified = req;
 
	myWebem->CheckForAction( req_modified );
 
	// do normal handling
	request_handler::handle_request( req_modified, rep);
 
	// Find and include any special cWebem strings
	myWebem->Include( rep.content );
 
	// adjust content length header
	// ( Firefox ignores this, but apparently some browsers truncate display without it.
	// fix provided by http://www.codeproject.com/Members/jaeheung72 )

	rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
 
	// tell browser that we are using UTF-8 encoding
	rep.headers[1].value = "text/html;charset=UTF-8";
}
 
Problem did my head in for a while, but I got there in the end. I also recommend adding a Stop() member function for the Run() such that a thread can shutdown gracefully when closing.

modified 27 Oct '11 - 11:40.

GeneralRe: CSS Supportmemberravenspoint27 Oct '11 - 6:37 
Justin,
 
Thank you for your contribution to WEBEM.
 
The reason that CSS is not supported is that I do not use CSS. My preference is to keep things simple. CSS is obviously a benefit to people creating large elaborate websites, so that they can modify the appearance of every page with one change. I doubt that WEBEM would be appropriate for any such website.
 
An important goal of designing WEBEM was to keep changes to the boost::asio http server to an absolute minimum. Your change to the boost code looks harmless. Still I would prefer to avoid it. Can you explain what the change is for?
 
James
GeneralRe: CSS Supportmemberravenspoint27 Oct '11 - 8:39 
OK, I have looked into this. The issue is that external CSS style sheets are not supported ( internal style sheets are supported out of the box )
 
To fix this requires a one line addition to server/mime_types.cpp
 
@@ -23,10 +23,11 @@
   { "gif", "image/gif" },
   { "htm", "text/html" },
   { "html", "text/html" },
   { "jpg", "image/jpeg" },
   { "png", "image/png" },
+  { "css", "text/css" },
   { 0, 0 } // Marks end of list.
 };
 
 std::string extension_to_type(const std::string& extension)
 {
 
I don't why Justin changed the encoding of htm and html or modified the handle_request(), but those changes are not necessary.
 
If you want to use external style sheets, you can make the above change to the code downloaded from codeproject, or you can download the latest and greatest from the fossil repository at http://66.199.140.183/cgi-bin/webem.cgi/home[^]. If you go to the fossil repository you will get other small fixes since the article was published.
 
Thanks again to Justin for the contribution.
 
James
GeneralRe: CSS Supportmemberravenspoint27 Oct '11 - 9:34 
Justin,
 
My first goal is to minimize changes to the existing code-base. This minimizes the chance of breaking something in the installed user base, and simplifies backing out the change if something does get broken.
 
My second goal is to minimize the changes to the boost code, so that if the boost code ever needs to be updated to a new boost release, the integration will be as simple as possible.
 
The result is that making changes because something 'looks bad' to you is not going to happen, unless you can show that it actually causes a real problem.
 
James
GeneralRe: CSS SupportmemberJeroen Walter26 Nov '11 - 1:45 
The reason why that line is commented out in cWebemRequestHandler::handle_request is because it overwrites (and thus breaks) the content type header just filled in by request_handler::handle_request.
request_handler::handle_request fills in the correct mime type, i.e. css, which then promptly gets overwritten with text/html......
GeneralRe: CSS Supportmemberravenspoint26 Nov '11 - 4:54 
Jeroen, Thanks for joining in.
 
When you say 'breaks', do you mean something stops working? In my tests, noting is broken. Can you describe, or even post a test showing, what is broken, please.
 
James
GeneralRe: CSS SupportmemberJeroen Walter28 Nov '11 - 1:19 
Hi
 
For example, FireFox 8 generates a parser error when loading a css file with the incorrect mime type.
 
Regardless, that line always overwrites the mime type that was just set by request_handler::handle_request, so not just for css but for every extension.
And that can't be the intention I think....
 
Also, the call myWebem->Include(...) is not only called for html files, but for all files, so also for large images etc.
It would be better to only call this for certain files, such as html and css and maybe js files.
GeneralRe: CSS Supportmemberravenspoint28 Nov '11 - 8:27 
I just tried webem with an external stylesheet and Firefox 8. I noticed no error.
 
I have added the test example to the repository at http://66.199.140.183/cgi-bin/webem.cgi/home[^]
 
James
GeneralRe: CSS Support [modified]memberJeroen Walter28 Nov '11 - 21:28 
Thanks for the example, this one doesn't cause an error in firefox.
I'm not at home now, so I can't look up why my files caused an error, will do that tonight.

 
[edit]Sorry, I simple opened the example html in Firefox from disk instead of via Webem. That was stupid of me.
So I don't know yet if it will cause an error. I will try it when I'm at home again.[/edit]
 
However......
The point still remains that the line 378 in CWebem.cpp 'rep.headers[1].value = "text/html;charset=UTF-8";'
still overwrites ALL mime types that were just set by the call request_handler::handle_requestin line 366.
 
And this is just not correct, sorry.
 
Nevertheless, I still want to say that webem is very cool, has a low learning curve without external dependencies except for boost, which I already use, and after some changes, is usable for my purposes.
So thanks ! Smile | :)

modified 29 Nov '11 - 6:01.

GeneralRe: CSS Supportmemberravenspoint29 Nov '11 - 1:41 
Thank you for the kind words about WEBEM.
 
I would like to see a simple test example that shows the problem you describe.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 1 Jun 2010
Article Copyright 2008 by ravenspoint
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid