Как обрабатывать заголовок Keep-Alive?

170
23 января 2022, 04:00

Пишу свой http-сервер, для обработки запросов использую классическую схемy: на каждый запрос устанавливается отдельное соединение в отдельном процессе, но в http 1.1 присутствует технология Keep-Alive. Каким способом можно обрабатывать такие запросы? Как принять следующий запрос, используя тот же самый клиентский соккет?

Я использую стандартный алгоритм обработки запроса :создаение клиентского сокета с помощью accept, чтение из него запроса с помощью recv, обработка этого запроса, отправка ответа с помощью send. Как шаг нужно добавить, что сокет можно было переиспользовать ?

   for(;;)
    {
      client *current = client::listen_port(cone.get_socket());//принимаем соединение
      pid_t child = fork();//создаем дочерний процесс
      if (child == 0)
      {
         httpHandler worker(current); // в этом конструкторе вызывается парсер, который вызывает recv
         worker.handle();// метод обработки запроса
         _exit(EXIT_SUCCESS);
      }
   }

Код парсера, где вызывается recv

httpParser::httpParser(int client_d)
{
    char buffer[req_buff_size];
    int bytes_recv = recv(client_d, buffer,req_buff_size - 1, 0); // получаем сообщение из сокета
    if (bytes_recv < 0)
    {
        send(client_d, heading::error404().c_str(), heading::error404().length(), 0);
    }
    buffer[bytes_recv] = '\0';
    reqest = buffer;
    std::cout << reqest <<std::endl; // для тестов
    if (bytes_recv < 0)
        throw std::invalid_argument( "error recv\n" );
    if (reqest.find("GET") == 0)
    {
        type = GET;
        GET_parse();
    }
    else if (reqest.find("POST") == 0)
    {
        type = POST;
        POST_Parse();
    }
    else if (reqest.find("HEAD") == 0)
        type = HEAD;
    else
         throw std::invalid_argument( "invalid REQ\n" );
    requestedFile();
}

вот код формирования ответорв:

void httpHandler::handle(httpParser *temp)
{
    parser = temp;
    if (parser->get_type() == HEAD)
    {
        heading head;
        send(newclient->get_client(), head.get_head().c_str(), head.get_head().length(), 0);
        return;
    }
    if (parser->get_type() == UNKNOWN)
        send(newclient->get_client(), heading::error404().c_str(), heading::error404().length(), 0);
    if (!parser->get_dynamic())
        static_handle();
    else
        dynamic_handle();
    parser = nullptr;
}

функция обработки статического контента:

void httpHandler::static_handle()
{
    std::string buffer; // создаем буфер в который будем формировать сообщение, отправляемое браузеру
    std::ifstream file(getenv("path") + parser->get_file(), std::ifstream::binary); // открываем читаемый файл c конца, скливая переменную окружения "путь" и имя файла
    if (file) // если файл открылся, продолжаем
     {
         auto const size{file.tellg()}; //узнаем позицию курсора
         file.seekg(0); //обнуляем позицию курсора
         char current;
         buffer.reserve(size);
         while(file.get(current)) //читаем файл
             buffer.push_back(current);
         heading head(buffer.length(), parser->get_file()); // этот класс формирует заголовок
         buffer = head.get_head() + buffer; // приклеиваем полученный заголовок к буферу
         send(newclient->get_client(), buffer.c_str(), buffer.length(), 0); // отправляем полученный буфер браузеру
      }
      else
         send(newclient->get_client(), heading::error404().c_str(), heading::error404().length(), 0);
     file.close();
}

в этом конструкторе создается заголовок ответа

heading::heading(const int content_size, const std::string &file)
{
    head = "HTTP/1.1 200 OK\r\n";
    std::string Content_Type, Content_Length;
    std::string extension = file.substr(file.find(".") + 1);
    if (extension == "png" || extension == "gif")
        Content_Type = "Content-Type: image/apng\r\n";
    else if(extension == "jpg")
        Content_Type = "Content-Type: image/jpeg\r\n";
    else
        Content_Type = "Content-Type: text/html\r\n";
    Content_Length = "Content-Lenght: " + std::to_string(content_size) + "\r\n";
    head = head + "Server: Cone \r\n" + Content_Type + Content_Length + "Connection: keep-alive\r\n\r\n";
}
Answer 1

Для сервера, который запускает отдельный процесс для каждого клиента будет выглядеть примерно так:

  pid_t child = fork();//создаём дочерний процесс
  if (child == 0) {
    httpParser worker(current);
    while (1) {
      ssize_t recv_sz = worker.nextMessage();
      if (recv_sz > 0) {
        worker.handle();// метод обработки запроса
      } else if (recv_sz == 0) {
        break;
      } else {
        fprintf(stderr, "Error: %s", strerror(errno));
        exit(EXIT_FAILURE);
      }
    }
    _exit(EXIT_SUCCESS);
  }

При этом httpParser::nextMessage() будет содержать практически всё что указано в конструкторе и возвращать значение recv().

О чём стоит ещё подумать/другие замечания:

  • В случае, если клиент не закроет соединение сразу, то сервер будет ждать неограниченно долго. Для большинства браузеров это нормальное поведение — по умолчанию они могут поддерживать активными сотни соединений и откладывают их закрытие до исчерпания этого пула. Также в случае, если клиента в результате внезапного разрыва соединения, не закрывает его корректно, оно будет существовать вечно. Дабы этого избежать, нужно ограничить выполнение recv() по времени. Это делается с помощью вызовов poll()/select().
  • Вообще говоря, recv() может вернуть только часть сообщения, а может сразу несколько (для HTTP/1.x в большинстве случаев не актуально). Это нужно учитывать.
  • Заголовок ответа обязан содержать корректный Content-length и желательно (для HTTP/1.0 — обязательно) Connection: keep-alive.
  • В случае, когда в заголовке обнаружен не поддерживаемый метод, сервер должен не бросать исключение (и падать судя по всему), а возвращать код 501 Not Implemented или 400 Bad Request (также на определённом оборудовании возможно возвращение кода 418)
  • А в случае ошибки чтения из сокета отвечать что-либо клиенту (в том числе и 404) бессмысленно.
  • Имена типов с маленькой буквы — моветон...
READ ALSO
Ошибка 0xc0000142 при подключении DLL

Ошибка 0xc0000142 при подключении DLL

У меня есть проект на C++ в Visual StudioВ

184
параметры функции main argc argv [дубликат]

параметры функции main argc argv [дубликат]

Для написания каких программ может понадобится параметры функции main argc argv ?

86
считывания чисел из файла с помощью QFile

считывания чисел из файла с помощью QFile

Есть файл вот такого типа

103
C++, static члены шаблонного класса

C++, static члены шаблонного класса

Рассмотрим следующий код:

74