Однофайловая кроссплатформенная библиотека HTTP/HTTPS C++11, предназначенная только для заголовков.
Это чрезвычайно легко настроить. Просто включите файл httplib.h в свой код!
Важный
Эта библиотека использует «блокирующий» сокет ввода-вывода. Если вы ищете библиотеку с «неблокирующим» сокетным вводом-выводом, это не та библиотека, которая вам нужна.
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// HTTPhttplib::Server svr;// HTTPShttplib::SSLServer svr; svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { res.set_content("Привет, мир!", "text/plain"); }); svr.listen("0.0.0.0", 8080);
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// HTTPhttplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");// HTTPShttplib::Client cli(" https://cpp-httplib-server.yhirose.repl.co");auto res = cli.Get("/hi"); рез-> статус; рез-> тело;
Поддержка SSL доступна с помощью CPPHTTPLIB_OPENSSL_SUPPORT
. libssl
и libcrypto
должны быть связаны.
Примечание
cpp-httplib в настоящее время поддерживает только версию 3.0 или новее. Пожалуйста, посетите эту страницу, чтобы получить дополнительную информацию.
Кончик
Для macOS: cpp-httplib теперь может использовать системные сертификаты с CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
. CoreFoundation
и Security
должны быть связаны с -framework
.
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// Serverhttplib::SSLServer svr("./cert.pem", "./key.pem");// Clienthttplib::Client cli("https: //локальный хост:1234"); // схема + хостhttplib::SSLClient cli("localhost:1234"); // хостhttplib::SSLClient cli("localhost", 1234); // хост, порт// Используйте свой ЦС Bundlecli.set_ca_cert_path("./ca-bundle.crt");// Отключите проверку сертификатаcli.enable_server_certificate_verification(false);// Отключите проверку хостаcli.enable_server_host_verification(false);// Отключите проверку хостаcli.enable_server_host_verification(false);
Примечание
При использовании SSL кажется невозможным во всех случаях избегать SIGPIPE, поскольку в некоторых операционных системах SIGPIPE можно подавлять только для каждого сообщения, но невозможно заставить библиотеку OpenSSL делать это для своих внутренних коммуникаций. Если вашей программе необходимо избежать завершения работы на SIGPIPE, единственным полностью универсальным способом может быть настройка обработчика сигнала для SIGPIPE, который будет обрабатывать или игнорировать его самостоятельно.
#include <httplib.h>int main(void) { используя пространство имен httplib; Сервер свр; svr.Get("/hi", [](const Request& req, Response& res) { res.set_content("Привет, мир!", "text/plain"); }); // Сопоставляем путь запроса с регулярным выражением // и извлекаем его записи svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { auto Numbers = req.matches[1]; res.set_content(числа, "текст/обычный"); }); // Захватываем второй сегмент пути запроса как параметр пути «id» svr.Get("/users/:id", [&](const Request& req, Response& res) { auto user_id = req.path_params.at("id"); res.set_content(user_id, «текст/обычный»); }); // Извлечение значений из заголовков HTTP и параметров запроса URL svr.Get("/body-header-param", [](const Request& req, Response& res) { if (req.has_header("Content-Length")) { auto val = req.get_header_value("Content-Length" ); } if (req.has_param("ключ")) { auto val = req.get_param_value("ключ"); } res.set_content(req.body, «текст/обычный»); }); svr.Get("/stop", [&](const Request& req, Response& res) { свр.стоп(); }); svr.listen("локальный хост", 1234); }
Также поддерживаются методы Post
, Put
, Delete
и Options
.
int port = svr.bind_to_any_port("0.0.0.0"); svr.listen_after_bind();
// Монтируем / в ./wwwdirectoryauto ret = svr.set_mount_point("/", "./www");if (!ret) { // Указанный базовый каталог не существует...}// Монтируем / public в ./wwwdirectoryret = svr.set_mount_point("/public", "./www");// Монтируем /public в ./www1 и ./www2 Directoriesret = svr.set_mount_point("/public", "./www1"); // 1-й заказ на поиск = svr.set_mount_point("/public", "./www2"); // 2-й порядок поиска // Удалить монтирование /ret = svr.remove_mount_point("/"); // Удалить монтирование /publicret = svr.remove_mount_point("/public");
// Определяемое пользователем расширение файла и сопоставление типов MIME ssvr.set_file_extension_and_mimetype_mapping("cc", "text/xc"); svr.set_file_extension_and_mimetype_mapping("cpp", "text/xc"); svr.set_file_extension_and_mimetype_mapping("чч", "текст/xh");
Ниже приведены встроенные сопоставления:
Расширение | MIME-тип | Расширение | MIME-тип |
---|---|---|---|
CSS | текст/css | миль на галлон | аудио/mpeg |
csv | текст/CSV | веба | аудио/вебм |
текст | текстовый/обычный | вав | аудио/волна |
втт | текст/ВТТ | отф | шрифт/otf |
хмл, хтм | текст/html | ТТФ | шрифт/ttf |
apng | изображение/apng | уфф | шрифт/woff |
живо | изображение/авиф | woff2 | шрифт/woff2 |
БМП | изображение/bmp | 7з | приложение/x-7z-сжатое |
гифка | изображение/гиф | атом | приложение/атом+xml |
png | изображение/png | приложение/pdf | |
SVG | изображение/svg+xml | мджс, js | приложение/Javascript |
веб-сайт | изображение/вебп | JSON | приложение/json |
ico | изображение/x-значок | RSS | приложение/rss+xml |
размолвка | изображение/TIFF | смола | приложение/x-tar |
размолвка | изображение/TIFF | хтмл, хт | приложение/xhtml+xml |
jpg, jpg | изображение/JPEG | xslt | приложение/xslt+xml |
mp4 | видео/mp4 | xml | приложение/xml |
MPEG | видео/mpeg | гз | приложение/gzip |
вебм | видео/вебм | молния | приложение/zip |
mp3 | аудио/mp3 | васм | приложение/вас |
Предупреждение
Эти методы статического файлового сервера не являются потокобезопасными.
// Обработчик вызывается непосредственно перед отправкой ответа клиентуvr.set_file_request_handler([](const Request &req, Response &res) { ... });
svr.set_logger([](const auto& req, const auto& res) { your_logger(req, res); });
svr.set_error_handler([](const auto& req, auto& res) { auto fmt = "<p>Состояние ошибки: <span style='color:red;'>%d</span></p>"; char buf [BUFSIZ]; snprintf(buf, sizeof(buf), fmt, res.status); res.set_content(buf, "текст/html"); });
Обработчик исключений вызывается, если обработчик маршрутизации пользователя выдает ошибку.
svr.set_Exception_handler([](const auto& req, auto& res, std::Exception_ptr ep) { auto fmt = "<h1>Ошибка 500</h1><p>%s</p>"; char buf[BUFSIZ] ; попробуйте { std::rethrow_Exception (ep); } catch (std::Exception &e) { snprintf(buf, sizeof(buf), fmt, e.what()); } catch (...) { // См. следующее ПРИМЕЧАНИЕ snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); } res.set_content(buf, "текст/html"); res.status = StatusCode::InternalServerError_500; });
Осторожность
если вы не предоставите блок catch (...)
для повторно создаваемого указателя исключения, неперехваченное исключение приведет к сбою сервера. Будь осторожен!
svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") { res.set_content("мир", "текст/html"); вернуть Сервер::HandlerResponse::Handled; } Возврат Сервер::HandlerResponse::Unhandled; });
svr.set_post_routing_handler([](const auto& req, auto& res) { res.set_header("ДОПОЛНИТЕЛЬНЫЙ_ЗАГОЛОВОК", "значение"); });
svr.Post("/multipart", [&](const auto& req, auto& res) { auto size = req.files.size(); auto ret = req.has_file("name1"); const auto& file = req. get_file_value("name1"); // file.filename; // file.content_type; // file.content;});
svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // ПРИМЕЧАНИЕ: `content_reader` блокируется до тех пор, пока не будет прочитано каждое поле данных формы файлы MultipartFormDataItems; content_reader( [&](const MultipartFormData &file) { files.push_back(файл); вернуть истину; }, [&](const char *data, size_t data_length) { files.back().content.append(данные, длина_данных); вернуть истину; }); } еще { std::string тело; content_reader([&](const char *data, size_t data_length) { body.append(данные, длина_данных); вернуть истину; }); } });
const size_t DATA_CHUNK_SIZE = 4; svr.Get("/stream", [&](const Request &req, Response &res) { auto data = new std::string("abcdefg"); res.set_content_provider( data->size(), // Длина контента "text/plain", // Тип контента [&, данные](смещение size_t, длина size_t, DataSink &sink) { const auto &d = *data; раковина.write(&d[смещение], std::min(длина, DATA_CHUNK_SIZE)); вернуть истину; // вернем «false», если вы хотите отменить процесс. }, [данные](bool успех) {удалить данные; }); });
Без длины контента:
svr.Get("/stream", [&](const Request &req, Response &res) { res.set_content_provider( "text/plain", // Тип контента [&](size_t offset, DataSink &sink) { if (/* данные еще есть */) { std::vector<char> данные; // подготавливаем данные... раковина.запись(data.data(), data.size()); } еще { раковина.готово(); // Больше нет данных } Вернуть истину; // вернем «false», если вы хотите отменить процесс. }); });
svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_chunked_content_provider("текст/обычный", [](смещение size_t, DataSink &sink) { раковина.write("123", 3); раковина.write("345", 3); раковина.write("789", 3); раковина.готово(); // Данных больше нет. Возвращаем true; // вернем «false», если вы хотите отменить процесс. } ); });
С трейлером:
svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_header("Трейлер", "Манешка1, Манекен2"); res.set_chunked_content_provider("текст/обычный", [](смещение size_t, DataSink &sink) { раковина.write("123", 3); раковина.write("345", 3); раковина.write("789", 3); раковина.done_with_trailer({ {"Dummy1", "DummyVal1"}, {"Dummy2", "DummyVal2"} }); вернуть истину; } ); });
svr.Get("/content", [&](const Request &req, Response &res) { res.set_file_content("./path/to/conent.html"); }); svr.Get("/content", [&](const Request &req, Response &res) { res.set_file_content("./путь/к/коненту", "текст/html"); });
По умолчанию сервер отправляет ответ 100 Continue
для заголовка Expect: 100-continue
.
// Отправляем ответ «417 Expectation Failed» });
// Отправляем окончательный статус, не читая сообщение body.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return res.status = StatusCode::Unauthorized_401; });
svr.set_keep_alive_max_count(2); // По умолчанию — 5svr.set_keep_alive_timeout(10); // По умолчанию 5
svr.set_read_timeout(5, 0); // 5 секунд svr.set_write_timeout(5, 0); // 5 секунд svr.set_idle_interval(0, 100000); // 100 миллисекунд
svr.set_payload_max_length(1024 * 1024 * 512); // 512 МБ
Примечание
Если тип содержимого тела запроса — «www-form-urlencoded», фактическая длина полезных данных не должна превышать CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
.
См. пример сервера и пример клиента.
ThreadPool
используется в качестве очереди задач по умолчанию , а количество потоков по умолчанию равно 8 или std::thread::hardware_concurrency()
. Вы можете изменить его с помощью CPPHTTPLIB_THREAD_POOL_COUNT
.
Если вы хотите установить количество потоков во время выполнения, нет удобного способа... Но вот как это сделать.
svr.new_task_queue = [] { return new ThreadPool(12); };
Вы также можете предоставить необязательный параметр, чтобы ограничить максимальное количество ожидающих запросов, то есть запросов accept()
обработанных прослушивателем, но все еще ожидающих обслуживания рабочими потоками.
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };
Ограничение по умолчанию — 0 (неограничено). Как только предел будет достигнут, прослушиватель отключит клиентское соединение.
Вы можете предоставить собственную реализацию пула потоков в соответствии с вашими потребностями.
класс YourThreadPoolTaskQueue: public TaskQueue {public: YourThreadPoolTaskQueue(size_t n) { пул_.start_with_thread_count(n); } virtual bool enqueue(std::function<void()> fn) override { /* Возвращает true, если задача действительно была поставлена в очередь, или false *, если вызывающая сторона должна разорвать соответствующее соединение. */ returnpool_.enqueue(fn); } Virtual void Shutdown() переопределить { пул_.shutdown_graceful(); }частный: ВашThreadPool пул_; }; svr.new_task_queue = [] { return new YourThreadPoolTaskQueue(12); };
#include <httplib.h>#include <iostream>int main(void) { httplib::Client cli("localhost", 1234); if (auto res = cli.Get("/hi")) { if (res->status == StatusCode::OK_200) { std::cout << res->body << std::endl; } } Еще {автоошибка = res.error(); std::cout << "Ошибка HTTP: " << httplib::to_string(err) << std::endl; } }
Кончик
Конструктор со строкой схема-хост-порт теперь поддерживается!
httplib::Client cli("localhost"); httplib::Client cli("localhost:8080"); httplib::Client cli("http://localhost"); httplib::Client cli("http://localhost:8080"); httplib::Client cli("https://localhost"); httplib::SSLClient cli("localhost");
Вот список ошибок из Result::error()
.
ошибка перечисления { Успех = 0, Неизвестный, Связь, ПривязатьIP-адрес, Читать, Писать, ПревышениеRedirectCount, Отменено, SSLСоединение, SSLLoadingCerts, SSLServerVerification, НеподдерживаемыеMultipartBoundaryChars, Сжатие, Таймаут соединения, };
httplib::Headers заголовки = { { "Accept-Encoding", "gzip, deflate" } };auto res = cli.Get("/hi", headers);
или
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
или
cli.set_default_headers({ { "Accept-Encoding", "gzip, deflate" } });auto res = cli.Get("/hi");
res = cli.Post("/post", "text", "text/plain"); res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded");
httplib::Params параметры; params.emplace("имя", "Джон"); params.emplace("note", "coder");auto res = cli.Post("/post", params);
или
httplib::Params параметры{ { "имя", "Джон" }, { "примечание", "кодер" } };auto res = cli.Post("/post", params);
httplib::MultipartFormDataItems items = { { "текст1", "текст по умолчанию", "", "" }, { "text2", "aωb", "", "" }, { "file1", "hnennlnlnon", "hello.txt", "text/plain" }, { "file2", "{n "world", truen}n", "world.json", "application/json" }, { "file3", "", "", "application/octet-stream" }, };auto res = cli.Post("/multipart", items);
res = cli.Put("/resource/foo", "text", "text/plain");
res = cli.Delete("/resource/foo");
res = cli.Options("*"); res = cli.Options("/resource/foo");
cli.set_connection_timeout(0, 300000); // 300 миллисекундscli.set_read_timeout(5, 0); // 5 секундcli.set_write_timeout(5, 0); // 5 секунд
std::string body;auto res = cli.Get("/large-data", [&](const char *data, size_t data_length) { body.append(данные, длина_данных); вернуть истину; });
std::string body;auto res = cli.Get( "/stream", Headers(), [&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); вернуть истину; // вернем «false», если вы хотите отменить запрос. }, [&](const char *data, size_t data_length) { body.append(данные, длина_данных); вернуть истину; // вернем «false», если вы хотите отменить запрос. });
std::string body = ...;auto res = cli.Post( "/stream", body.size(), [](смещение size_t, длина size_t, DataSink &sink) { мойка.write(body.data() + смещение, длина); вернуть истину; // вернем «false», если вы хотите отменить запрос. }, "текст/обычный");
auto res = cli.Post( "/stream", [](смещение size_t, DataSink &sink) { раковина.os << "кусковые данные 1"; раковина.os << "разбивка данных 2"; раковина.os << "разбивка данных 3"; раковина.готово(); вернуть истину; // вернем «false», если вы хотите отменить запрос. }, "текст/обычный");
httplib::Client cli(url, port);// печатает: 0/000 байт => 50% Completeauto res = cli.Get("/", [](uint64_t len, uint64_t total) { printf("%lld / %lld bytes => %d%% завершеноn", Лен, всего, (целое)(длин*100/всего)); вернуть истину; // вернем false, если вы хотите отменить запрос.} );
// Базовая аутентификацияcli.set_basic_auth("user", "pass");// Дайджест-аутентификацияcli.set_digest_auth("user", "pass");// Authentication токена носителяcli.set_bearer_token_auth("token");
Примечание
OpenSSL необходим для дайджест-аутентификации.
cli.set_proxy("host", port);// Базовая аутентификацияcli.set_proxy_basic_auth("user", "pass");// Дайджест-аутентификацияcli.set_proxy_digest_auth("user", "pass");// Аутентификация токена носителяcli.set_proxy_bearer_token_auth ("проходить");
Примечание
OpenSSL необходим для дайджест-аутентификации.
httplib::Client cli("httpbin.org");auto res = cli.Get("/range/32", { httplib::make_range_header({{1, 10}}) // 'Диапазон: байты=1- 10'});// res->status должно быть 206.// res->body должно быть "bcdefghijk".
httplib::make_range_header({{1, 10}, {20, -1}}) // 'Диапазон: bytes=1-10, 20-'httplib::make_range_header({{100, 199}, {500, 599) }}) // 'Диапазон: байты=100-199, 500-599'httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Диапазон: байты=0-0, -1'
httplib::Client cli("localhost", 1234); cli.Get("/привет"); // с «Соединением: закрыть»cli.set_keep_alive(true); cli.Get("/мир"); cli.set_keep_alive(ложь); cli.Get("/последний-запрос"); // с «Соединением: закрыть»
httplib::Client cli("yahoo.com");auto res = cli.Get("/"); рез-> статус; // 301cli.set_follow_location(true); res = cli.Get("/"); рез-> статус; // 200
Примечание
Эта функция пока недоступна в Windows.
cli.set_interface("eth0"); // Имя интерфейса, IP-адрес или имя хоста
Сервер может применять сжатие к следующему содержимому типа MIME:
все типы текста, кроме text/event-stream
изображение/svg+xml
приложение/Javascript
приложение/json
приложение/xml
приложение/xhtml+xml
Сжатие gzip доступно с помощью CPPHTTPLIB_ZLIB_SUPPORT
. libz
должен быть связан.
Сжатие Brotli доступно с помощью CPPHTTPLIB_BROTLI_SUPPORT
. Необходимые библиотеки должны быть связаны. Для получения более подробной информации см. https://github.com/google/brotli.
cli.set_compress(истина); res = cli.Post("/resource/foo", "...", "text/plain");
cli.set_decompress(ложь); res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); рез-> тело; // Сжатые данные
poll
вместо select
Системный вызов select
используется по умолчанию, поскольку он более широко поддерживается. Если вы хотите, чтобы cpp-httplib вместо этого использовал poll
, вы можете сделать это с помощью CPPHTTPLIB_USE_POLL
.
Поддержка Unix Domain Socket доступна в Linux и macOS.
// Serverhttplib::Server svr("./my-socket.sock"); svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);// Clienthttplib::Client cli("./my-socket.sock"); cli.set_address_family(AF_UNIX);
«my-socket.sock» может быть относительным или абсолютным путем. Ваше приложение должно иметь соответствующие разрешения для пути. Вы также можете использовать абстрактный адрес сокета в Linux. Чтобы использовать абстрактный адрес сокета, добавьте к пути нулевой байт ('x00').
$ ./split.py -husage: Split.py [-h] [-e РАСШИРЕНИЕ] [-o OUT]Этот скрипт разбивает httplib.h на части .h и .cc. Необязательные аргументы: -h, --help show это справочное сообщение и выход -e РАСШИРЕНИЕ, --extension РАСШИРЕНИЕ расширение файла реализации (по умолчанию: cc) -o OUT, --out OUT куда записывать файлы (по умолчанию: out)$ ./split.py Записал out/httplib.h и out/httplib.cc
Доступен Dockerfile для статического HTTP-сервера. Номер порта этого HTTP-сервера — 80, и он обслуживает статические файлы из каталога /html
в контейнере.
> docker build -t cpp-httplib-server ....> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Обслуживание HTTP на порту 0.0.0.0 80... 192.168.65.1 - - [31.08.2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31.08.2024] :21:34:26 +0000] "ПОЛУЧИ / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0..."
Из Docker Hub
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server ...> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Обслуживание HTTP на порту 0.0.0.0 80... 192.168.65.1 - - [31.08.2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31.08.2024] :21:34:26 +0000] "ПОЛУЧИ / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0..."
g++ 4.8 и ниже не могут собрать эту библиотеку, поскольку <regex>
в этих версиях не работает.
Включите httplib.h
перед Windows.h
или включите Windows.h
предварительно определив WIN32_LEAN_AND_MEAN
.
#include <httplib.h>#include <Windows.h>
#define WIN32_LEAN_AND_MEAN#include <Windows.h>#include <httplib.h>
Примечание
cpp-httplib официально поддерживает только последнюю версию Visual Studio. Возможно, это работает с предыдущими версиями Visual Studio, но я больше не могу это проверить. Запросы на включение всегда приветствуются для более старых версий Visual Studio, если они не нарушают соответствие C++11.
Примечание
Windows 8 или более ранняя версия, Visual Studio 2013 или более ранняя версия, а также Cygwin и MSYS2, включая MinGW, не поддерживаются и не тестируются.
Лицензия MIT (© 2024 Юджи Хиросе)
Эти люди внесли большой вклад в то, чтобы вывести эту библиотеку на совершенно другой уровень, чем простая игрушка!