Как стать автором
Обновить

Знакомство с библиотекой libevent на примере создания простейшего Web-сервера картинок

Время на прочтение7 мин
Количество просмотров11K
В данной статье я покажу как используя библиотеку libevent, написать простейший Web-сервер, который будет по запросу клиентов выдавать файлы jpeg картинок.

Библиотека libevent предоставляет программистам доступ к кроссплатформенному асинхронному сетевому API. На основе данной библиотеки можно создавать высокопроизводительные сетевые приложения. Например, libevent используется в таких известных приложениях как Memcached (распределённая система кэширования) и TOR (распределённая анонимная сеть).



Скачать код сервера можно тут.

Начнем рассмотрение кода с функции main ():

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:}


Наш Web-сервер будет принимать два параметра из командной строки: IP адрес и порт, которые будет прослушивать сервер. В строках 80-83 идет проверка количества переданных параметров из командной строки. Не забывайте, что первым элементом массива argv[] всегда является имя самого приложения.

Строка 85 производит инициализацию библиотеки libevent, возвращает указатель на структуру данных «struct event_base». Во все дальнейшие вызовы функций библиотеки libevent должна будет передаваться эта переменная.

С 87 по 92 строку создается HTTP сервер, вызывается функция, которая инициализирует прослушку на переданном в командной строке адресе. Если вдруг данный адрес уже прослушивается каким-то другим приложением или у нас нет прав на занятие данного адреса (на забывайте, что порты до 1024 может занимать только приложение запущенное с правами пользователя root), то программа выводит сообщение на экран и завершает выполнение. Далее мы устанавливаем callback-функцию для «/img» URI.

Вся библиотека libevent построена на основе так называемых «callback»-функций (функций обратного вызова), они работают следующим образом: допустим мы хотим, чтобы при наступлении какого-то события («event» — англ. «событие», отсюда и название библиотеки) вызывалась наша функция. Для этого мы, использую API libevent, регистрируем нашу функцию для конкретного события (это может быть событие таймера, готовность сокета к приему данных, запрос URI и т.д). В дальнейшем, при наступлении этого события, происходит вызов нашей функции, которой передаются все необходимые параметры.

Строка 94 запускает цикл обработки событий. В нашем примере цикл будет выполнятся бесконечное количество раз, так как мы не предусмотрели завершение работы HTTP сервера. Для того чтобы выйти из этого цикла и завершить работу сервера, нужно будет в консоли нажать CTRL+C.

Теперь рассмотрим обработку «/img» HTTP запроса:

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: }


Сторка 13 объявляет «callback» функцию http_img_cb (). При вызове функции, ей, в качестве аргументов, передастся указатель на структуру «struct evhttp_request», в которой содержится вся необходимая информация о HTTP запросе и указатель на пользовательские данные. В нашем примере переменная «ctx» не используется.
Строка 22 инициализирует переменную с типом «struct evbuffer *».

В libevent структура «struct evbuffer» является основным типом для работы с данными Ввода/Вывода («I/O»). API для работы с «struct evbuffer» позволяет эффективно считывать, записывать и производить поиск данных.

Строка 26 вызывает libevent функцию «evhttp_parse_query ()», которая принимает строку URI и возвращает список со значениями «ключ» → «значение» из параметров URI. Например, если произошел запрос «http://serverIP:port/img?aa=bb&cc=dd» и вызвали функцию «evhttp_parse_query (request->uri, &uri_params)», то в «uri_params» будут содержаться пары «aa» → «bb», «cc» → «dd». API libevent содержит несколько функций для работы с типом «struct evkeyvalq».

Строка 27 вызывает одну из таких функций, которая принимает указатель на структуру «struct evkeyvalq» и строку с ключом. Функция возвращает значение ключа или «NULL», если такой ключ не найден. В нашем случае мы будем принимать в URI параметрах ключ «name», в котором будет содержаться имя требуемого файла.

