Пишу свой 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";
}
Для сервера, который запускает отдельный процесс для каждого клиента будет выглядеть примерно так:
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
) бессмысленно.Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Для написания каких программ может понадобится параметры функции main argc argv ?