Une bibliothèque HTTP/HTTPS multiplateforme C++11 à en-tête de fichier unique uniquement.
C'est extrêmement simple à configurer. Incluez simplement le fichier httplib.h dans votre code !
Important
Cette bibliothèque utilise des E/S de socket « bloquantes ». Si vous recherchez une bibliothèque avec des E/S de socket « non bloquantes », ce n'est pas celle que vous souhaitez.
#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("Bonjour tout le monde !", "text/plain"); }); svr.écouter("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"); res->statut ; res->corps;
La prise en charge SSL est disponible avec CPPHTTPLIB_OPENSSL_SUPPORT
. libssl
et libcrypto
doivent être liés.
Note
cpp-httplib ne prend actuellement en charge que la version 3.0 ou ultérieure. Veuillez consulter cette page pour obtenir plus d'informations.
Conseil
Pour macOS : cpp-httplib peut désormais utiliser les certificats système avec CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
. CoreFoundation
et Security
doivent être liés à -framework
.
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// Serverhttplib::SSLServer svr("./cert.pem", "./key.pem");// Clienthttplib::Client cli("https: //localhost:1234"); // schéma + hôtehttplib::SSLClient cli("localhost:1234"); // hosthttplib::SSLClient cli("localhost", 1234); // hôte, port// Utilisez votre autorité de certification bundlecli.set_ca_cert_path("./ca-bundle.crt");// Désactivez la vérification du certificatcli.enable_server_certificate_verification(false);// Désactivez la vérification de l'hôtecli.enable_server_host_verification(false);
Note
Lors de l'utilisation de SSL, il semble impossible d'éviter SIGPIPE dans tous les cas, puisque sur certains systèmes d'exploitation, SIGPIPE ne peut être supprimé que message par message, mais il n'existe aucun moyen de forcer la bibliothèque OpenSSL à le faire pour ses communications internes. Si votre programme doit éviter de se terminer sur SIGPIPE, le seul moyen tout à fait général pourrait être de configurer un gestionnaire de signal pour que SIGPIPE le gère ou l'ignore vous-même.
#include <httplib.h>int main(void) { en utilisant l'espace de noms httplib ; SVR du serveur ; svr.Get("/hi", [](const Request& req, Response& res) { res.set_content("Bonjour tout le monde !", "text/plain"); }); // Faites correspondre le chemin de la requête avec une expression régulière // et extrayez ses captures svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { numéros automatiques = req.matches[1]; res.set_content(nombres, "texte/plain"); }); // Capturez le deuxième segment du chemin de la requête en tant que paramètre de chemin "id" svr.Get("/users/:id", [&](const Request& req, Response& res) { auto user_id = req.path_params.at("id"); res.set_content(user_id, "text/plain"); }); // Extraire les valeurs des en-têtes HTTP et des paramètres de requête 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("key")) { auto val = req.get_param_value("key"); } res.set_content(req.body, "text/plain"); }); svr.Get("/stop", [&](const Request& req, Response& res) { svr.stop(); }); svr.listen("localhost", 1234); }
Les méthodes Post
, Put
, Delete
et Options
sont également prises en charge.
int port = svr.bind_to_any_port("0.0.0.0"); svr.listen_after_bind();
// Monter / vers ./www directoryauto ret = svr.set_mount_point("/", "./www");if (!ret) { // Le répertoire de base spécifié n'existe pas...}// Mount / public vers ./www directoryret = svr.set_mount_point("/public", "./www");// Monter /public sur ./www1 et ./www2 directoryret = svr.set_mount_point("/public", "./www1"); // 1er ordre vers searchret = svr.set_mount_point("/public", "./www2"); // 2ème ordre de recherche// Supprimer le montage /ret = svr.remove_mount_point("/");// Supprimer le montage /publicret = svr.remove_mount_point("/public");
// Extension de fichier définie par l'utilisateur et mappages de type MIMEsvr.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("hh", "text/xh");
Les éléments suivants sont des mappages intégrés :
Extension | Type MIME | Extension | Type MIME |
---|---|---|---|
css | texte/css | mpga | audio/mpeg |
CSV | texte/csv | Weba | audio/Web |
SMS | texte/plain | wav | audio/onde |
VTT | texte/vtt | otf | police/otf |
html, htm | texte/html | tf | police/ttf |
apng | image/apng | wow | police/woff |
avif | image/avif | woff2 | police/woff2 |
bmp | image/bmp | 7z | application/x-7z-compressé |
gif | image/gif | atome | application/atome+xml |
png | image/png | candidature/pdf | |
SVG | image/svg+xml | mjs, js | application/javascript |
webp | image/site Web | json | application/json |
ico | image/icône x | rss | application/rss+xml |
tif | image/tiff | goudron | application/x-tar |
tiff | image/tiff | xhtml, xht | application/xhtml+xml |
jpeg, jpg | image/jpeg | xslt | application/xslt+xml |
mp4 | vidéo/mp4 | XML | application/xml |
MPEG | vidéo/mpeg | gz | application/gzip |
webm | vidéo/webm | fermeture éclair | application/zip |
mp3 | audio/mp3 | était | application/wasm |
Avertissement
Ces méthodes de serveur de fichiers statiques ne sont pas thread-safe.
// Le gestionnaire est appelé juste avant que la réponse ne soit envoyée à un clientvr.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>Statut d'erreur : <span style='color:red;'>%d</span></p>"; char buf [BUFSIZ]; snprintf(buf, sizeof(buf), fmt, res.status); res.set_content(buf, "text/html"); });
Le gestionnaire d'exceptions est appelé si un gestionnaire de routage utilisateur génère une erreur.
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { auto fmt = "<h1>Erreur 500</h1><p>%s</p>"; char buf[BUFSIZ] ; essayez { std::rethrow_exception(ep); } catch (std::exception &e) { snprintf(buf, sizeof(buf), fmt, e.what()); } catch (...) { // Voir la REMARQUE suivante snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); } res.set_content(buf, "text/html"); res.status = StatusCode :: InternalServerError_500 ; });
Prudence
si vous ne fournissez pas le bloc catch (...)
pour un pointeur d'exception renvoyé, une exception non interceptée finira par provoquer le crash du serveur. Sois prudent!
svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") { res.set_content("monde", "text/html"); return Server::HandlerResponse::Handled; } return Server::HandlerResponse::Unhandled; });
svr.set_post_routing_handler([](const auto& req, auto& res) { res.set_header("ADDITIONAL_HEADER", "valeur"); });
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"); // fichier.filename; // fichier.content_type; // fichier.content;});
svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // REMARQUE : `content_reader` bloque jusqu'à ce que chaque champ de données de formulaire soit lu Fichiers MultipartFormDataItems ; content_reader( [&](const MultipartFormData &fichier) { fichiers.push_back(fichier); renvoie vrai ; }, [&](const char *données, taille_t longueur_données) { files.back().content.append(data, data_length); renvoie vrai ; }); } autre { std :: corps de chaîne ; content_reader([&](const char *data, size_t data_length) { body.append (données, longueur_données); renvoie vrai ; }); } });
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(), // Longueur du contenu "text/plain", // Type de contenu [&, data](size_t offset, size_t length, DataSink &sink) { const auto &d = *data; puits.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler le processus. }, [data](bool success) { supprimer les données ; }); });
Sans longueur de contenu :
svr.Get("/stream", [&](const Demande &req, Réponse &res) { res.set_content_provider( "text/plain", // Type de contenu [&](size_t offset, DataSink &sink) { if (/* il y a encore des données */) { données std::vector<char> ; // prépare les données... puits.write(data.data(), data.size()); } autre { puits.done(); // Plus de données } renvoie vrai ; // renvoie 'false' si vous souhaitez annuler le processus. }); });
svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_chunked_content_provider( "texte/plaine", [](size_t offset, DataSink &sink) { puits.write("123", 3); puits.write("345", 3); puits.write("789", 3); puits.done(); // Plus aucune donnée ne renvoie true ; // renvoie 'false' si vous souhaitez annuler le processus. } ); });
Avec remorque :
svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_header("Bande-annonce", "Dummy1, Dummy2"); res.set_chunked_content_provider( "texte/plaine", [](size_t offset, DataSink &sink) { puits.write("123", 3); puits.write("345", 3); puits.write("789", 3); puits.done_with_trailer({ {"Dummy1", "DummyVal1"}, {"Dummy2", "DummyVal2"} }); renvoie vrai ; } ); });
svr.Get("/content", [&](const Demande &req, Réponse &res) { res.set_file_content("./path/to/conent.html"); }); svr.Get("/content", [&](const Demande &req, Réponse &res) { res.set_file_content("./path/to/conent", "text/html"); });
Par défaut, le serveur envoie une réponse 100 Continue
pour un en-tête Expect: 100-continue
.
// Envoie une réponse '417 Échec de l'attente'.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return StatusCode::ExpectationFailed_417; });
// Envoie un statut final sans lire le message 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); // La valeur par défaut est 5svr.set_keep_alive_timeout(10); // La valeur par défaut est 5
svr.set_read_timeout(5, 0); // 5 secondessvr.set_write_timeout(5, 0); // 5 secondessvr.set_idle_interval(0, 100000); // 100 millisecondes
svr.set_payload_max_length(1024 * 1024 * 512); // 512 Mo
Note
Lorsque le type de contenu du corps de la demande est « www-form-urlencoded », la longueur réelle de la charge utile ne doit pas dépasser CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
.
Veuillez consulter l'exemple de serveur et l'exemple de client.
ThreadPool
est utilisé comme file d'attente de tâches par défaut et le nombre de threads par défaut est de 8, ou std::thread::hardware_concurrency()
. Vous pouvez le modifier avec CPPHTTPLIB_THREAD_POOL_COUNT
.
Si vous souhaitez définir le nombre de threads au moment de l'exécution, il n'existe pas de moyen pratique... Mais voici comment procéder.
svr.new_task_queue = [] { return new ThreadPool(12); } ;
Vous pouvez également fournir un paramètre facultatif pour limiter le nombre maximum de requêtes en attente, c'est-à-dire les requêtes accept()
émises par l'écouteur mais toujours en attente d'être traitées par les threads de travail.
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); } ;
La limite par défaut est 0 (illimité). Une fois la limite atteinte, l'écouteur arrêtera la connexion client.
Vous pouvez fournir votre propre implémentation de pool de threads en fonction de vos besoins.
class YourThreadPoolTaskQueue : public TaskQueue {public : YourThreadPoolTaskQueue (size_t n) { pool_.start_with_thread_count(n); } virtual bool enqueue(std::function<void()> fn) override { /* Renvoie true si la tâche a été réellement mise en file d'attente, ou false * si l'appelant doit abandonner la connexion correspondante. */ return pool_.enqueue(fn); } remplacement de virtual void shutdown() { pool_.shutdown_gracefully(); }privé: Votre poolThreadPool_; } ; 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; } } else { auto err = res.erreur(); std::cout << "Erreur HTTP : " << httplib::to_string(err) << std::endl; } }
Conseil
Le constructeur avec la chaîne Scheme-Host-Port est désormais pris en charge !
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");
Voici la liste des erreurs de Result::error()
.
erreur d'énumération { Succès = 0, Inconnu, Connexion, Lier l'adresse IP, Lire, Écrire, DépasserRedirectCount, Annulé, Connexion SSL, SSLLoadingCerts, Vérification du serveur SSL, Non pris en chargeMultipartBoundaryChars, Compression, ConnectionTimeout, } ;
httplib::En-têtes en-têtes = { { "Accepter-Encodage", "gzip, dégonfler" } };auto res = cli.Get("/hi", en-têtes);
ou
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
ou
cli.set_default_headers({ { "Accepter-Encodage", "gzip, dégonfler" } });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 paramètres ; params.emplace("nom", "john"); params.emplace("note", "coder");auto res = cli.Post("/post", params);
ou
httplib::Params paramètres{ { "nom", "john" }, { "note", "codeur" } };auto res = cli.Post("/post", params);
Éléments httplib::MultipartFormDataItems = { { "text1", "texte par défaut", "", "" }, { "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", éléments);
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 millisecondescli.set_read_timeout(5, 0); // 5 secondescli.set_write_timeout(5, 0); // 5 secondes
std::string body;auto res = cli.Get("/large-data", [&](const char *données, taille_t longueur_données) { body.append (données, longueur_données); renvoie vrai ; });
std::string body;auto res = cli.Get( "/stream", en-têtes(), [&](const Réponse &réponse) { EXPECT_EQ(StatusCode::OK_200, réponse.status); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler la requête. }, [&](const char *données, taille_t longueur_données) { body.append (données, longueur_données); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler la requête. });
std::string body = ...;auto res = cli.Post( "/stream", body.size(), [](décalage size_t, longueur size_t, DataSink &sink) { puits.write(body.data() + offset, longueur); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler la requête. }, "texte/plain");
auto res = cli.Post( "/stream", [](size_t offset, DataSink &sink) { Sink.os << "données fragmentées 1" ; Sink.os << "données fragmentées 2" ; Sink.os << "données fragmentées 3" ; puits.done(); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler la requête. }, "texte/plain");
httplib::Client cli(url, port);// imprime : 0 / 000 octets => 50 % completeauto res = cli.Get("/", [](uint64_t len, uint64_t total) { printf("%lld / %lld octets => %d%% terminén", len, total, (int)(len*100/total)); renvoie vrai ; // renvoie 'false' si vous souhaitez annuler la requête.} );
// Authentification de basecli.set_basic_auth("user", "pass");// Digest Authenticationcli.set_digest_auth("user", "pass");// Bearer Token Authenticationcli.set_bearer_token_auth("token");
Note
OpenSSL est requis pour l’authentification Digest.
cli.set_proxy("host", port);// Authentification de basecli.set_proxy_basic_auth("user", "pass");// Authentification Digestcli.set_proxy_digest_auth("user", "pass");// Authentification du jeton du porteurcli.set_proxy_bearer_token_auth ("passer");
Note
OpenSSL est requis pour l’authentification Digest.
httplib::Client cli("httpbin.org");auto res = cli.Get("/range/32", { httplib::make_range_header({{1, 10}}) // 'Plage : octets=1- 10'});// res->status doit être 206.// res->body doit être "bcdefghijk".
httplib::make_range_header({{1, 10}, {20, -1}}) // 'Plage : octets=1-10, 20-'httplib::make_range_header({{100, 199}, {500, 599 }}) // 'Plage : octets=100-199, 500-599'httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Plage : octets=0-0, -1'
httplib::Client cli("localhost", 1234); cli.Get("/bonjour"); // avec "Connexion : fermer"cli.set_keep_alive(true); cli.Get("/monde"); cli.set_keep_alive(false); cli.Get("/dernière-requête"); // avec "Connexion : fermer"
httplib::Client cli("yahoo.com");auto res = cli.Get("/"); res->statut ; // 301cli.set_follow_location(true); res = cli.Get("/"); res->statut ; // 200
Note
Cette fonctionnalité n'est pas encore disponible sous Windows.
cli.set_interface("eth0"); // Nom de l'interface, adresse IP ou nom d'hôte
Le serveur peut appliquer une compression aux contenus de type MIME suivants :
tous les types de texte sauf texte/flux d'événements
image/svg+xml
application/javascript
application/json
application/xml
application/xhtml+xml
La compression 'gzip' est disponible avec CPPHTTPLIB_ZLIB_SUPPORT
. libz
devrait être lié.
La compression Brotli est disponible avec CPPHTTPLIB_BROTLI_SUPPORT
. Les bibliothèques nécessaires doivent être liées. Veuillez consulter https://github.com/google/brotli pour plus de détails.
cli.set_compress(true); res = cli.Post("/resource/foo", "...", "text/plain");
cli.set_decompress(false); res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); res->corps; // Données compressées
poll
au lieu de select
L'appel système select
est utilisé par défaut car il est plus largement pris en charge. Si vous souhaitez laisser cpp-httplib utiliser poll
à la place, vous pouvez le faire avec CPPHTTPLIB_USE_POLL
.
La prise en charge d'Unix Domain Socket est disponible sur Linux et macOS.
// Serveurhttplib::Serveur 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" peut être un chemin relatif ou un chemin absolu. Votre application doit disposer des autorisations appropriées pour le chemin. Vous pouvez également utiliser une adresse de socket abstraite sous Linux. Pour utiliser une adresse de socket abstraite, ajoutez un octet nul (« x00 ») au chemin.
$ ./split.py -husage : split.py [-h] [-e EXTENSION] [-o OUT]Ce script divise httplib.h en parties .h et .cc.arguments facultatifs : -h, --help show ce message d'aide et quittez -e EXTENSION, --extension EXTENSION extension du fichier d'implémentation (par défaut : cc) -o OUT, --out OUT où écrire les fichiers (par défaut : out)$ ./split.pyWrote out/httplib.h et out/httplib.cc
Dockerfile pour le serveur HTTP statique est disponible. Le numéro de port de ce serveur HTTP est 80 et il sert les fichiers statiques du répertoire /html
du conteneur.
> docker build -t cpp-httplib-server ....> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Servir HTTP sur le port 0.0.0.0 80... 192.168.65.1 - - [31/août/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/août/2024 :21:34:26 +0000] "OBTENIR / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/Août/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0..."
À partir du hub Docker
> 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 Servir HTTP sur le port 0.0.0.0 80... 192.168.65.1 - - [31/août/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/août/2024 :21:34:26 +0000] "OBTENIR / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/Août/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0..."
g++ 4.8 et versions antérieures ne peuvent pas créer cette bibliothèque car <regex>
dans les versions est cassé.
Incluez httplib.h
avant Windows.h
ou incluez Windows.h
en définissant WIN32_LEAN_AND_MEAN
au préalable.
#include <httplib.h>#include <Windows.h>
#define WIN32_LEAN_AND_MEAN#include <Windows.h>#include <httplib.h>
Note
cpp-httplib ne prend officiellement en charge que la dernière version de Visual Studio. Cela peut fonctionner avec les anciennes versions de Visual Studio, mais je ne peux plus le vérifier. Les demandes d'extraction sont toujours les bienvenues pour les anciennes versions de Visual Studio, à moins qu'elles ne rompent la conformité C++11.
Note
Windows 8 ou version antérieure, Visual Studio 2013 ou version antérieure, ainsi que Cygwin et MSYS2, y compris MinGW, ne sont ni pris en charge ni testés.
Licence MIT (© 2024 Yuji Hirose)
Ces personnes ont apporté de grandes contributions pour peaufiner cette bibliothèque à un niveau totalement différent d'un simple jouet !