Модераторы: feodorv
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> libevent + http server + неблокирующие сокеты 
:(
    Опции темы
neosapient
Дата 26.11.2013, 13:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 672
Регистрация: 16.8.2006

Репутация: нет
Всего: 4



Здравствуйте.

Вопрос к специалистам по libevent: как запустить html сервер, на основе неблокирующих сокетов? 

Взял пример "libevent-2.0.21\sample\http-server.c". В примере демонстрируется работа с http-сервером на основе асинхронного ввода/вывода. В теле main() идет настройка сервера, в частности, связываем путь "/dump" с обработчиком dump_request_cb(). То есть, когда пользователь зайдет на "127.0.0.1:port/dump", движок вызовет  обработчик dump_request_cb(), который сформирует ответ в виде html страницы и вернет ответ, вызвав evhttp_send_reply("ответ", 200, "ok").

Захотел протестировать сложную ситуацию: обработка запроса затягивает на пару минут - как следствие, evhttp_send_reply() не может быть вызван сразу же.
Реализовал следующий пример, пусть первые два запроса вообще не обрабатываются вызовом evhttp_send_reply(), добавил в dump_request_cb() несколько строк:
Код

    static int count =0;  //  
    count ++;             // 
    if(count<3)           //   <-- имитирую зависание (долгую работу html-сервера)
        return;           // 


Оказалось, что без вызова evhttp_send_reply() сервер просто перестал принимать последующие запросы. Т.е. запустил сервер, поставил точку остановки в на мой код. Запускаю 5 страниц браузера к "127.0.0.1:port/dump", но обработчик libevent вызывает evhttp_send_reply() только 1 раз (соответственно, обработчик завершается без вызова evhttp_send_reply()), а остальные страницы браузера зависают в ожидании подключения.

Где я ошибся? И как исправить ситуацию?
- Возможно, следует вызывать некую функцию, которая указала бы http-серверу  "ждать формирования ответа, а пока перейти к обработке другого входящего соединения". Как это сделать?
- Возможно, требуется где то указать, что я хочу пользоваться неблокирующими сокетами. Где и как выставить этот флаг?
- Возможно, у меня где то в голове фундаментальная ошибка понимания работы библиотеки libevent или http-сервера. Надо исправлять )

P.S.
Запрещено решать данную задачу через многопоточность. Требуется решить задачу через неблокирующие сокеты и асинхронный ввод/вывод.

Полный код примера
Код

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#else
#include <sys/stat.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#endif

#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>

#ifdef _EVENT_HAVE_NETINET_IN_H
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#endif

/* Compatibility for possible missing IPv6 declarations */
#include "../util-internal.h"

#ifdef WIN32
#define stat _stat
#define fstat _fstat
#define open _open
#define close _close
#define O_RDONLY _O_RDONLY
#endif

char uri_root[512];

static const struct table_entry {
    const char *extension;
    const char *content_type;
} content_type_table[] = {
    { "txt", "text/plain" },
    { "c", "text/plain" },
    { "h", "text/plain" },
    { "html", "text/html" },
    { "htm", "text/htm" },
    { "css", "text/css" },
    { "gif", "image/gif" },
    { "jpg", "image/jpeg" },
    { "jpeg", "image/jpeg" },
    { "png", "image/png" },
    { "pdf", "application/pdf" },
    { "ps", "application/postsript" },
    { NULL, NULL },
};

/* Try to guess a good content-type for 'path' */
static const char *
guess_content_type(const char *path)
{
    const char *last_period, *extension;
    const struct table_entry *ent;
    last_period = strrchr(path, '.');
    if (!last_period || strchr(last_period, '/'))
        goto not_found; /* no exension */
    extension = last_period + 1;
    for (ent = &content_type_table[0]; ent->extension; ++ent) {
        if (!evutil_ascii_strcasecmp(ent->extension, extension))
            return ent->content_type;
    }

not_found:
    return "application/misc";
}

/* Callback used for the /dump URI, and for every non-GET request:
 * dumps all information to stdout and gives back a trivial 200 ok */
