C++11 단일 파일 헤더 전용 크로스 플랫폼 HTTP/HTTPS 라이브러리입니다.
설정이 매우 쉽습니다. 코드에 httplib.h 파일을 포함시키면 됩니다!
중요한
이 라이브러리는 '블로킹' 소켓 I/O를 사용합니다. '비차단' 소켓 I/O가 있는 라이브러리를 찾고 있다면 이는 원하는 라이브러리가 아닙니다.
#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("Hello World!", "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"); res->상태; 해상도->본체;
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); // 호스트, 포트// CA Bundlecli.set_ca_cert_path("./ca-bundle.crt") 사용;// 인증서 확인 비활성화cli.enable_server_certificate_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 요청& 요청, 응답& res) { res.set_content("Hello World!", "text/plain"); }); // 요청 경로를 정규 표현식과 일치시키고 // 해당 캡처를 추출합니다. svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { 자동 숫자 = req.matches[1]; res.set_content(numbers, "text/plain"); }); // 요청 경로의 두 번째 세그먼트를 "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"); }); // 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("key")) { auto val = req.get_param_value("key"); } res.set_content(req.body, "text/plain"); }); svr.Get("/stop", [&](const 요청& 요청, 응답& res) { svr.stop(); }); svr.listen("localhost", 1234); }
Post
, Put
, Delete
및 Options
메소드도 지원됩니다.
int port = svr.bind_to_any_port("0.0.0.0"); svr.listen_after_bind();
// /를 ./www 디렉토리에 마운트auto ret = svr.set_mount_point("/", "./www");if (!ret) { // 지정된 기본 디렉토리가 존재하지 않습니다...}// 마운트 / ./www 디렉토리에 공개ret = svr.set_mount_point("/public", "./www");// /public을 ./www1 및 ./www2 디렉토리에 마운트ret = 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("hh", "text/xh");
다음은 기본 제공 매핑입니다.
확대 | MIME 유형 | 확대 | MIME 유형 |
---|---|---|---|
CSS | 텍스트/CSS | mpga | 오디오/MPEG |
CSV | 텍스트/CSV | 웨바 | 오디오/웹 |
txt | 텍스트/일반 | 웨이브 | 오디오/웨이브 |
vtt | 텍스트/vtt | otf | 글꼴/otf |
HTML, HTM | 텍스트/html | ttf | 글꼴/ttf |
APNG | 이미지/apng | 와우 | 글꼴/woff |
아비프 | 이미지/avif | 워프2 | 글꼴/woff2 |
bmp | 이미지/bmp | 7z | 응용 프로그램/x-7z 압축 |
gif | 이미지/gif | 원자 | 애플리케이션/원자+xml |
png | 이미지/png | 신청서/pdf | |
svg | 이미지/svg+xml | mjs, js | 애플리케이션/자바스크립트 |
웹 | 이미지/웹 | JSON | 애플리케이션/json |
아이코 | 이미지/x-아이콘 | RSS | 애플리케이션/rss+xml |
티프 | 이미지/티파니 | 타르 | 애플리케이션/x-tar |
사소한 말다툼 | 이미지/티파니 | xhtml, xht | 응용프로그램/xhtml+xml |
JPEG, jpg | 이미지/jpeg | xslt | 응용프로그램/xslt+xml |
mp4 | 비디오/mp4 | xml | 애플리케이션/xml |
mpeg | 비디오/MPEG | gz | 애플리케이션/gzip |
웹 | 비디오/웹엠 | 지퍼 | 애플리케이션/zip |
mp3 | 오디오/mp3 | 말벌 | 애플리케이션/wasm |
경고
이러한 정적 파일 서버 방법은 스레드로부터 안전하지 않습니다.
// 핸들러는 클라이언트에 응답이 전송되기 직전에 호출됩니다.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, "text/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, "알 수 없는 예외"); } res.set_content(buf, "text/html"); res.status = StatusCode::InternalServerError_500; });
주의
다시 발생한 예외 포인터에 대해 catch (...)
블록을 제공하지 않으면 포착되지 않은 예외로 인해 서버 충돌이 발생합니다. 조심하세요!
svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") { res.set_content("세계", "text/html"); 서버::HandlerResponse::Handled를 반환합니다. } return Server::HandlerResponse::Unhandled; });
svr.set_post_routing_handler([](const auto& req, auto& res) { res.set_header("ADDITIONAL_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;
svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // 참고: `content_reader`는 모든 양식 데이터 필드를 읽을 때까지 차단됩니다. MultipartFormDataItems 파일; 내용_리더( [&](const MultipartFormData &file) { files.push_back(파일); 사실을 반환; }, [&](const char *data, size_t data_length) { files.back().content.append(data, data_length); 사실을 반환; }); } 또 다른 { 표준::문자열 본문; content_reader([&](const char *data, size_t data_length) { body.append(data, data_length); 사실을 반환; }); } });
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; ink.write(&d[오프셋], std::min(길이, DATA_CHUNK_SIZE)); 사실을 반환; // 프로세스를 취소하려면 'false'를 반환합니다. }, [데이터](bool 성공) { 데이터 삭제; }); });
콘텐츠 길이가 없는 경우:
svr.Get("/stream", [&](const 요청 &req, 응답 &res) { res.set_content_provider( "text/plain", // 콘텐츠 유형 [&](size_t offset, DataSink &sink) { if (/* 아직 데이터가 있음 */) { std::벡터<char> 데이터; // 데이터 준비... ink.write(data.data(), data.size()); } 또 다른 { 싱크.완료(); // 더 이상 데이터가 없습니다. } true를 반환합니다. // 프로세스를 취소하려면 'false'를 반환합니다. }); });
svr.Get("/chunked", [&](const 요청& 요청, 응답& res) { res.set_chunked_content_provider( "텍스트/일반", [](size_t 오프셋, 데이터싱크 및 싱크) { sing.write("123", 3); sing.write("345", 3); sing.write("789", 3); 싱크.완료(); // 더 이상 데이터가 반환되지 않습니다. true; // 프로세스를 취소하려면 'false'를 반환합니다. } ); });
트레일러 포함:
svr.Get("/chunked", [&](const 요청& 요청, 응답& res) { res.set_header("예고편", "Dummy1, Dummy2"); res.set_chunked_content_provider( "텍스트/일반", [](size_t 오프셋, 데이터싱크 및 싱크) { sing.write("123", 3); sing.write("345", 3); sing.write("789", 3); sing.done_with_trailer({ {"Dummy1", "DummyVal1"}, {"Dummy2", "DummyVal2"} }); 사실을 반환; } ); });
svr.Get("/content", [&](const 요청 &req, 응답 &res) { res.set_file_content("./path/to/conent.html"); }); svr.Get("/content", [&](const 요청 &req, 응답 &res) { res.set_file_content("./path/to/conent", "text/html"); });
기본적으로 서버는 Expect: 100-continue
헤더에 대해 100 Continue
응답을 보냅니다.
// '417 예상 실패' 보내기 response.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return StatusCode::ExpectationFailed_417; });
// 메시지를 읽지 않고 최종 상태를 보냅니다. 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); // 512MB
메모
요청 본문 콘텐츠 유형이 '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) { pool_.start_with_thread_count(n); } virtual bool enqueue(std::function<void()> fn) override { /* 작업이 실제로 대기열에 추가된 경우 true를 반환하고, 호출자가 해당 연결을 끊어야 하는 경우 false *를 반환합니다. */ return pool_.enqueue(fn); } 가상 무효 종료() 재정의 { pool_.shutdown_graceously(); }사적인: YourThreadPool 풀_; }; svr.new_task_queue = [] { return new YourThreadPoolTaskQueue(12); };
#include <httplib.h>#include <iostream>int main(void) { httplib::클라이언트 cli("localhost", 1234); if (auto res = cli.Get("/hi")) { if (res->status == StatusCode::OK_200) { std::cout << res->body << std::endl; } } else { 자동 오류 = res.error(); std::cout << "HTTP 오류: " << httplib::to_string(err) << std::endl; } }
팁
이제 Scheme-host-port 문자열이 포함된 생성자가 지원됩니다!
httplib::클라이언트 cli("localhost"); httplib::클라이언트 cli("localhost:8080"); httplib::클라이언트 cli("http://localhost"); httplib::클라이언트 cli("http://localhost:8080"); httplib::클라이언트 cli("https://localhost"); httplib::SSLClient cli("localhost");
다음은 Result::error()
의 오류 목록입니다.
열거형 오류 { 성공 = 0, 알려지지 않은, 연결, 바인딩IP주소, 읽다, 쓰다, 초과리디렉션카운트, 취소, SSL연결, SSLLoadingCerts, SSL서버확인, 지원되지 않는MultipartBoundaryChars, 압축, 연결 시간 초과, };
httplib::헤더 헤더 = { { "인코딩 허용", "gzip, 수축" } };auto res = cli.Get("/hi", 헤더);
또는
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
또는
cli.set_default_headers({ { "인코딩 허용", "gzip, 수축" } });auto res = cli.Get("/hi");
res = cli.Post("/포스트", "텍스트", "텍스트/일반"); 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 항목 = { { "텍스트1", "텍스트 기본값", "", "" }, { "텍스트2", "aΩb", "", "" }, { "file1", "hnennlnlnon", "hello.txt", "text/plain" }, { "file2", "{n "world", truen}n", "world.json", "application/json" }, { "file3", "", "", "응용 프로그램/옥텟 스트림" }, };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밀리초cli.set_read_timeout(5, 0); // 5초cli.set_write_timeout(5, 0); // 5초
std::string body;auto res = cli.Get("/대형 데이터", [&](const char *data, size_t data_length) { body.append(data, data_length); 사실을 반환; });
std::string body;auto res = cli.Get( "/stream", 헤더(), [&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); 사실을 반환; // 요청을 취소하려면 'false'를 반환합니다. }, [&](const char *data, size_t data_length) { body.append(data, data_length); 사실을 반환; // 요청을 취소하려면 'false'를 반환합니다. });
std::string body = ...;auto res = cli.Post( "/stream", body.size(), [](size_t 오프셋, size_t 길이, 데이터싱크 및 싱크) { sing.write(body.data() + 오프셋, 길이); 사실을 반환; // 요청을 취소하려면 'false'를 반환합니다. }, "텍스트/일반");
자동 해상도 = cli.Post( "/스트림", [](size_t 오프셋, 데이터싱크 및 싱크) { ink.os << "청크 데이터 1"; ink.os << "청크 데이터 2"; ink.os << "청크 데이터 3"; 싱크.완료(); 사실을 반환; // 요청을 취소하려면 'false'를 반환합니다. }, "텍스트/일반");
httplib::Client cli(url, port);// 인쇄: 0 / 000바이트 => 50% Completeauto res = cli.Get("/", [](uint64_t len, uint64_t total) { printf("%lld / %lld바이트 => %d%% 완료n", 렌, 합계, (int)(len*100/전체)); 사실을 반환; // 요청을 취소하려면 'false'를 반환합니다.} );
// 기본 인증cli.set_basic_auth("user", "pass");// 다이제스트 인증cli.set_digest_auth("user", "pass");// Bearer 토큰 인증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");// Bearer 토큰 인증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}}) // '범위: 바이트=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::클라이언트 cli("localhost", 1234); cli.Get("/안녕하세요"); // "연결: 닫기"로 cli.set_keep_alive(true); cli.Get("/세계"); cli.set_keep_alive(false); cli.Get("/마지막 요청"); // "연결: 닫기" 사용
httplib::클라이언트 cli("yahoo.com");auto res = cli.Get("/"); res->상태; // 301cli.set_follow_location(true); res = cli.Get("/"); res->상태; // 200
메모
이 기능은 아직 Windows에서는 사용할 수 없습니다.
cli.set_interface("eth0"); // 인터페이스 이름, IP 주소 또는 호스트 이름
서버는 다음 MIME 유형 콘텐츠에 압축을 적용할 수 있습니다.
text/event-stream을 제외한 모든 텍스트 유형
이미지/svg+xml
애플리케이션/자바스크립트
애플리케이션/json
애플리케이션/xml
응용프로그램/xhtml+xml
CPPHTTPLIB_ZLIB_SUPPORT
사용하면 'gzip' 압축을 사용할 수 있습니다. libz
연결해야 합니다.
Brotli 압축은 CPPHTTPLIB_BROTLI_SUPPORT
를 통해 사용할 수 있습니다. 필요한 라이브러리를 연결해야 합니다. 자세한 내용은 https://github.com/google/brotli를 참조하세요.
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"}}); 해상도->본체; // 압축된 데이터
select
대신 poll
사용 select
시스템 호출은 더 광범위하게 지원되므로 기본값으로 사용됩니다. cpp-httplib poll
대신 사용하도록 하려면 CPPHTTPLIB_USE_POLL
사용하면 됩니다.
Unix 도메인 소켓 지원은 Linux 및 macOS에서 사용할 수 있습니다.
// 서버httplib::서버 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 EXTENSION] [-o OUT]이 스크립트는 httplib.h를 .h 및 .cc 부분으로 분할합니다. 선택적 인수: -h, --help 표시 이 도움말 메시지를 보고 종료합니다. -e EXTENSION, --extension EXTENSION 구현 파일의 확장(기본값: cc) -o OUT, --out OUT 파일을 쓸 위치(기본값: out)$ ./split.pyout/httplib.h 및 out/httplib.cc 작성
정적 HTTP 서버용 Dockerfile을 사용할 수 있습니다. 이 HTTP 서버의 포트 번호는 80이며 컨테이너의 /html
디렉터리에서 정적 파일을 제공합니다.
> docker build -t cpp-httplib-server ....> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server 0.0.0.0 포트 80에서 HTTP 제공 중... 192.168.65.1 - - [31/8월/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [2024년 8월 31일 :21:34:26 +0000] "GET / 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 " -" "모질라/5.0 ..."
도커허브에서
> 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 0.0.0.0 포트 80에서 HTTP 제공 중 ... 192.168.65.1 - - [31/8월/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [2024년 8월 31일 :21:34:26 +0000] "GET / 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 " -" "모질라/5.0 ..."
g++ 4.8 이하는 버전의 <regex>
손상되어 이 라이브러리를 빌드할 수 없습니다.
Windows.h
앞에 httplib.h
포함하거나 WIN32_LEAN_AND_MEAN
미리 정의하여 Windows.h
포함합니다.
#include <httplib.h>#include <Windows.h>
#define WIN32_LEAN_AND_MEAN#include <Windows.h>#include <httplib.h>
메모
cpp-httplib는 공식적으로 최신 Visual Studio만 지원합니다. 이전 버전의 Visual Studio에서도 작동할 수 있지만 더 이상 확인할 수 없습니다. C++11 규격을 위반하지 않는 한 이전 버전의 Visual Studio에 대한 끌어오기 요청은 항상 환영합니다.
메모
Windows 8 이하, Visual Studio 2013 이하, MinGW를 포함한 Cygwin 및 MSYS2는 지원되거나 테스트되지 않습니다.
MIT 라이센스(© 2024 Yuji Hirose)
이 사람들은 이 라이브러리를 단순한 장난감에서 완전히 다른 수준으로 발전시키는 데 큰 공헌을 했습니다!