Строки 29-35 проверяют, чтобы ключ «name» был указан в URI параметрах. Если он не указан, мы, используя функцию «evhttp_send_reply ()», отсылаем ответ клиенту с HTTP кодом 400 и сообщением для пользователя «Bad request». Далее вызываем функцию «evhttp_clear_headers ()» для очистки списка «ключ» → «значение» и функцию «evbuffer_free ()» для освобождения памяти, занимаемой структурой «struct evbuffer» и выходим из функции.

Функция «evhttp_send_reply ()» является основным способом отсылки HTTP сообщений клиентам, в параметрах она принимает указатель на структуру «struct evhttp_request», HTTP код (в libevent есть несколько предопределённых констант с HTTP кодами), строку с коротким текстовым сообщением для броузера и указатель на структуру «struct evbuffer» — эти данные будут непосредственно отображены клиенту.

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: }


Строки 37-51 открывают файл для чтения. Имя файла берется из параметра «name». Далее вызывается функция «fstat ()», которая возвращает различную системную информацию о файле. Если в любой из функций произошла ошибка, то мы отсылаем ответ клиенту с HTTP кодом 404 и сообщением для пользователя «File not found», освобождаем память и выходим из функции.

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: }


Строки 53-65 считывают данные из открытого файла в структуру «struct evbuffer». Функция «evbuffer_read ()» принимает в параметрах указатель на структуру «struct evbuffer», десктриптор открытого файла и сколько байтов нужно считать. Функция возвращает количество считанных байтов из файла. Возможна такая ситуация, что файл слишком большой, и его нельзя считать одним вызовом «evbuffer_read ()», тогда мы в цикле сверяем количество уже считанных байтов с размером файла, полученного из вызова «fstat ()» и при необходимости повторяем считывание.

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:}


Строки 66-72 отсылают содержание файла клиенту с HTTP кодом 200. Предварительно добавив к ответу HTTP заголовок «Content-Type: image/jpeg», это необходимо для того, чтобы броузер клиента правильно отобразил содержание картинки. После этого освобождаем память, закрываем файл и выходим из функции.

Сборка и тестирование.

Скопируйте в текущую директорию пару jpeg картинок и запомните имена файлов.

Чтобы скомпилить данный код, вам нужно на своем компьютере установить библиотеку libevent версии 1.4.XX (замените XX на последнюю доступную версию). На некоторых Linux дистрибутивах надо будет установить пакет «libevent-dev» Примеры:
для Gentoo: emerge libevent
для Ubuntu: aptitude install libevent-dev

После того, как библиотека установлена, сохраните код программы в файл main.c и можно попробовать скомпилить:
gcc main.c -o web_server -levent

Если ни каких ошибок не было, то у вас в текущей директории появится файл «web_server». Запускаем его с параметрами IP адрес и порт, например:
./web_server 127.0.0.1 8090

Теперь на своей машине откройте любой броузер и введите такой URL: http://127.0.0.1:8090/img?name=имя_jpeg_картинки
Должна показаться картинка. Ура, вы только что создали web сервер!

Немного о безопасности.
Web-сервер в данном виде не пригоден для использования в Интернете, так как нет проверки к какому файлу идет обращение. Допустим, можно послать такой запрос: «http://serverIP:port/img?name=/etc/passwd» и клиенту отошлется файл со списком пользователей системы. Я специально опустил рассмотрение безопасности, так как это тема целой отдельной статьи. Могу лишь посоветовать, что вполне безопасно запускать такой Web-сервер в «chroot» среде, предварительно скомпилив его с флагом «-static».

Что дальше.
Возможно, я в скором времени напишу продолжение о разработке простейшего Web-сервера, где покажу как можно кэшировать файлы картинок. Так же недавно начал писать статейку, где хочу рассказать, как можно организовать взаимодействие сервера и Flash клиента посредством двоичного протокола и API libevent.
Есть много разных интересный вещей, что можно сделать используя API libevent. Главное что отличает разработку программ с используя libevent — это простота и в то же время эффективность и скорость выполнения полученных программ. Обязательно почитайте книгу Learning Libevent. Успешного кодинга!

P.S. Подсветка кода почему-то не отображается ;/
Теги:
Хабы:
+6
Комментарии10

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн