1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/sogou-workflow

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
tutorial-05-http_proxy.md 12 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.03.2025 10:20 552780a

Пример асинхронного сервера: http_proxy

Пример кода

tutorial-05-http_proxy.cc

Описание http_proxy

Это HTTP прокси-сервер, который можно использовать в браузере. Поддерживает все методы HTTP.
Однако принцип работы HTTPS-прокси отличается, поэтому этот пример не поддерживает HTTPS-прокси. Вы сможете просматривать только HTTP-сайты.
В реализации этого прокси требуется загрузить полную страницу HTTP перед её пересылкой, что может вызвать задержки при скачивании/загрузке больших файлов.

Изменение конфигурации сервера

В предыдущих примерах мы использовали стандартные параметры HTTP-сервера. В этом примере мы немного модифицируем конфигурацию, чтобы ограничить размер запроса и защититься от потенциальных атак.

int main(int argc, char *argv[])
{
    ...
    struct WFServerParams params = HTTP_SERVER_PARAMS_DEFAULT;
    params.request_size_limit = 8 * 1024 * 1024;

    WFHttpServer server(&params, process);
    if (server.start(port) == 0)
    {
        pause();
        server.stop();
    }
    else
    {
        perror("не удалось запустить сервер");
        exit(1);
    }

    return 0;   
}

В отличие от предыдущего примера, мы передаём дополнительный параметр при создании объекта сервера. Давайте рассмотрим доступные параметры HTTP-сервера.
В WFHttpServer.h есть следующие стандартные параметры HTTP-сервера:

static constexpr struct WFServerParams HTTP_SERVER_PARAMS_DEFAULT =
{
    .transport_type         =    TT_TCP,
    .max_connections        =    2000,
    .peer_response_timeout  =    10 * 1000,
    .receive_timeout        =    -1,
    .keep_alive_timeout     =    60 * 1000,
    .request_size_limit     =    (size_t)-1,
    .ssl_accept_timeout     =    10 * 1000,
};

transport_type: тип транспорта, по умолчанию TCP. Также допустимы значения TT_UDP и TT_SCTP для Linux.
max_connections: максимальное количество соединений — 2000. При достижении лимита старейшие keep-alive соединения будут закрываться.
peer_response_timeout: время ожидания ответа от удалённой стороны — 10 секунд.
receive_timeout: время ожидания получения полного запроса — бесконечность.
keep_alive_timeout: время жизни keep-alive соединения — 1 минута.
request_size_limit: максимальный размер запроса — неограничен.
ssl_accept_timeout: время ожидания завершения SSL-рукопожатия — 10 секунд.

Бизнес-логика прокси-сервера

Основная задача этого прокси-сервера — пересылка запросов пользователя на соответствующий веб-сервер и последующее пересылка ответа обратно пользователю. Запросы пользователя содержат схему и хост в URI, эти данные должны быть удалены при пересылке. Например, при обращении к http://www.sogou.com/, браузер отправляет следующую строку: GET http://www.sogou.com/ HTTP/1.1 Которая должна быть преобразована в: GET / HTTP/1.1

void process(WFHttpTask *proxy_task)
{
    auto *req = proxy_task->get_req();
    SeriesWork *series = series_of(proxy_task);
    WFHttpTask *http_task; /* для запроса к удалённому веб-серверу. */

    tutorial_series_context *context = new tutorial_series_context;
    context->url = req->get_request_uri();
    context->proxy_task = proxy_task;

    series->set_context(context);
    series->set_callback([](const SeriesWork *series) {
        delete (tutorial_series_context *)series->get_context();
    });

    http_task = WFTaskFactory::create_http_task(req->get_request_uri(), 0, 0,
                                                http_callback);

    const void *body;
    size_t len;

    /* Копируем запрос пользователя в новый запрос используя std::move(). */
    req->set_request_uri(http_task->get_req()->get_request_uri());
    req->get_parsed_body(&body, &len);
    req->append_output_body_nocopy(body, len);
    *http_task->get_req() = std::move(*req);

    /* Также ограничиваем размер ответа от удалённого веб-сервера. */
    http_task->get_resp()->set_size_limit(200 * 1024 * 1024);

    *series << http_task;
}

Это всё содержание функции process. Здесь создаётся запрос к удалённому веб-серверу. req->get_request_uri() используется для получения полного URL-адреса запроса пользователя, затем строится запрос к серверу. Этот запрос не будет повторяться или переадресовываться, так как это делает браузер при получении кода ошибки 302.

    req->set_request_uri(http_task->get_req()->get_request_uri());
    req->get_parsed_body(&body, &len);
    req->append_output_body_nocopy(body, len);
    *http_task->get_req() = std::move(*req);

