Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Embedding a Webserver in your C++ Application

4.74/5 (30 votes)
16 Jul 2011CPOL7 min read 125.8K   4.3K  
Embedding a webserver in your C++ application using the Lacewing networking library

Introduction

This article will explain how to integrate an embedded webserver with your Win32/C++ application, using the Lacewing networking library. It could be used for things like:

  • Web administration - Server applications often have a web interface, for remote administration - nothing but a web browser is required to administrate the server
  • Web applications - Web applications written in C++ can be much faster and more scalable than PHP or ASP, with lower resource usage and nothing else to be installed

Background

I decided to write my own webserver class as an experiment over the Lacewing::Server class, which is designed for scalability using IO completion ports, epoll or kqueue.

Lacewing::Webserver soon became a powerful HTTP server, featuring things like:

  • HTTP GET/POST with automatic parameter parsing
  • Non-blocking file sending
  • Cookies and sessions
  • Multipart file upload
  • Getting/setting the last modified date, to easily implement caching
  • Full HTTPS support

Installing the Library

Lacewing runs on both Windows and many flavours of *nix (including Linux, BSD, Mac OS X and even Android). This article, however, was originally written for the Windows version - the code for other platforms is identical, but the installation procedure will be different.

For Windows, the first thing is you'll need to download and install the Lacewing Win32 binaries from the liblacewing website. The Lacewing.dll file will need to be distributed alongside your executable file, but you may put it in your Windows\System32 folder (or Windows\SysWOW64 on a 64 bit system) for development convenience if you want.

Put the path to the folder with Lacewing.h and Lacewing.lib in your VC++ directories, in both the header and library sections. This will be under Tools > Options > Projects and Solutions > VC++ directories in Visual C++ 2008. Once the path is added, you should be able to #include <Lacewing.h> in your code, which will automatically link with Lacewing.lib.

Using the Class

For any class in Lacewing to function, you'll first need an instance of Lacewing::EventPump, which is responsible for watching for events and "pumping" them to the interested classes.

You can then create an instance of Lacewing::Webserver, passing the event pump to the constructor.

C++
Lacewing::EventPump EventPump;
Lacewing::Webserver Webserver (EventPump);  

This is already ready to host, and would show a blank page. To make it show something interesting, you'll need to register some handlers. We're only interested in responding to GET for now, but there are more handlers available for things like HEAD, POST, error handling and multipart file upload - see the Lacewing::Webserver<code> class definition in Lacewing.h for the full list.

An example GET handler looks like this:

C++
void onGet (Lacewing::Webserver &Webserver, Lacewing::Webserver::Request &Request)
{
	Request << "Welcome to " << Request.URL ();
} 

This will output the text "Welcome to " followed by the requested URL. To register it with Lacewing so that it will be called when a GET request comes through, simply pass it to the onGet method of your Webserver instance.

When all the initialisation is complete, Host() can be called to start the webserver. If no parameters are passed, it defaults to port 80. You might want to use Host(81) or something similar if port 80 is taken on your system.

C++
Webserver.onGet (onGet);
Webserver.Host (); 

And that's it - providing the Webserver object stays in scope, you should be able to open a web browser and visit http://localhost/home to be greeted with:

Welcome to home

If you ran the webserver on a port other than 80, you'll need to visit something like http://localhost:82/home.

Working with Files

The Request object passed to the GET handler provides two functions to make it simple to send files from the disk:

  • SendFile - Sends a file by filename. The file will not be loaded into memory (TransmitFile is used on Windows, sendfile on *nix)
  • GuessMimeType - Sets an appropriate MIME type for a file extension or filename. For example, a htm file will set the MIME type text/html, while a png file will set the MIME type image/png.

We can easily use these to build a simple webserver, hosting the current directory.

C++
void onGet(Lacewing::Webserver &Webserver, Lacewing::Webserver::Request &Request)
{
	Request.GuessMimeType(Request.URL());
	Request.SendFile(Request.URL());
}  

Note that nothing is actually sent until after the handler is complete, so it wouldn't matter if the GuessMimeType and SendFile functions were called the other way around. All the functions in Lacewing return instantly, and you can do things like set cookies and change HTTP headers right until the handler returns.

With this new GET handler calling the two functions, it's now possible to put an entire website with HTML/CSS/JS in the same folder as the webserver and access it from a web browser. This can be combined with custom behaviour in C++ to do things like C++ based AJAX applications (which is a pretty awesome idea). For now, I just tried it with an image:

