Back to Home

Acquaintance with the libevent library as an example of creating the simplest Web-based image server

c · libevent · web · network programming · learning

Acquaintance with the libevent library as an example of creating the simplest Web-based image server

    In this article I will show how to use the libevent library to write a simple Web server that will produce jpeg image files at the request of clients.

    The libevent library provides programmers with access to a cross-platform asynchronous network API. Based on this library, you can create high-performance network applications. For example, libevent is used in such well-known applications as Memcached (distributed caching system) and TOR (distributed anonymous network).



    You can download the server code here .

    Let's start our code review with the main () function:

    75:int main (int argc, char *argv[])
    76:{
    77: struct event_base *ev_base;
    78: struct evhttp *ev_http;
    79:  
    80: if (argc != 3) {
    81:     printf ("Usage: %s host port\n", argv[0]);
    82:     exit (1);
    83: }
    84:
    85: ev_base = event_init ();
    86:
    87: ev_http = evhttp_new (ev_base);
    88: if (evhttp_bind_socket (ev_http, argv[1], (u_short)atoi (argv[2]))) {
    89:     printf ("Failed to bind %s:%s\n", argv[1], argv[2]);
    90:     exit (1);
    91: }
    92: evhttp_set_cb (ev_http, "/img", http_img_cb, NULL);
    93:
    94: event_base_dispatch (ev_base);
    95:
    96: return 0;
    97:}


    Our Web server will accept two parameters from the command line: the IP address and the port that the server will listen on. Lines 80-83 check the number of parameters passed from the command line. Do not forget that the first element of the argv [] array is always the name of the application itself.

    Line 85 initializes the libevent library , returns a pointer to the data structure “ struct event_base ”. In all further calls to the libevent library functions, this variable will be passed.

    From line 87 to line 92, an HTTP server is created, a function is called that initializes wiretapping at the address given on the command line. If suddenly this address is already being listened to by some other application or we don’t have rights to occupy this address (remember that up to 1024 ports can only be occupied by an application running as root), the program displays a message on the screen and terminates execution. Next, we set up the callback function for the “/ img” URI.

    The entire libevent library is built on the basis of the so-called “callback” functions (callback functions), they work as follows: let's say that when an event occurs (“event” - English “event”, hence the name of the library ) our function was called. To do this, we use the libevent API, register our function for a specific event (this may be a timer event, readiness of the socket to receive data, request URI, etc.). In the future, when this event occurs, our function is called, to which all the necessary parameters are transferred.

    Line 94 starts the event loop. In our example, the loop will be executed an infinite number of times, since we did not provide for the shutdown of the HTTP server. In order to exit this cycle and shut down the server, you will need to press CTRL + C in the console.

    Now consider the processing of the “/ img” HTTP request: Line 13 declares a “callback” to the http_img_cb () function . When a function is called, it will be passed, as arguments, a pointer to the structure " struct evhttp_request

    13:static void http_img_cb (struct evhttp_request *request, void *ctx)
    14:{
    15: struct evbuffer *evb;
    16: int fd;
    17: const char *fname;
    18: struct stat stbuf;
    19: int total_read_bytes, read_bytes;
    20: struct evkeyvalq uri_params;
    21:
    22: evb = evbuffer_new ();
    23:
    24: printf ("Request from: %s:%d URI: %s\n", request->remote_host, request->remote_port, request->uri);
    25:
    26: evhttp_parse_query (request->uri, &uri_params);
    27: fname = evhttp_find_header (&uri_params, "name");
    28:
    29: if (!fname) {
    30:     evbuffer_add_printf (evb, "Bad request");
    31:     evhttp_send_reply (request, HTTP_BADREQUEST, "Bad request", evb);
    32:     evhttp_clear_headers (&uri_params);
    33:     evbuffer_free (evb);
    34:     return;
    35: }


    ", Which contains all the necessary information about the HTTP request and a pointer to user data. In our example, the variable " ctx " is not used.
    Line 22 initializes a variable of type “ struct evbuffer *”.

    In libevent, the " struct evbuffer " structure is the main type for working with I / O data ("I / O"). The API for working with " struct evbuffer " allows you to efficiently read, write and search data.

    Line 26 calls the libevent function " evhttp_parse_query() ", Which takes a URI string and returns a list with the values" key "→" value "from the URI parameters. For example, if the request “http: // serverIP: port / img? Aa = bb & cc = dd” occurred and the function “ evhttp_parse_query (request-> uri, & uri_params) ” was called , then the pair “aa” → “will be contained in the uri_params” bb "," cc "→" dd ". The libevent API contains several functions for working with the “ struct evkeyvalq ” type.

    Line 27 calls one of these functions, which takes a pointer to a struct evkeyvalq structure and a line with a key. The function returns the value of the key or " NULL " if such a key is not found. In our case, we will accept the “name” key in the URI parameters, which will contain the name of the required file.

    Lines 29-35 verify that the "name" key is specified in the URI parameters. If it is not specified, we, using the function " evhttp_send_reply ()", send a response to the client with HTTP code 400 and a message for the user "Bad request". Next, we call the function " evhttp_clear_headers ()" to clear the list "key" → "value" and the function " evbuffer_free ()" to free the memory occupied by the structure " struct evbuffer " and exit the function.

    The function " evhttp_send_reply ()" is the main way to send HTTP messages to clients, in the parameters it takes a pointer to the structure " struct evhttp_request ", HTTP code (in libeventthere are several predefined constants with HTTP codes), a string with a short text message for the browser, and a pointer to the “ struct evbuffer ” structure - this data will be directly displayed to the client. Lines 37-51 open the file for reading. The file name is taken from the "name" parameter. Next, the fstat () function is called , which returns various system information about the file. If an error occurs in any of the functions, then we send a response to the client with HTTP code 404 and a message for the user “File not found”, free the memory and exit the function. Lines 53-65 read data from an open file into a struct evbuffer structure . The function " evbuffer_read ()" takes in the parameters a pointer to the structure "

    37: if ((fd = open (fname, O_RDONLY)) < 0) {
    38:     evbuffer_add_printf (evb, " File %s not found", fname);
    39:     evhttp_send_reply (request, HTTP_NOTFOUND, "File not found", evb);
    40:     evhttp_clear_headers (&uri_params);
    41:     evbuffer_free (evb);
    42:     return;
    43: }
    44: if (fstat (fd, &stbuf) < 0) {
    45:     evbuffer_add_printf (evb, "File %s not found", fname);
    46:     evhttp_send_reply (request, HTTP_NOTFOUND, "File not found", evb);
    47:     evhttp_clear_headers (&uri_params);
    48:     evbuffer_free (evb);
    49:     close (fd);
    50:     return;
    51: }




    53: total_read_bytes = 0;
    54: while (total_read_bytes < stbuf.st_size) {
    55:     read_bytes = evbuffer_read (evb, fd, stbuf.st_size);
    56:     if (read_bytes < 0) {
    57:         evbuffer_add_printf (evb, "Error reading file %s", fname);
    58:         evhttp_send_reply (request, HTTP_NOTFOUND, "File not found", evb);
    59:         evhttp_clear_headers (&uri_params);
    60:         evbuffer_free (evb);
    61:         close (fd);
    62:         return;
    63:     }
    64:     total_read_bytes += read_bytes;
    65: }


    struct evbuffer ", open file descriptor and how many bytes to read. The function returns the number of bytes read from the file. It is possible that the file is too large and cannot be considered as one call to “ evbuffer_read ()”, then in a cycle we check the number of bytes already read with the file size obtained from the call to “ fstat ()” and repeat the reading if necessary. Lines 66-72 send the contents of the file to the client with HTTP code 200. After adding the “Content-Type: image / jpeg” header to the HTTP response, this is necessary so that the client browser correctly displays the contents of the picture. After that we free the memory, close the file and exit the function. Assembly and testing.

    66: evhttp_add_header (request->output_headers, "Content-Type", "image/jpeg");
    67: evhttp_send_reply (request, HTTP_OK, "HTTP_OK", evb);
    68:
    69: evhttp_clear_headers (&uri_params);
    70: evbuffer_free (evb);
    71: close (fd);
    72:}






    Copy a couple of jpeg images into the current directory and remember the file names.

    To compile this code, you need to install the libevent version 1.4.XX library on your computer (replace XX with the latest available version). On some Linux distributions, it will be necessary to install the “ libevent-dev ” package Examples:
    for Gentoo: emerge libevent
    for Ubuntu: aptitude install libevent-dev

    After the library is installed, save the program code in the main.c file and you can try to compile:
    gcc main.c -o web_server -levent

    If there weren’t any errors, then you in the current directory appears the file "web_server". Run it with the parameters IP address and port, for example:
    ./web_server 127.0.0.1 8090

    Now on your machine, open any browser and enter the following URL: http://127.0.0.1:8090/img?name=имя_jpeg_картинки
    A picture should appear. Hooray, you just created a web server!

    A bit about security.
    The web server in this form is not suitable for use on the Internet, since there is no verification of which file is being accessed. Suppose you can send this request: " http://serverIP:port/img?name=/etc/passwd" and a file with a list of users of the system will be sent to the client. I deliberately omitted the consideration of security, since this is the topic of a whole separate article. I can only advise that it is safe to run such a Web server in a “ chroot ” environment, after compiling it with the “ -static ” flag .

    What's next.
    Perhaps I will soon write a sequel on the development of a simple Web server, where I will show how you can cache image files. Just recently I started writing an article where I want to tell how you can organize the interaction of the server and the Flash client using the binary protocol and the libevent API .
    There are many different interesting things that can be done using the libevent API . The main thing that distinguishes program development using libevent is its simplicity and, at the same time, the efficiency and speed of the resulting programs. Be sure to read the book Learning Libevent . Successful coding!

    PS Code highlighting for some reason is not displayed; /

    Read Next