How to build a custom static file serving HTTP server using Libevent in C

Libevent is an event notification library which lays the foundation for immensely successful open source projects like Memcached. As the web advances into a real time mode, more and more websites are using a mix of technologies like HTTP Pub-Sub, HTTP Long-polling and Comet with a custom light weight HTTP servers in the backend to create a real time user experience. In this blog post, I will start with necessary prerequisites for setting up the development environment. Further, I will demonstrate how to build a HTTP server capable of serving static pages. Finally, I will put up a few use cases of a custom HTTP server in today’s world.

Setting up Environment
Follow the following steps to install the latest version of libevent (version 2.0.3-alpha)

  • $ wget http://www.monkey.org/~provos/libevent-2.0.3-alpha.tar.gz
  • $ tar -xvzf libevent-2.0.3-alpha.tar.gz
  • $ cd libevent-2.0.3-alpha.tar.gz
  • ./configure
  • make
  • sudo make install

Check the environment by running the following piece of C code (event2.cpp):

#include <event2/event.h>

int main(int argc, char **argv) {
	const char *version;
	version = event_get_version();
	printf("%sn", version);
	return 0;
}

Compile and run as following:

$ g++ -arch x86_64 -Wall -levent event2.cpp -o event2
$ ./event2
$ 2.0.3-alpha

I had to pass -arch x86_64 flags on Mac OSX 10.5.8. This can vary depending upon your operating system.

Libsrvr: Static file serving HTTP Server
Below is the C code for a static file serving HTTP server using libevent called “Libsrvr”:

libsrvr.h

// General purpose header files
#include <iostream>
#include <getopt.h>
#include <sys/stat.h>

// Libevent header files
#include </usr/local/include/event2/event.h>
#include </usr/local/include/event2/http.h>
#include </usr/local/include/event2/buffer.h>

// Libsrvr configuration settings
#define LIBSRVR_SIGNATURE "libsrvr v 0.0.1"
#define LIBSRVR_HTDOCS "/Users/sabhinav/libsrvr/www"
#define LIBSRVR_INDEX "/index.html"

// Libsrvr http server and base struct
struct evhttp *libsrvr;
struct event_base *libbase;

// Libsrvr options
struct _options {
	int port;
	char *address;
	int verbose;
} options;
  • LIBSRVR_SIGNATURE is the server signature sent as response header for all incoming requests
  • LIBSRVR_HTDOCS is the path to the the DocumentRoot for libsrvr
  • LIBSRVR_INDEX is the similar to DirectoryIndex directive of apache

libsrvr.cpp

#include </Users/sabhinav/libsrvr/libsrvr.h>

void router(struct evhttp_request *r, void *arg) {
	const char *uri = evhttp_request_get_uri(r);

	char *static_file = (char *) malloc(strlen(LIBSRVR_HTDOCS) + strlen(uri) + strlen(LIBSRVR_INDEX) + 1);
	stpcpy(stpcpy(static_file, LIBSRVR_HTDOCS), uri);

	bool file_exists = true;
	struct stat st;
	if(stat(static_file, &st) == -1) {
		file_exists = false;
		evhttp_send_error(r, HTTP_NOTFOUND, "NOTFOUND");
	}
	else {
		if(S_ISDIR(st.st_mode)) {
			strcat(static_file, LIBSRVR_INDEX);

			if(stat(static_file, &st) == -1) {
				file_exists = false;
				evhttp_send_error(r, HTTP_NOTFOUND, "NOTFOUND");
			}
		}
	}

	if(file_exists) {
		int file_size = st.st_size;

		char *html;
		html = (char *) alloca(file_size);

		if(file_size != 0) {
			FILE *fp = fopen(static_file, "r");
			fread(html, 1, file_size, fp);
			fclose(fp);
		}

		struct evbuffer *buffer;
		buffer = evbuffer_new();

		struct evkeyvalq *headers = evhttp_request_get_output_headers(r);
		evhttp_add_header(headers, "Content-Type", "text/html; charset=UTF-8");
		evhttp_add_header(headers, "Server", LIBSRVR_SIGNATURE);

		evbuffer_add_printf(buffer, "%s", html);
		evhttp_send_reply(r, HTTP_OK, "OK", buffer);
		evbuffer_free(buffer);

		if(options.verbose) fprintf(stderr, "%st%dn", static_file, file_size);
	}
	else {
		if(options.verbose) fprintf(stderr, "%st%sn", static_file, "404 Not Found");
	}

	free(static_file);
}

int main(int argc, char **argv) {
	int opt;

	options.port = 4080;
	options.address = "0.0.0.0";
	options.verbose = 0;

	while((opt = getopt(argc,argv,"p:vh")) != -1) {
		switch(opt) {
			case 'p':
				options.port = atoi(optarg);
				break;
			case 'v':
				options.verbose = 1;
				break;
			case 'h':
				printf("Usage: ./libsrvr -p port -v[erbose] -h[elp]n");
				exit(1);
		}
	}

	libbase = event_base_new();
	libsrvr = evhttp_new(libbase);
	evhttp_bind_socket(libsrvr, options.address, options.port);
	evhttp_set_gencb(libsrvr, router, NULL);
	event_base_dispatch(libbase);

	return 0;
}

