Simple webserver with libevent in C++

For a project written in C++ I wanted to use a webserver component, one that is not too heavyweight, but still capable. Performance should be (of course!) good, but for side-projects I put more emphasis on ease of use. It should be fun after all! My use case is quite simple, I want to implement custom logic for handling an incoming HTTP request. So when localhost:1234/some/file.png is requested, a handler function is triggerd and I can execute some custom logic to return some reply to the client.

After some research I considered boost asio and Facebooks Proxygen. Both libraries are surely up to the job, but I didn't immediately understand from the exmaples how things work, so I disregarded both without bothering too much - too heavyweight.

After a bit more research, I landed finally at libevent, which I haven't used in the longest time. The library for asynchronous I/O scheduling has some components contained in the <event2/http.h> header that help with creating a basic socket server. For the time being I don't mind the fact that this server is single-threaded. The documentation and examples around libevents http component were thorough enough to get me started.

Make it

The first thing I did when setting up this new project was to create a Makefile for it. The second thing I did when setting up this project was giving up writing the Makefile. Even though I have a somewhat clean template for Makefile's I still find the experience quite frustrating whenever I need to change something in that template, like, for example linking against an external library. Which happens rather frequently.

So I went on to write the cmake CMakeLists.txt file for it, which worked almost on the first try. In the process of doing this I discovered how to properly define parameters for cmake, documenting them and even setting default values for them. The build file looks like this:

I added parameters for passing in name/path of libevent, because I have a local build of libevent in one of my dev folders in order to have full control of what library I link against, avoiding linking against the system-wide libevent library that comes with mac OS. By default it will link against the system-wide installation of libevent, as the default values for the parameters indicate.

Handling requests

As mentioned before, I want to handle incoming requests, registering a wildcard "request handler", that is called whenever a client sends a request to the server. Libevent makes this easy, so here is the setup code in the main() function, starting an event loop and listening to a certain port on localhost.

Not so special, the main part is evhttp_set_gencb(http, handle_file, argv[1]); which registers a callback function handle_file that is the catchall handler.

Translating the URI to a path

When a request comes in, such as http://localhost:8881/foo.jpg I want to do something with /foo.jpg, that is, the decoded part of the incoming URI. Whatever I do with that part, be it loading and serving the file, incrementing statistics or something else, in the handler for the request I first need to extract the requested file name from the full URI.

In order to do so, I created a little helper function that can help me with this task. It takes the URI as input and returns the decoded path. It also uses std::tuple to indicate if something went wrong. This is more elegant than throwing exceptions, because if the component fails at decoding a URL, this is hardly something exceptional and should not interrupt the normal flow of the program. Instead, the library should clearly communicate what went wrong, so that the caller can decide how to proceed (in this case a 400 - Bad Reuqest response could be appropriate.)

The helper function looks like this:

Implementing the request handler

The last part is now to implement the actual callback handle_file. This implementation doesn't do anything interesting, it only prints the requested path to stdout and returns 404 - Not Found.

Here we could load a file from disk, determine the mime-type and return it back to the caller, return HTML or do anything else that's interesting. When serving local files, it's really important to make sure that the client is actually allowed to access the file, path injection and the likes are common attack vectors against home-grown servers like this.

With the last part now added, the webserver is functional and can be compiled and started. After starting the webserver we can test if it works using a simple curl command like curl http://localhost:8881/testing.

The whole implementation of this simple webserver in libevent and C++ can be found on GitHub.