static void
dump_request_cb(struct evhttp_request *req, void *arg)
{
    static int count =0;  //  
    count ++;             // 
    if(count<3)           //   <-- имитирую зависание (долгую работу html-сервера)
        return;           // 

    const char *cmdtype;
    struct evkeyvalq *headers;
    struct evkeyval *header;
    struct evbuffer *buf;

    switch (evhttp_request_get_command(req)) {
    case EVHTTP_REQ_GET: cmdtype = "GET"; break;
    case EVHTTP_REQ_POST: cmdtype = "POST"; break;
    case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
    case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
    case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
    case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
    case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
    case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
    case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
    default: cmdtype = "unknown"; break;
    }

    printf("Received a %s request for %s\nHeaders:\n",
        cmdtype, evhttp_request_get_uri(req));

    headers = evhttp_request_get_input_headers(req);
    for (header = headers->tqh_first; header;
        header = header->next.tqe_next) {
        printf("  %s: %s\n", header->key, header->value);
    }

    buf = evhttp_request_get_input_buffer(req);
    puts("Input data: <<<");
    while (evbuffer_get_length(buf)) {
        int n;
        char cbuf[128];
        n = evbuffer_remove(buf, cbuf, sizeof(buf)-1);
        if (n > 0)
            (void) fwrite(cbuf, 1, n, stdout);
    }
    puts(">>>");

    evhttp_send_reply(req, 200, "OK", NULL);
}

/* This callback gets invoked when we get any http request that doesn't match
 * any other callback.  Like any evhttp server callback, it has a simple job:
 * it must eventually call evhttp_send_error() or evhttp_send_reply().
 */
static void
send_document_cb(struct evhttp_request *req, void *arg)
{

    struct evbuffer *evb = NULL;
    const char *docroot = (const char *)arg;
    const char *uri = evhttp_request_get_uri(req);
    struct evhttp_uri *decoded = NULL;
    const char *path;
    char *decoded_path;
    char *whole_path = NULL;
    size_t len;
    int fd = -1;
    struct stat st;

    if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
        dump_request_cb(req, arg);
        return;
    }

    printf("Got a GET request for <%s>\n",  uri);

    /* Decode the URI */
    decoded = evhttp_uri_parse(uri);
    if (!decoded) {
        printf("It's not a good URI. Sending BADREQUEST\n");
        evhttp_send_error(req, HTTP_BADREQUEST, 0);
        return;
    }

    /* Let's see what path the user asked for. */
    path = evhttp_uri_get_path(decoded);
    if (!path) path = "/";

    /* We need to decode it, to see what path the user really wanted. */
    decoded_path = evhttp_uridecode(path, 0, NULL);
    if (decoded_path == NULL)
        goto err;
    /* Don't allow any ".."s in the path, to avoid exposing stuff outside
     * of the docroot.  This test is both overzealous and underzealous:
     * it forbids aceptable paths like "/this/one..here", but it doesn't
     * do anything to prevent symlink following." */
    if (strstr(decoded_path, ".."))
        goto err;

    len = strlen(decoded_path)+strlen(docroot)+2;
    if (!(whole_path = (char *)malloc(len))) {
        perror("malloc");
        goto err;
    }
    evutil_snprintf(whole_path, len, "%s/%s", docroot, decoded_path);

    if (stat(whole_path, &st)<0) {
        goto err;
    }

    /* This holds the content we're sending. */
    evb = evbuffer_new();

    if (S_ISDIR(st.st_mode)) {
        /* If it's a directory, read the comments and make a little
         * index page */
#ifdef WIN32
        HANDLE d;
        WIN32_FIND_DATAA ent;
        char *pattern;
        size_t dirlen;
#else
        DIR *d;
        struct dirent *ent;
#endif
        const char *trailing_slash = "";

        if (!strlen(path) || path[strlen(path)-1] != '/')
            trailing_slash = "/";

#ifdef WIN32
        dirlen = strlen(whole_path);
        pattern = (char *)malloc(dirlen+3);
        memcpy(pattern, whole_path, dirlen);
        pattern[dirlen] = '\\';
        pattern[dirlen+1] = '*';
        pattern[dirlen+2] = '\0';
        d = FindFirstFileA(pattern, &ent);
        free(pattern);
        if (d == INVALID_HANDLE_VALUE)
            goto err;
#else
        if (!(d = opendir(whole_path)))
            goto err;
#endif

        evbuffer_add_printf(evb, "<html>\n <head>\n"
            "  <title>%s</title>\n"
            "  <base href='%s%s%s'>\n"
            " </head>\n"
            " <body>\n"
            "  <h1>%s</h1>\n"
            "  <ul>\n",
            decoded_path, /* XXX html-escape this. */
            uri_root, path, /* XXX html-escape this? */
            trailing_slash,
            decoded_path /* XXX html-escape this */);
#ifdef WIN32
        do {
            const char *name = ent.cFileName;
#else
        while ((ent = readdir(d))) {
            const char *name = ent->d_name;
#endif
            evbuffer_add_printf(evb,
                "    <li><a href=\"%s\">%s</a>\n",
                name, name);/* XXX escape this */
#ifdef WIN32
        } while (FindNextFileA(d, &ent));
#else
        }
#endif
        evbuffer_add_printf(evb, "</ul></body></html>\n");
#ifdef WIN32
        CloseHandle(d);
#else
        closedir(d);
#endif
        evhttp_add_header(evhttp_request_get_output_headers(req),
            "Content-Type", "text/html");
    } else {
        /* Otherwise it's a file; add it to the buffer to get
         * sent via sendfile */
        const char *type = guess_content_type(decoded_path);
        if ((fd = open(whole_path, O_RDONLY)) < 0) {
            perror("open");
            goto err;
        }

        if (fstat(fd, &st)<0) {
            /* Make sure the length still matches, now that we
             * opened the file :/ */
            perror("fstat");
            goto err;
        }
        evhttp_add_header(evhttp_request_get_output_headers(req),
            "Content-Type", type);
        evbuffer_add_file(evb, fd, 0, st.st_size);
    }

    evhttp_send_reply(req, 200, "OK", evb);
    goto done;