Эти четыре строки фактически используются для создания запроса к веб-серверу. req представляет собой входящий запрос пользователя, который мы будем перемещать непосредственно в новый запрос. Первая строка просто удаляет часть http://host:port из request_uri, оставляя только путь. Вторая и третья строки указывают, что анализированный http-body является внешним выходным http-body. Это необходимо потому что в нашей реализации HttpMessage анализированный body и отправляемый body находятся в разных областях памяти. Четвёртая строка перемещает весь запрос в новый запрос к веб-серверу. После того, как запрос создан, он добавляется в текущий series, и функция process завершается.

Принцип работы асинхронного сервера

Функция process не является всей бизнес-логикой прокси. Мы также должны обрабатывать ответы от веб-сервера и заполнять ответы для браузера. В примере эхо-сервера нам не требовалось сетевое взаимодействие, достаточно было заполнить ответную страницу. Однако для прокси нам нужно ждать ответа от веб-сервера. Можно было бы занять поток процесса, ожидая ответ, но такой синхронный подход явно не подходит. Поэтому после получения асинхронного ответа мы должны вернуть ответ пользователю, не занимая никаких потоков во время ожидания. Поэтому в начале функции process мы устанавливаем контекст для текущего series.

struct tutorial_series_context
{
    std::string url;
    WFHttpTask *proxy_task;
    bool is_keep_alive;
};

void process(WFHttpTask *proxy_task)
{
    SeriesWork *series = series_of(proxy_task);
    ...
    tutorial_series_context *context = new tutorial_series_context;
    context->url = req->get_request_uri();
    context->proxy_task = proxy_task;

    series->set_context(context);
    series->set_callback([](const SeriesWork *series) {
        delete (tutorial_series_context *)series->get_context();
    });
    ...
}
```Как мы говорили ранее, любой активный запрос находится внутри серии. Серверные запросы тоже не являются исключением.
Поэтому мы можем получить текущую серию и установить контекст. URL主要用于日志记录,`proxy_task`包含我们将在响应中填充的主要信息。
接下来让我们看看如何处理来自Web服务器的响应。
```cpp
void http_callback(WFHttpTask *task)
{
    int state = task->get_state();
    auto *resp = task->get_resp();
    SeriesWork *series = series_of(task);
    tutorial_series_context *context =
        (tutorial_series_context *)series->get_context();
    auto *proxy_resp = context->proxy_task->get_resp();




    ...
    if (state == WFT_STATE_SUCCESS)
    {
        const void *body;
        size_t len;

        /* Установка обратного вызова для получения статуса ответа. */
        context->proxy_task->set_callback(reply_callback);

        /* Копируем ответ от удаленного Web-сервера в ответ прокси. */
        resp->get_parsed_body(&body, &len);
        resp->append_output_body_nocopy(body, len);
        *proxy_resp = std::move(*resp);
        ...
    }
    else
    {
        // Вернуть страницу "404 Not Found"
        ...
    }
}

我们只考虑成功的案例。任何从Web服务器成功获取完整的HTTP页面都被视为成功,无论响应代码是什么。所有其他情况都视为失败,并返回“404 Not Found”页面。 原始数据可能非常大,在我们的示例中设置了200MB的限制。因此我们需要检查响应的成功或失败。 服务器和客户端的任务具有相同的类型——WFHttpTask。唯一的区别在于,服务器任务由框架创建并开始时为空回调。 对于服务器和客户端任务的回调在完成HTTP交互后执行。因此,对于服务器任务,回调在收到响应后执行。 最后三行应该很熟悉,它们通过不复制的方式将Web服务器的响应传递给代理响应。 在调用http_callback函数之后,浏览器的响应会自动发送出去,整个过程都是异步进行的。 剩余的回调reply_callback()仅用于输出日志。在其执行完毕后,代理任务将被自动删除。 最后,序列的回调删除上下文。

时间响应

值得注意的是,服务器响应会在所有其他系列中的任务完成后自动发送。无需调用task->reply()方法。 如果调用了task->noreply()方法,则连接将在应答消息发送之前关闭(状态为NOREPLY)。 同样可以在服务器任务的回调中通过series_of()获取任务序列。即使在发送响应之后也可以继续向该序列添加新的任务。

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/sogou-workflow.git
git@api.gitlife.ru:oschina-mirror/sogou-workflow.git
oschina-mirror
sogou-workflow
sogou-workflow
master