Lacewing logo transmitted over HTTP

Setting Up HTTPS

It might surprise you that Lacewing (on Windows) doesn't use OpenSSL or any other library to implement HTTPS. Like the rest of the library, it's built on the APIs provided by the operating system - Windows provides functions for secure sockets, as used by Internet Explorer and IIS. The API isn't very pleasant to use (start here if you're interested), but Lacewing wraps it nicely in Lacewing::Webserver into these functions:

  • HostSecure - Like the Host function, but for HTTPS rather than HTTP. After this has been called, the handlers will be called as normal for HTTPS requests, but Request.Secure() will return true. Either LoadCertificateFile or LoadSystemCertificate must be called before this.
  • LoadCertificateFile - Loads a certificate from a certificate file (CER or PFX). Takes two parameters - the first is the filename of the certificate file, and the second is the common name used when creating the certificate. *nix OR Windows Vista and greater only.
  • LoadSystemCertificate - Loads a certificate that has been imported into the system, (import it by opening the certificate file and following the wizard). Takes three parameters - the first is the store name (the predefined system stores are "MY", "Root", "Trust" and "CA"), the second is the common name used when creating the certificate, and the third (optionally) is which certificate store to look in, the default being "CurrentUser". More information about the system certificate stores can be found here. Windows XP and greater only.

You will need to obtain a certificate from a signing authority, or generate a self-signed certificate for testing. Doing this is beyond the scope of this article, but there's a nice tutorial on generating self-signed certificates with OpenSSL here.

My test certificate with the common name "localhost" has already been imported, so now I can change my initialization code to this:

C++
Webserver.onGet(onGet);
Webserver.Host();

Webserver.LoadSystemCertificate("MY", "localhost");
Webserver.HostSecure();  

And now if I visit https://localhost/lacewing_02.png...

Lacewing logo transmitted over HTTPS

Forms

Lacewing::Webserver has full support for two kinds of request parameters:

  • GET parameters are added as part of the URL. If you were to load http://localhost/home?foo=bar, you could then use Request.GET("foo") in the GET handler, which would return "bar". Multiple parameters can be used in the form http://localhost/home?foo=bar&foo2=bar2. These can be added manually by the user, via links, or via HTML forms with method="GET".
  • POST parameters are the body of the HTTP request, rather than part of the URL. They are added by HTML forms with method="POST", and can be retrieved in the POST handler with Request.POST("name").

GET parameters are limited by the URL length, and GET requests can be subject to browser caching. The general rule for deciding whether to use GET or POST is that GET is for specifying parameters for reading data, and POST is for specifying parameters for writing data.

Both GET and POST parameters can be used from JavaScript. I highly recommend jQuery, which I have used extensively for web applications with Lacewing::Webserver as a backend. The stream << operator in the Request object makes it very easy to generate JSON, which translates directly into Javascript objects.

I won't be covering anything like that in this article, but there is an example available in the library examples folder on GitHub. For a simple HTML form test to try out the POST functionality, you will first need a POST handler in addition to the GET handler:

C++
void onGet(Lacewing::Webserver &Webserver, Lacewing::Webserver::Request &Request)
{
	if(!strcmp(Request.URL(), "form"))
	{
		Request.SendFile("form.html");
		return;
	}
	
	Request << "Page not found";
}

void onPost(Lacewing::Webserver &Webserver, Lacewing::Webserver::Request &Request)
{
	if(!strcmp(Request.URL(), "submit"))
	{
		Request << "Hello, " << Request.POST("name") << "!";
		return;
	}
	
	Request << "Page not found";
}

The POST handler can be registered alongside the GET handler.

C++
Webserver.onGet (onGet);
Webserver.onPost (onPost); 

You'll also need this form.html file:

HTML
<form action="submit" method="post">

	Enter your name: <input name="name">
	<input type="submit" value="Go!">
</form>

And that's it! If you run the application, you should be able to visit http://localhost/form:

Lacewing logo transmitted over HTTPS

And submit...

Lacewing logo transmitted over HTTPS

Conclusion

Although I didn't cover every aspect of Lacewing::Webserver, I hope the article was a useful introduction. The rest of the functions are quite straightforward and consistently named, so it should be pretty easy to find your way around.

If you have any questions or suggestions, please do leave a comment or send me an email. The library is also now open source on GitHub with an issue tracker.

History

  • 3rd October, 2010: Initial post
  • 16th July, 2011: Updated with the new open source API

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)