Here is some explanation for the above code:

  • Command line options are parsed using GNU getopt library
  • libbase is the event base for HTTP server libsrvr.
  • HTTP server is bind to port 4080 (by default).
  • A callback function is registered for each incoming HTTP request to libsrvr. Function router is invoked every time a HTTP request is received
  • Finally libbase is dispatched and code never reaches return 0

The working of the router function is as follows:

  • Incoming request uri is converted to absolute file path on the system
  • Checks for file or directory existence is done
  • If absolute path is a directory, LIBSRVR_INDEX is served out of that directory

Launching Libsrvr:
Compile and run the libsrvr as follows:

$ g++ -arch x86_64 -Wall -levent libsrvr.cpp -o libsrvr
$ ./libsrvr -v
/Users/sabhinav/libsrvr/www//index.html	538
/Users/sabhinav/libsrvr/www/assets/style.css	35
/Users/sabhinav/libsrvr/www/assets/script.js	27
/Users/sabhinav/libsrvr/www/dummy	404 Not Found
/Users/sabhinav/libsrvr/www/index.html	538
/Users/sabhinav/libsrvr/www/assets/style.css	35

If started under verbose mode (-v), libsrvr will output each requested file path on the console as shown above.

Use cases
Below are a few use cases of a custom HTTP server as seen in web today:

  • Facebook Chat: Uses a custom http server based on mochiweb framework
  • Yahoo finance: Uses a custom http streaming server based on libevent

Generally, iframe technique is combined with javascript hacks for streaming data from the custom http servers. Read “How to make cross-sub-domain ajax (XHR) requests using mod_proxy and iframes” for details.

Conclusion
Though a static file server find little place in today’s world, the idea was to show the ease by which you can create your own HTTP server which is light weight, fast and scalable (all thanks to Niels for his libevent). Couple libsrvr with memcached for caching static files, and benchmark will show over 10,000 req/sec handling capability of libsrvr.

Share if you like it and also let me know your thoughts through comments.

Writing a custom unix style tail in PHP using Libevent API on Mac OS X 10.5.x and other platforms

Libevent is a library which provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Many famous applications/frameworks/libraries like memcached are using libevent. In this blog post, I will demonstrate how to write a custom unix style tail script using Libevent API in PHP.

Setting up the environment:
Setting up libevent with PHP is a little tricky. Below are the steps, I followed to make it work on Mac OSX 10.5. However the steps should be same for any other OS you choose to code on. Here we go:

  1. Check the version of libevent installed on your system. If you don’t have libevent or the installed version is < 1.4, you will need to compile libevent-1.4.x
    ~ sabhinav$ port list | grep libevent
    libevent                       @1.4.12         devel/libevent
  2. Uninstall existing libevent
    ~ sabhinav$ port uninstall libevent
  3. Add the following into your .bash_profile file:
    export MACOSX_DEPLOYMENT_TARGET=10.5
    export CFLAGS="-arch x86_64 -g -Os -pipe -no-cpp-precomp"
    export CCFLAGS="-arch x86_64 -g -Os -pipe"
    export CXXFLAGS="-arch x86_64 -g -Os -pipe"
    export LDFLAGS="-arch x86_64 -bind_at_load"
  4. Open a new terminal window. Download and extract libevent-1.4.12-stable
    ~ sabhinav$ wget http://www.monkey.org/~provos/libevent-1.4.12-stable.tar.gz
    ~ sabhinav$ tar -xvzf libevent-1.4.12-stable.tar.gz
  5. Compile libevent-1.4.12-stable
    ~ sabhinav$ cd libevent-1.4.12-stable
    ~ sabhinav$ ./configure
    ~ sabhinav$ make
    ~ sabhinav$ sudo make install
  6. Assuming you have a successful installation, lets install PECL package libevent-0.0.2.
    ~ sabhinav$ pecl download libevent-0.0.2
    ~ sabhinav$ tar -xvzf libevent-0.0.2.tgz libevent-0.0.2
    ~ sabhinav$ cd libevent-0.0.2
    ~ sabhinav$ phpize
    ~ sabhinav$ ./configure
    ~ sabhinav$ make
    ~ sabhinav$ sudo make install
  7. Enable libevent extension in your php.ini
    extension=libevent.so
  8. Reload apache server
    ~ sabhinav$ sudo apachectl restart
  9. Confirm we have libevent extension enabled using phpinfo(); or
    ~ sabhinav$ php -i | grep libevent

Writing a custom unix style tail script in PHP (tail.php)
Below is a sample script which can be used as a base for writing custom unix style tail script. Comments in the code will help you understanding the flow of the code. Also do view official documentation for PHP Libevent extension usage.

<?php

	// callback function called whenever the registered event is triggered
	function eventFd($fd, $events, $arg) {
		echo fread($fd, 4096);
	}

	// create event base
	$base_fd = event_base_new();

	// create a new event
	$event_fd = event_new();

	// resource to be monitored
	$fd = fopen($argv[1], 'r');

	// set event on passed file name
	event_set($event_fd, $fd, EV_WRITE | EV_PERSIST, 'eventFd', array($event_fd, $base_fd));

	// associate base with this event
	event_base_set($event_fd, $base_fd);

	// register event
	event_add($event_fd);

	// start event loop
	event_base_loop($base_fd);

?>

Trying out tail.php
Save the above code file and issue the following on the terminal:

~ sabhinav$ php tail.php /var/log/apache2/access_log

Try accessing a page on your webserver and you should see the access log being tailed by the php script. 😀

Enjoy!