Uma biblioteca HTTP/HTTPS de plataforma cruzada somente de cabeçalho de arquivo único C++ 11.
É extremamente fácil de configurar. Basta incluir o arquivo httplib.h no seu código!
Importante
Esta biblioteca usa E/S de soquete de 'bloqueio'. Se você está procurando uma biblioteca com E/S de soquete 'sem bloqueio', esta não é a que você deseja.
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "caminho/para/httplib.h"// HTTPhttplib::Server svr;// HTTPShttplib::SSLServer svr;
svr.Get("/oi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Olá Mundo!", "texto/simples");
});
svr.ouvir("0.0.0.0", 8080); #define CPPHTTPLIB_OPENSSL_SUPPORT#include "caminho/para/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->estado;
res->corpo; O suporte SSL está disponível com CPPHTTPLIB_OPENSSL_SUPPORT . libssl e libcrypto devem estar vinculados.
Observação
cpp-httplib atualmente suporta apenas a versão 3.0 ou posterior. Por favor, consulte esta página para obter mais informações.
Dica
Para macOS: cpp-httplib agora pode usar certificados de sistema com CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN . CoreFoundation e Security devem estar vinculados a -framework .
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "caminho/para/httplib.h"// Serverhttplib::SSLServer svr("./cert.pem", "./key.pem");// Clienthttplib::Client cli("https: //localhost:1234"); // esquema + hosthttplib::SSLClient cli("localhost:1234"); //hosthttplib::SSLClient cli("localhost", 1234); // host, porta // Use seu CA bundlecli.set_ca_cert_path("./ca-bundle.crt");// Desative a verificação de certificadocli.enable_server_certificate_verification(false);// Desative a verificação de hostcli.enable_server_host_verification(false);Observação
Ao usar SSL, parece impossível evitar o SIGPIPE em todos os casos, já que em alguns sistemas operacionais, o SIGPIPE só pode ser suprimido por mensagem, mas não há como fazer com que a biblioteca OpenSSL faça isso para suas comunicações internas. Se o seu programa precisar evitar ser encerrado no SIGPIPE, a única maneira totalmente geral pode ser configurar um manipulador de sinal para o SIGPIPE manipular ou ignorá-lo você mesmo.
#include <httplib.h>int principal(void)
{ usando o espaço para nome httplib;
Servidor SVR;
svr.Get("/oi", [](const Solicitação& req, Resposta& res) {
res.set_content("Olá Mundo!", "texto/simples");
}); // Combine o caminho da solicitação com uma expressão regular // e extraia suas capturas
svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { números automáticos = req.matches[1];
res.set_content(números, "texto/simples");
}); //Captura o segundo segmento do caminho da solicitação como parâmetro de caminho "id"
svr.Get("/users/:id", [&](const Request& req, Response& res) { auto user_id = req.path_params.at("id");
res.set_content(user_id, "texto/simples");
}); // Extrai valores de cabeçalhos HTTP e parâmetros de consulta de 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("chave")) { auto val = req.get_param_value("chave");
}
res.set_content(req.body, "texto/simples");
});
svr.Get("/stop", [&](const Solicitação& req, Resposta& res) {
svr.stop();
});
svr.listen("localhost", 1234);
} Os métodos Post , Put , Delete e Options também são suportados.
porta interna = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind(); // Montar / em ./www directoryauto ret = svr.set_mount_point("/", "./www");if (!ret) { // O diretório base especificado não existe...}// Montar / public para ./www directoryret = svr.set_mount_point("/public", "./www");// Monte /public para ./www1 e ./www2 directoriesret = svr.set_mount_point("/public", "./www1"); // 1ª ordem para searchret = svr.set_mount_point("/public", "./www2"); // 2ª ordem de pesquisa// Remover montagem /ret = svr.remove_mount_point("/");// Remover montagem /publicret = svr.remove_mount_point("/public"); // Extensão de arquivo definida pelo usuário e tipo MIME mapeamentossvr.set_file_extension_and_mimetype_mapping("cc", "text/xc");
svr.set_file_extension_and_mimetype_mapping("cpp", "texto/xc");
svr.set_file_extension_and_mimetype_mapping("hh", "texto/xh");Os seguintes são mapeamentos integrados:
| Extensão | Tipo MIME | Extensão | Tipo MIME |
|---|---|---|---|
| css | texto/css | mpga | áudio/mpeg |
| csv | texto/csv | teia | áudio/webm |
| TXT | texto/simples | wav | áudio/onda |
| vtt | texto/vtt | ai | fonte/otf |
| html, html | texto/html | ttf | fonte/ttf |
| apng | imagem/apng | uau | fonte/woff |
| avif | imagem/avif | woff2 | fonte/woff2 |
| bmp | imagem/bmp | 7z | aplicativo/x-7z-comprimido |
| gif | imagem/gif | átomo | aplicação/atom+xml |
| png | imagem/png | aplicação/pdf | |
| SVG | imagem/svg+xml | mjs, js | aplicativo/javascript |
| webp | imagem/webp | json | aplicativo/json |
| ico | imagem/ícone x | rsrs | aplicativo/rss+xml |
| tif | imagem/tiff | alcatrão | aplicação/x-tar |
| briga | imagem/tiff | xhtml, xht | aplicação/xhtml+xml |
| jpeg, jpeg | imagem/jpeg | XSLT | aplicativo/xslt+xml |
| mp4 | vídeo/mp4 | xml | aplicativo/xml |
| mpeg | vídeo/mpeg | gz | aplicativo/gzip |
| webm | vídeo/webm | zip | aplicativo/zip |
| mp3 | áudio/mp3 | estava | aplicação/wasm |
Aviso
Esses métodos de servidor de arquivos estáticos não são thread-safe.
// O manipulador é chamado logo antes da resposta ser enviada para um clientsvr.set_file_request_handler([](const Request &req, Response &res) {
...
}); svr.set_logger([](const auto& req, const auto& res) { seu_logger(req, res);
}); svr.set_error_handler([](const auto& req, auto& res) { auto fmt = "<p>Status do erro: <span style='color:red;'>%d</span></p>"; char buf [BUFSIZ];snprintf(buf, sizeof(buf),fmt,res.status);
res.set_content(buf, "texto/html");
});O manipulador de exceção será chamado se um manipulador de roteamento de usuário gerar um erro.
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { auto fmt = "<h1>Erro 500</h1><p>%s</p>"; char buf[BUFSIZ] ; tente { std::rethrow_exception(ep);
} catch (std::exception &e) { snprintf(buf, sizeof(buf), fmt, e.what());
} catch (...) { // Veja a seguinte NOTA snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
}
res.set_content(buf, "texto/html");
res.status = StatusCode::InternalServerError_500;
});Cuidado
se você não fornecer o bloco catch (...) para um ponteiro de exceção relançado, uma exceção não detectada acabará causando a falha do servidor. Tome cuidado!
svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") {
res.set_content("mundo", "texto/html"); return Servidor::HandlerResponse::Handled;
} return Servidor::HandlerResponse::Unhandled;
}); svr.set_post_routing_handler([](const auto& req, auto& res) {
res.set_header("ADDITIONAL_HEADER", "valor");
}); 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("nome1"); // arquivo.nomedoarquivo; // arquivo.content_type; svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // NOTA: `content_reader` está bloqueando até que todos os campos de dados do formulário sejam lidos
Arquivos MultipartFormDataItems; content_reader(
[&](const MultipartFormData &arquivo) {
arquivos.push_back(arquivo); retornar verdadeiro;
},
[&](const char *dados, tamanho_t comprimento_dados) {
arquivos.back().content.append(dados, data_length); retornar verdadeiro;
});
} outro {
std::string corpo; content_reader([&](const char *dados, tamanho_t data_length) {
body.append(dados, data_length); retornar verdadeiro;
});
}
}); const tamanho_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const Solicitação &req, Resposta &res) { auto dados = new std::string("abcdefg");
res.set_content_provider(
data->size(), // Comprimento do conteúdo "text/plain", // Tipo de conteúdo
[&, dados](tamanho_t deslocamento, tamanho_t comprimento, DataSink &sink) { const auto &d = *dados;
sink.write(&d[offset], std::min(comprimento, DATA_CHUNK_SIZE)); retornar verdadeiro; // retorne 'false' se quiser cancelar o processo.
},
[dados](bool sucesso) {excluir dados; });
});Sem comprimento de conteúdo:
svr.Get("/stream", [&](const Solicitação &req, Resposta &res) {
res.set_content_provider( "text/plain", // Tipo de conteúdo
[&](size_t offset, DataSink &sink) { if (/* ainda há dados */) {
std::vector<char> dados; //prepara dados...
sink.write(data.data(), data.size());
} outro {
pia.concluído(); //Não há mais dados
} retornar verdadeiro; // retorne 'false' se quiser cancelar o processo.
});
}); svr.Get("/chunked", [&](const Solicitação& req, Resposta& res) {
res.set_chunked_content_provider("texto/simples",
[](tamanho_t deslocamento, DataSink &sink) {
coletor.write("123", 3);
coletor.write("345", 3);
coletor.write("789", 3);
pia.concluído(); // Não há mais dados return true; // retorne 'false' se quiser cancelar o processo.
}
);
});Com reboque:
svr.Get("/chunked", [&](const Solicitação& req, Resposta& res) {
res.set_header("Trailer", "Manequim1, Manequim2");
res.set_chunked_content_provider("texto/simples",
[](tamanho_t deslocamento, DataSink &sink) {
coletor.write("123", 3);
coletor.write("345", 3);
coletor.write("789", 3);
sink.done_with_trailer({
{"Dummy1", "DummyVal1"},
{"Dummy2", "DummyVal2"}
}); retornar verdadeiro;
}
);
}); svr.Get("/content", [&](const Solicitação &req, Resposta &res) {
res.set_file_content("./path/to/conent.html");
});
svr.Get("/content", [&](const Solicitação &req, Resposta &res) {
res.set_file_content("./caminho/para/conent", "texto/html");
}); Por padrão, o servidor envia uma resposta 100 Continue para um cabeçalho Expect: 100-continue .
// Envia uma resposta '417 Expectation Failed'.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return StatusCode::ExpectationFailed_417;
}); // Envia um status final sem ler a mensagem 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); // O padrão é 5svr.set_keep_alive_timeout(10); // O padrão é 5
svr.set_read_timeout(5, 0); // 5 segundossvr.set_write_timeout(5, 0); // 5 segundossvr.set_idle_interval(0, 100000); // 100 milissegundos
svr.set_payload_max_length(1024 * 1024 * 512); //512MB
Observação
Quando o tipo de conteúdo do corpo da solicitação é 'www-form-urlencoded', o comprimento real da carga útil não deve exceder CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH .
Consulte exemplo de servidor e exemplo de cliente.
ThreadPool é usado como uma fila de tarefas padrão e a contagem de threads padrão é 8 ou std::thread::hardware_concurrency() . Você pode alterá-lo com CPPHTTPLIB_THREAD_POOL_COUNT .
Se você deseja definir a contagem de threads em tempo de execução, não há uma maneira conveniente... Mas aqui está como.
svr.new_task_queue = [] {retorna novo ThreadPool(12); }; Você também pode fornecer um parâmetro opcional para limitar o número máximo de solicitações pendentes, ou seja, solicitações accept() editadas pelo ouvinte, mas ainda aguardando para serem atendidas por threads de trabalho.
svr.new_task_queue = [] { retornar novo ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };O limite padrão é 0 (ilimitado). Quando o limite for atingido, o ouvinte encerrará a conexão do cliente.
Você pode fornecer sua própria implementação de pool de threads de acordo com sua necessidade.
class YourThreadPoolTaskQueue: public TaskQueue {public: YourThreadPoolTaskQueue (tamanho_t n) {
pool_.start_with_thread_count(n);
} virtual bool enqueue(std::function<void()> fn) override { /* Retorna true se a tarefa foi realmente enfileirada ou false * se o chamador precisar interromper a conexão correspondente. */ return pool_.enqueue(fn);
} virtual void shutdown() substituição {
pool_.shutdown_gracefully();
}privado:
Seu poolThreadPool_;
};
svr.new_task_queue = [] {retorna novo YourThreadPoolTaskQueue(12);
}; #include <httplib.h>#include <iostream>int principal(void)
{
httplib::Cliente 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.error();
std::cout << "Erro HTTP: " << httplib::to_string(err) << std::endl;
}
}Dica
Construtor com string esquema-host-port agora é suportado!
httplib::Cliente cli("localhost");
httplib::Cliente cli("localhost:8080");
httplib::Cliente cli("http://localhost");
httplib::Cliente cli("http://localhost:8080");
httplib::Cliente cli("https://localhost");
httplib::SSLClient cli("localhost"); Aqui está a lista de erros de Result::error() .
Erro de enumeração {
Sucesso = 0,
Desconhecido,
Conexão,
Vincular endereçoIP,
Ler,
Escrever,
ExceedRedirectCount,
Cancelado,
Conexão SSL,
Certificados SSLLoading,
SSLServerVerificação,
MultipartBoundaryChars não suportados,
Compressão,
ConnectionTimeout,
}; httplib::Cabeçalhos cabeçalhos = {
{ "Aceitar codificação", "gzip, deflate" }
};auto res = cli.Get("/hi", cabeçalhos);ou
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});ou
cli.set_default_headers({
{ "Aceitar codificação", "gzip, deflate" }
});auto res = cli.Get("/oi"); res = cli.Post("/post", "texto", "texto/simples");
res = cli.Post("/pessoa", "nome=john1¬e=coder", "aplicativo/x-www-form-urlencoded"); httplib::Params params;
params.emplace("nome", "joão");
params.emplace("nota", "codificador");auto res = cli.Post("/post", params);ou
httplib::Params params{
{ "nome", "joão" },
{ "nota", "codificador" }
};auto res = cli.Post("/post", params); httplib::MultipartFormDataItems itens = {
{ "texto1", "texto padrão", "", "" },
{ "text2", "aωb", "", "" },
{ "arquivo1", "hnennlnlnon", "olá.txt", "texto/simples" },
{ "arquivo2", "{n "mundo", truen}n", "mundo.json", "aplicativo/json" },
{ "arquivo3", "", "", "aplicativo/fluxo de octetos" },
};auto res = cli.Post("/multipart", itens); res = cli.Put("/resource/foo", "texto", "texto/simples"); res = cli.Delete("/recurso/foo"); res = cli.Options("*");
res = cli.Options("/resource/foo");cli.set_connection_timeout(0, 300000); // 300 milissegundoscli.set_read_timeout(5, 0); // 5 segundoscli.set_write_timeout(5, 0); // 5 segundos
std::string body;auto res = cli.Get("/dados grandes",
[&](const char *dados, tamanho_t comprimento_dados) {
body.append(dados, data_length); retornar verdadeiro;
}); std::string body;auto res = cli.Get( "/stream", Headers(),
[&](const Resposta &resposta) { EXPECT_EQ(StatusCode::OK_200, resposta.status); retornar verdadeiro; // retorne 'false' se quiser cancelar a solicitação.
},
[&](const char *dados, tamanho_t comprimento_dados) {
body.append(dados, data_length); retornar verdadeiro; // retorne 'false' se quiser cancelar a solicitação.
}); std::string body = ...;auto res = cli.Post( "/stream", body.size(),
[](deslocamento de tamanho_t, comprimento de tamanho_t, DataSink &sink) {
sink.write(body.data() + deslocamento, comprimento); retornar verdadeiro; // retorne 'false' se quiser cancelar a solicitação.
}, "texto/simples"); resolução automática = cli.Post( "/stream",
[](tamanho_t deslocamento, DataSink &sink) {
sink.os << "dados fragmentados 1";
sink.os << "dados fragmentados 2";
sink.os << "dados fragmentados 3";
pia.concluído(); retornar verdadeiro; // retorne 'false' se quiser cancelar a solicitação.
}, "texto/simples"); httplib::Cliente cli(url, porta);// imprime: 0/000 bytes => 50% completeauto res = cli.Get("/", [](uint64_t len, uint64_t total) { printf("%lld / %lld bytes => %d%% concluídon",
lento, total,
(int)(len*100/total)); retornar verdadeiro; // retorne 'false' se quiser cancelar a solicitação.}
); 
// Autenticação Básicacli.set_basic_auth("usuário", "pass");// Autenticação Digestcli.set_digest_auth("usuário", "pass");// Autenticação do Token do Portadorcli.set_bearer_token_auth("token");Observação
OpenSSL é necessário para autenticação Digest.
cli.set_proxy("host", porta);// Autenticação básicacli.set_proxy_basic_auth("usuário", "pass");// Autenticação Digestcli.set_proxy_digest_auth("usuário", "pass");// Autenticação de token de portadorcli.set_proxy_bearer_token_auth ("passar");Observação
OpenSSL é necessário para autenticação Digest.
httplib::Client cli("httpbin.org");auto res = cli.Get("/range/32", { httplib::make_range_header({{1, 10}}) // 'Intervalo: bytes=1- 10'});// res->status deve ser 206.// res->body deve ser "bcdefghijk". httplib::make_range_header({{1, 10}, {20, -1}}) // 'Intervalo: bytes=1-10, 20-'httplib::make_range_header({{100, 199}, {500, 599 }}) // 'Intervalo: bytes=100-199, 500-599'httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Intervalo: bytes=0-0, -1' httplib::Cliente cli("localhost", 1234);
cli.Get("/olá"); // com "Conexão: fechar"cli.set_keep_alive(true);
cli.Get("/mundo");
cli.set_keep_alive(falso);
cli.Get("/última solicitação"); // com "Conexão: fechar" httplib::Client cli("yahoo.com");auto res = cli.Get("/");
res->estado; //301cli.set_follow_location(true);
res = cli.Get("/");
res->estado; // 200Observação
Este recurso ainda não está disponível no Windows.
cli.set_interface("eth0"); //Nome da interface, endereço IP ou nome do hostO servidor pode aplicar compactação aos seguintes conteúdos do tipo MIME:
todos os tipos de texto, exceto text/event-stream
imagem/svg+xml
aplicativo/javascript
aplicativo/json
aplicativo/xml
aplicação/xhtml+xml
A compactação 'gzip' está disponível com CPPHTTPLIB_ZLIB_SUPPORT . libz deve estar vinculado.
A compactação Brotli está disponível com CPPHTTPLIB_BROTLI_SUPPORT . As bibliotecas necessárias devem estar vinculadas. Consulte https://github.com/google/brotli para obter mais detalhes.
cli.set_compress(verdadeiro);
res = cli.Post("/resource/foo", "...", "text/plain"); cli.set_decompress(falso);
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->corpo; //Dados compactadospoll em vez de select select a chamada do sistema é usada como padrão, pois é mais amplamente suportada. Se você quiser permitir que o cpp-httplib use poll , você pode fazer isso com CPPHTTPLIB_USE_POLL .
O suporte para soquete de domínio Unix está disponível no Linux e no macOS.
// Servidorhttplib::Servidor svr("./my-socket.sock");
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);// Clientehttplib::Client cli("./my-socket.sock");
cli.set_address_family(AF_UNIX);"my-socket.sock" pode ser um caminho relativo ou absoluto. Seu aplicativo deve ter as permissões apropriadas para o caminho. Você também pode usar um endereço de soquete abstrato no Linux. Para usar um endereço de soquete abstrato, acrescente um byte nulo ('x00') ao caminho.
$ ./split.py -husage: split.py [-h] [-e EXTENSION] [-o OUT]Este script divide httplib.h em partes .h e .cc.argumentos opcionais: -h, --help show esta mensagem de ajuda e saia -e EXTENSION, --extension EXTENSION extensão do arquivo de implementação (padrão: cc) -o OUT, --out OUT onde gravar os arquivos (padrão: out)$ ./split.pyEscreveu/httplib.h e saiu/httplib.cc
Dockerfile para servidor HTTP estático está disponível. O número da porta deste servidor HTTP é 80 e serve arquivos estáticos do diretório /html no contêiner.
> docker build -t cpp-httplib-server ....> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Servindo HTTP na porta 0.0.0.0 80 ... 192.168.65.1 - - [31/ago/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/ago/2024 :21:34:26 +0000] "OBTER / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/ago/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0 ..."
Do DockerHub
> 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 Servindo HTTP na porta 0.0.0.0 80 ... 192.168.65.1 - - [31/ago/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/ago/2024 :21:34:26 +0000] "OBTER / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/ago/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -" "Mozilla/5.0 ..."
g++ 4.8 e anteriores não podem construir esta biblioteca porque <regex> nas versões estão quebradas.
Inclua httplib.h antes de Windows.h ou inclua Windows.h definindo WIN32_LEAN_AND_MEAN antecipadamente.
#include <httplib.h>#include <Windows.h>
#define WIN32_LEAN_AND_MEAN#include <Windows.h>#include <httplib.h>
Observação
cpp-httplib suporta oficialmente apenas o Visual Studio mais recente. Pode funcionar com versões anteriores do Visual Studio, mas não consigo mais verificá-lo. Solicitações pull são sempre bem-vindas para versões mais antigas do Visual Studio, a menos que quebrem a conformidade do C++ 11.
Observação
Windows 8 ou inferior, Visual Studio 2013 ou inferior e Cygwin e MSYS2 incluindo MinGW não são suportados nem testados.
Licença MIT (© 2024 Yuji Hirose)
Essas pessoas fizeram grandes contribuições para aprimorar esta biblioteca a um nível totalmente diferente, passando de um simples brinquedo!