×
Главная   »   Заметки   »   День 9. Протокол HTTP и сервер на С++
День 9. Протокол HTTP и сервер на С++
Метки:      ,   ,   

Сегодня немного разобрался с протоколом HTTP и написал простой сервер на C++.

Автор статьи никого не призывает к правонарушениям и отказывается нести ответственность за ваши действия. Вся информация предоставлена исключительно в ознакомительных целях. Все действия происходят на виртуальных машинах и внутри локальной сети автора. Спасибо!

Сегодня постараюсь написать простейший сервер на C++, чтобы лучше понять работу HTTP. Но, для начала необходимо разобраться с определенными теоретическими моментами.

Чтобы работать с сетью на низком уровне, следует использовать сокеты. По запросу «сокеты», кроме сокетов для микропроцессоров, популярен запрос «Сокеты Беркли». В 80-х годах университет в городе Беркли финансировался ARPA, чтобы они реализовали протокол TCP/IP для UNIX.

Изначально сокеты были встроены в ОС UNIX, однако Windows, так же реализовал интерфейс сокетов в виде библиотек.

Если кратко, сокеты выступают в виде абстракции, которая позволяет работать с сетевыми ресурсами, как с файлами. Очень подробно про сокеты Беркли написано здесь: https://gigabaza.ru/doc/138590.html

Разумеется, я не буду писать свое API, а буду использовать готовую библиотеку WinSock2.h для работы с сокетами и WS2tcpip.h для работы с IP-адресами. Так же на http://stackoverflow.com/a/20306451 рекомендуют определить константу _WIN32_WINNT со значением 0x501, чтобы избежать дальнейших проблем. Эту константу необходимо определить перед библиотекой WS2tcpip.h. Как я понял, если полистать эту библиотеку, этот параметр используется для выбора предпроцессором определенной ветки (строка 511, в моем случае). Как написать простой сервер расписано очень подробно здесь: https://code-live.ru/post/cpp-http-server-over-sockets/.

Сервер получился. Теперь нужно разбираться какие заголовки есть у HTTP и, что они обозначают. Перехожу в консоль разработчика (или как она называется в Я.Браузере), нажимаю сеть и выбираю нужный файл, который загружается с сервера (http://127.0.0.1:8000, т.е. главную страницу).

Теперь разбираемся с HTTP. Во-первых, один запрос – один ответ. Да, кстати, HTTP в web стеке находится между HTML, CSS, JavaScript и стеком TCP/IP.

Протокол HTTP используется не только для формирования запроса к серверу, чтобы что-то получить, но и для передачи на этот сервер данных из различных форм (GET, POST). HTTP может использоваться для загрузки части документа (AJAX).

Как правило, запрос отправляет браузер, но также отправлять данные может кто угодно, например, поисковой робот. Каждый запрос (request), отправляемый серверу, имеет ответ (response). Между запросами и ответами есть посредники (например, прокси). Прокси-сервер – он облегчает доступ к контенту в Интернете. Крому перехвата запросов и возвращения ответов, он может пересылать запросы или ответы, или нет. Запрашивая какой-либо ресурс, мы необязательно получаем ответ от него, возможно, будет получен ответ от прокси-сервера, который кешировал запрашиваемый ресурс (положительный момент: уменьшается нагрузка на сеть и, возможно, уменьшается время между запрос/ответом; отрицательный момент: между клиентом и сервером может находиться злоумышленник).

Участник обмена (user agent) – как правило, браузер пользователя. Чтобы отобразить страницу, браузер посылает запрос серверу. После чего браузер «сканирует» полученную HTML страницу и подгружает дополнительные файлы (например, CSS, JS, изображения и т.д.).

С другой стороны расположен сервер (serve). Адрес сервера находится в заголовке Host.

Следующий момент, HTTP не имеет состояний (поддерживать видимость сохранения сессии, например, при авторизации, помогают куки).

При помощи HTTP можно управлять кешированием и куками (заголовок cookie).

Когда клиент хочет взаимодействовать с сервером, он открывает TCP соединение, далее отправляет HTTP-сообщения. До версии HTTP/2 сообщения можно было прочитать. Начиная с версии HTTP/2, сообщения инкапсулируются. Далее клиент читает ответ от сервера и закрывает соединение.

Исходный код HTTP сервера на C++

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>

#define _WIN32_WINNT 0x501

#include <WinSock2.h>
#include <WS2tcpip.h>

#pragma comment(lib, "Ws2_32.lib")
using std::cerr;

int main() {
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (result != 0) {
        cerr << "WSAStartup failed: " << result << "\n";
        return result;
    }

    struct addrinfo* addr = NULL;

    struct addrinfo hints;
    ZeroMemory(&hints, sizeof(hints));

    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    result = getaddrinfo("127.0.0.1", "8000", &hints, &addr);

    if (result != 0) {
        cerr << "getaddrinfo failed: " << result << "\n";
        WSACleanup();
        return 1;
    }

    int listen_socket = socket(addr->ai_family, addr->ai_socktype,
        addr->ai_protocol);

    if (listen_socket == INVALID_SOCKET) {
        cerr << "Error at socket: " << WSAGetLastError() << "\n";
        freeaddrinfo(addr);
        WSACleanup();
        return 1;
    }

    result = bind(listen_socket, addr->ai_addr, (int)addr->ai_addrlen);

    if (result == SOCKET_ERROR) {
        cerr << "bind failed with error: " << WSAGetLastError() << "\n";
        freeaddrinfo(addr);
        closesocket(listen_socket);
        WSACleanup();
        return 1;
    }

    if (listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) {
        cerr << "listen failed with error: " << WSAGetLastError() << "\n";
        closesocket(listen_socket);
        WSACleanup();
        return 1;
    }

    const int max_client_buffer_size = 1024;
    char buf[max_client_buffer_size];
    int client_socket = INVALID_SOCKET;

    for (;;) {
        // Принимаем входящие соединения
        client_socket = accept(listen_socket, NULL, NULL);
        if (client_socket == INVALID_SOCKET) {
            cerr << "accept failed: " << WSAGetLastError() << "\n";
            closesocket(listen_socket);
            WSACleanup();
            return 1;
        }

        result = recv(client_socket, buf, max_client_buffer_size, 0);

        std::stringstream response;
        std::stringstream response_body;

        if (result == SOCKET_ERROR) {
            cerr << "recv failed: " << result << "\n";
            closesocket(client_socket);
        }
        else if (result == 0) {
            cerr << "connection closed...\n";
        }
        else if (result > 0) {
            buf[result] = '\0';

            std::string line;
            std::ifstream in("H:\\code\\C++ VS\\ServerHTTP\\x64\\Debug\\index.html");
            if (!in.is_open()) {
                cerr << "file index not found...\n";
            } else {
                while (getline(in, line)) {
                    response_body << line;
                }
            }
            in.close();

            response << "HTTP/1.1 200 OK\r\n"
                << "Version: HTTP/1.1\r\n"
                << "Content-Type: text/html; charset=utf-8\r\n"
                << "Content-Length: " << response_body.str().length()
                << "\r\n\r\n"
                << response_body.str();

            result = send(client_socket, response.str().c_str(),
                response.str().length(), 0);

            if (result == SOCKET_ERROR) {
                cerr << "send failed: " << WSAGetLastError() << "\n";
            }
            closesocket(client_socket);
        }
    }

    closesocket(listen_socket);
    freeaddrinfo(addr);
    WSACleanup();
    return 0;
}

Сегодня немного разобрался с протоколом HTTP. Завтра буду разбираться со стеком TCP/IP.

797 просмотров
30.03.2022
Автор