err:
    evhttp_send_error(req, 404, "Document was not found");
    if (fd>=0)
        close(fd);
done:
    if (decoded)
        evhttp_uri_free(decoded);
    if (decoded_path)
        free(decoded_path);
    if (whole_path)
        free(whole_path);
    if (evb)
        evbuffer_free(evb);
}

static void
syntax(void)
{
    fprintf(stdout, "Syntax: http-server <docroot>\n");
}

int
main(int argc, char **argv)
{
    struct event_base *base;
    struct evhttp *http;
    struct evhttp_bound_socket *handle;

    unsigned short port = 0;
#ifdef WIN32
    WSADATA WSAData;
    WSAStartup(0x101, &WSAData);
#else
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        return (1);
#endif
    if (argc < 2) {
        syntax();
        return 1;
    }

    base = event_base_new();
    if (!base) {
        fprintf(stderr, "Couldn't create an event_base: exiting\n");
        return 1;
    }

    /* Create a new evhttp object to handle requests. */
    http = evhttp_new(base);
    if (!http) {
        fprintf(stderr, "couldn't create evhttp. Exiting.\n");
        return 1;
    }

    /* The /dump URI will dump all requests to stdout and say 200 ok. */
    evhttp_set_cb(http, "/dump", dump_request_cb, NULL);

    /* We want to accept arbitrary requests, so we need to set a "generic"
     * cb.  We can also add callbacks for specific paths. */
    evhttp_set_gencb(http, send_document_cb, argv[1]);

    /* Now we tell the evhttp what port to listen on */
    handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", port);
    if (!handle) {
        fprintf(stderr, "couldn't bind to port %d. Exiting.\n",
            (int)port);
        return 1;
    }

    {
        /* Extract and display the address we're listening on. */
        struct sockaddr_storage ss;
        evutil_socket_t fd;
        ev_socklen_t socklen = sizeof(ss);
        char addrbuf[128];
        void *inaddr;
        const char *addr;
        int got_port = -1;
        fd = evhttp_bound_socket_get_fd(handle);
        memset(&ss, 0, sizeof(ss));
        if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
            perror("getsockname() failed");
            return 1;
        }
        if (ss.ss_family == AF_INET) {
            got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
            inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
        } else if (ss.ss_family == AF_INET6) {
            got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
            inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
        } else {
            fprintf(stderr, "Weird address family %d\n",
                ss.ss_family);
            return 1;
        }
        addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
            sizeof(addrbuf));
        if (addr) {
            printf("Listening on %s:%d\n", addr, got_port);
            evutil_snprintf(uri_root, sizeof(uri_root),
                "http://%s:%d",addr,got_port);
        } else {
            fprintf(stderr, "evutil_inet_ntop failed\n");
            return 1;
        }
    }

    event_base_dispatch(base);

    return 0;
}


PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Сети | Следующая тема »


 




[ Время генерации скрипта: 0.0481 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.