From 61e533ddc5c60afe643e6fcdd3a1e345121e317f Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 10 Apr 2026 18:16:02 -0400 Subject: [PATCH] Add Cookbook C01-C19 (draft) --- .../en/cookbook/c01-get-response-body.md | 60 ++++++++++ docs-src/pages/en/cookbook/c02-json.md | 36 ++++++ .../pages/en/cookbook/c03-default-headers.md | 55 +++++++++ .../pages/en/cookbook/c04-follow-location.md | 38 ++++++ docs-src/pages/en/cookbook/c05-basic-auth.md | 46 ++++++++ .../pages/en/cookbook/c06-bearer-token.md | 50 ++++++++ .../pages/en/cookbook/c07-multipart-upload.md | 52 +++++++++ .../pages/en/cookbook/c08-post-file-body.md | 34 ++++++ .../pages/en/cookbook/c09-chunked-upload.md | 47 ++++++++ .../pages/en/cookbook/c10-stream-response.md | 52 +++++++++ .../en/cookbook/c11-progress-callback.md | 59 ++++++++++ docs-src/pages/en/cookbook/c12-timeouts.md | 50 ++++++++ docs-src/pages/en/cookbook/c13-max-timeout.md | 42 +++++++ docs-src/pages/en/cookbook/c14-keep-alive.md | 53 +++++++++ docs-src/pages/en/cookbook/c15-compression.md | 47 ++++++++ docs-src/pages/en/cookbook/c16-proxy.md | 52 +++++++++ docs-src/pages/en/cookbook/c17-error-codes.md | 63 ++++++++++ docs-src/pages/en/cookbook/c18-ssl-errors.md | 53 +++++++++ .../pages/en/cookbook/c19-client-logger.md | 57 +++++++++ docs-src/pages/en/cookbook/index.md | 110 +++++++++--------- .../ja/cookbook/c01-get-response-body.md | 60 ++++++++++ docs-src/pages/ja/cookbook/c02-json.md | 36 ++++++ .../pages/ja/cookbook/c03-default-headers.md | 55 +++++++++ .../pages/ja/cookbook/c04-follow-location.md | 38 ++++++ docs-src/pages/ja/cookbook/c05-basic-auth.md | 46 ++++++++ .../pages/ja/cookbook/c06-bearer-token.md | 50 ++++++++ .../pages/ja/cookbook/c07-multipart-upload.md | 52 +++++++++ .../pages/ja/cookbook/c08-post-file-body.md | 34 ++++++ .../pages/ja/cookbook/c09-chunked-upload.md | 47 ++++++++ .../pages/ja/cookbook/c10-stream-response.md | 52 +++++++++ .../ja/cookbook/c11-progress-callback.md | 59 ++++++++++ docs-src/pages/ja/cookbook/c12-timeouts.md | 50 ++++++++ docs-src/pages/ja/cookbook/c13-max-timeout.md | 42 +++++++ docs-src/pages/ja/cookbook/c14-keep-alive.md | 53 +++++++++ docs-src/pages/ja/cookbook/c15-compression.md | 47 ++++++++ docs-src/pages/ja/cookbook/c16-proxy.md | 52 +++++++++ docs-src/pages/ja/cookbook/c17-error-codes.md | 63 ++++++++++ docs-src/pages/ja/cookbook/c18-ssl-errors.md | 53 +++++++++ .../pages/ja/cookbook/c19-client-logger.md | 57 +++++++++ docs-src/pages/ja/cookbook/index.md | 110 +++++++++--------- 40 files changed, 2002 insertions(+), 110 deletions(-) create mode 100644 docs-src/pages/en/cookbook/c01-get-response-body.md create mode 100644 docs-src/pages/en/cookbook/c02-json.md create mode 100644 docs-src/pages/en/cookbook/c03-default-headers.md create mode 100644 docs-src/pages/en/cookbook/c04-follow-location.md create mode 100644 docs-src/pages/en/cookbook/c05-basic-auth.md create mode 100644 docs-src/pages/en/cookbook/c06-bearer-token.md create mode 100644 docs-src/pages/en/cookbook/c07-multipart-upload.md create mode 100644 docs-src/pages/en/cookbook/c08-post-file-body.md create mode 100644 docs-src/pages/en/cookbook/c09-chunked-upload.md create mode 100644 docs-src/pages/en/cookbook/c10-stream-response.md create mode 100644 docs-src/pages/en/cookbook/c11-progress-callback.md create mode 100644 docs-src/pages/en/cookbook/c12-timeouts.md create mode 100644 docs-src/pages/en/cookbook/c13-max-timeout.md create mode 100644 docs-src/pages/en/cookbook/c14-keep-alive.md create mode 100644 docs-src/pages/en/cookbook/c15-compression.md create mode 100644 docs-src/pages/en/cookbook/c16-proxy.md create mode 100644 docs-src/pages/en/cookbook/c17-error-codes.md create mode 100644 docs-src/pages/en/cookbook/c18-ssl-errors.md create mode 100644 docs-src/pages/en/cookbook/c19-client-logger.md create mode 100644 docs-src/pages/ja/cookbook/c01-get-response-body.md create mode 100644 docs-src/pages/ja/cookbook/c02-json.md create mode 100644 docs-src/pages/ja/cookbook/c03-default-headers.md create mode 100644 docs-src/pages/ja/cookbook/c04-follow-location.md create mode 100644 docs-src/pages/ja/cookbook/c05-basic-auth.md create mode 100644 docs-src/pages/ja/cookbook/c06-bearer-token.md create mode 100644 docs-src/pages/ja/cookbook/c07-multipart-upload.md create mode 100644 docs-src/pages/ja/cookbook/c08-post-file-body.md create mode 100644 docs-src/pages/ja/cookbook/c09-chunked-upload.md create mode 100644 docs-src/pages/ja/cookbook/c10-stream-response.md create mode 100644 docs-src/pages/ja/cookbook/c11-progress-callback.md create mode 100644 docs-src/pages/ja/cookbook/c12-timeouts.md create mode 100644 docs-src/pages/ja/cookbook/c13-max-timeout.md create mode 100644 docs-src/pages/ja/cookbook/c14-keep-alive.md create mode 100644 docs-src/pages/ja/cookbook/c15-compression.md create mode 100644 docs-src/pages/ja/cookbook/c16-proxy.md create mode 100644 docs-src/pages/ja/cookbook/c17-error-codes.md create mode 100644 docs-src/pages/ja/cookbook/c18-ssl-errors.md create mode 100644 docs-src/pages/ja/cookbook/c19-client-logger.md diff --git a/docs-src/pages/en/cookbook/c01-get-response-body.md b/docs-src/pages/en/cookbook/c01-get-response-body.md new file mode 100644 index 0000000..ae1f71b --- /dev/null +++ b/docs-src/pages/en/cookbook/c01-get-response-body.md @@ -0,0 +1,60 @@ +--- +title: "C01. Get the Response Body / Save to a File" +order: 1 +status: "draft" +--- + +## Get it as a string + +```cpp +httplib::Client cli("http://localhost:8080"); +auto res = cli.Get("/hello"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +`res->body` is a `std::string`, ready to use as-is. The entire response is loaded into memory. + +> **Warning:** If you fetch a large file with `res->body`, it all goes into memory. For large downloads, use a `ContentReceiver` as shown below. + +## Save to a file + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::ofstream ofs("output.bin", std::ios::binary); +if (!ofs) { + std::cerr << "Failed to open file" << std::endl; + return 1; +} + +auto res = cli.Get("/large-file", + [&](const char *data, size_t len) { + ofs.write(data, len); + return static_cast(ofs); + }); +``` + +With a `ContentReceiver`, data arrives in chunks. You can write each chunk straight to disk without buffering the whole body in memory — perfect for large file downloads. + +Return `false` from the callback to abort the download. In the example above, if writing to `ofs` fails, the download stops automatically. + +> **Detail:** Want to check response headers like Content-Length before downloading? Combine a `ResponseHandler` with a `ContentReceiver`. +> +> ```cpp +> auto res = cli.Get("/large-file", +> [](const httplib::Response &res) { +> auto len = res.get_header_value("Content-Length"); +> std::cout << "Size: " << len << std::endl; +> return true; // return false to skip the download +> }, +> [&](const char *data, size_t len) { +> ofs.write(data, len); +> return static_cast(ofs); +> }); +> ``` +> +> The `ResponseHandler` is called after headers arrive but before the body. Return `false` to skip the download entirely. + +> To show download progress, see C11. Use the progress callback. diff --git a/docs-src/pages/en/cookbook/c02-json.md b/docs-src/pages/en/cookbook/c02-json.md new file mode 100644 index 0000000..d92ec90 --- /dev/null +++ b/docs-src/pages/en/cookbook/c02-json.md @@ -0,0 +1,36 @@ +--- +title: "C02. Send and Receive JSON" +order: 2 +status: "draft" +--- + +cpp-httplib doesn't include a JSON parser. Use a library like [nlohmann/json](https://github.com/nlohmann/json) to build and parse JSON. The examples here use `nlohmann/json`. + +## Send JSON + +```cpp +httplib::Client cli("http://localhost:8080"); + +nlohmann::json j = {{"name", "Alice"}, {"age", 30}}; +auto res = cli.Post("/api/users", j.dump(), "application/json"); +``` + +Pass the JSON string as the second argument to `Post()` and the Content-Type as the third. The same pattern works with `Put()` and `Patch()`. + +> **Warning:** If you omit the Content-Type (the third argument), the server may not recognize the body as JSON. Always specify `"application/json"`. + +## Receive a JSON response + +```cpp +auto res = cli.Get("/api/users/1"); +if (res && res->status == 200) { + auto j = nlohmann::json::parse(res->body); + std::cout << j["name"] << std::endl; +} +``` + +`res->body` is a `std::string`, so you can pass it straight to your JSON library. + +> **Note:** Servers sometimes return HTML on errors. Check the status code before parsing to be safe. Some APIs also require an `Accept: application/json` header. If you're calling a JSON API repeatedly, C03. Set default headers can save you some boilerplate. + +> For how to receive and return JSON on the server side, see S02. Receive JSON requests and return JSON responses. diff --git a/docs-src/pages/en/cookbook/c03-default-headers.md b/docs-src/pages/en/cookbook/c03-default-headers.md new file mode 100644 index 0000000..b6017d0 --- /dev/null +++ b/docs-src/pages/en/cookbook/c03-default-headers.md @@ -0,0 +1,55 @@ +--- +title: "C03. Set Default Headers" +order: 3 +status: "draft" +--- + +When you want the same headers on every request, use `set_default_headers()`. Once set, they're attached automatically to every request sent from that client. + +## Basic usage + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_default_headers({ + {"Accept", "application/json"}, + {"User-Agent", "my-app/1.0"}, +}); + +auto res = cli.Get("/users"); +``` + +Register the headers you need on every API call — like `Accept` or `User-Agent` — in one place. No need to repeat them on each request. + +## Send a Bearer token on every request + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_default_headers({ + {"Authorization", "Bearer " + token}, + {"Accept", "application/json"}, +}); + +auto res1 = cli.Get("/me"); +auto res2 = cli.Get("/projects"); +``` + +Set the auth token once, and every subsequent request carries it. Handy when you're writing an API client that hits multiple endpoints. + +> **Note:** `set_default_headers()` **replaces** the existing default headers. Even if you only want to add one, pass the full set again. + +## Combine with per-request headers + +You can still pass extra headers on individual requests, even with defaults set. + +```cpp +httplib::Headers headers = { + {"X-Request-ID", "abc-123"}, +}; +auto res = cli.Get("/users", headers); +``` + +Per-request headers are **added** on top of the defaults. Both are sent to the server. + +> For details on Bearer token auth, see C06. Call an API with a Bearer Token. diff --git a/docs-src/pages/en/cookbook/c04-follow-location.md b/docs-src/pages/en/cookbook/c04-follow-location.md new file mode 100644 index 0000000..2ee262e --- /dev/null +++ b/docs-src/pages/en/cookbook/c04-follow-location.md @@ -0,0 +1,38 @@ +--- +title: "C04. Follow Redirects" +order: 4 +status: "draft" +--- + +By default, cpp-httplib does not follow HTTP redirects (3xx). If the server returns `302 Found`, you'll get it as a response with status code 302 — nothing more. + +To follow redirects automatically, call `set_follow_location(true)`. + +## Follow redirects + +```cpp +httplib::Client cli("http://example.com"); +cli.set_follow_location(true); + +auto res = cli.Get("/old-path"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +With `set_follow_location(true)`, the client reads the `Location` header and reissues the request to the new URL automatically. The final response ends up in `res`. + +## Redirects from HTTP to HTTPS + +```cpp +httplib::Client cli("http://example.com"); +cli.set_follow_location(true); + +auto res = cli.Get("/"); +``` + +Many sites redirect HTTP traffic to HTTPS. With `set_follow_location(true)` on, this case is handled transparently — the client follows redirects even when the scheme or host changes. + +> **Warning:** To follow redirects to HTTPS, you need to build cpp-httplib with OpenSSL (or another TLS backend). Without TLS support, redirects to HTTPS will fail. + +> **Note:** Following redirects adds to the total request time. See C12. Set Timeouts for timeout configuration. diff --git a/docs-src/pages/en/cookbook/c05-basic-auth.md b/docs-src/pages/en/cookbook/c05-basic-auth.md new file mode 100644 index 0000000..09d08ef --- /dev/null +++ b/docs-src/pages/en/cookbook/c05-basic-auth.md @@ -0,0 +1,46 @@ +--- +title: "C05. Use Basic Authentication" +order: 5 +status: "draft" +--- + +For endpoints that require Basic authentication, pass the username and password to `set_basic_auth()`. cpp-httplib builds the `Authorization: Basic ...` header for you. + +## Basic usage + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_basic_auth("alice", "s3cret"); + +auto res = cli.Get("/private"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +Set it once, and every request from that client carries the credentials. No need to build the header each time. + +## Per-request usage + +If you want credentials on only one specific request, pass headers directly. + +```cpp +httplib::Headers headers = { + httplib::make_basic_authentication_header("alice", "s3cret"), +}; +auto res = cli.Get("/private", headers); +``` + +`make_basic_authentication_header()` builds the Base64-encoded header for you. + +> **Warning:** Basic authentication **encodes** credentials in Base64 — it does not encrypt them. Always use it over HTTPS. Over plain HTTP, your password travels the network in the clear. + +## Digest authentication + +For the more secure Digest authentication scheme, use `set_digest_auth()`. This is only available when cpp-httplib is built with OpenSSL (or another TLS backend). + +```cpp +cli.set_digest_auth("alice", "s3cret"); +``` + +> To call an API with a Bearer token, see C06. Call an API with a Bearer Token. diff --git a/docs-src/pages/en/cookbook/c06-bearer-token.md b/docs-src/pages/en/cookbook/c06-bearer-token.md new file mode 100644 index 0000000..09edb38 --- /dev/null +++ b/docs-src/pages/en/cookbook/c06-bearer-token.md @@ -0,0 +1,50 @@ +--- +title: "C06. Call an API with a Bearer Token" +order: 6 +status: "draft" +--- + +For Bearer token authentication — common in OAuth 2.0 and modern Web APIs — use `set_bearer_token_auth()`. Pass the token and cpp-httplib builds the `Authorization: Bearer ` header for you. + +## Basic usage + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_bearer_token_auth("eyJhbGciOiJIUzI1NiIs..."); + +auto res = cli.Get("/me"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +Set it once and every subsequent request carries the token. This is the go-to pattern for token-based APIs like GitHub, Slack, or your own OAuth service. + +## Per-request usage + +When you want the token on only one request — or need a different token per request — pass it via headers. + +```cpp +httplib::Headers headers = { + httplib::make_bearer_token_authentication_header(token), +}; +auto res = cli.Get("/me", headers); +``` + +`make_bearer_token_authentication_header()` builds the `Authorization` header for you. + +## Refresh the token + +When a token expires, just call `set_bearer_token_auth()` again with the new one. + +```cpp +if (res && res->status == 401) { + auto new_token = refresh_token(); + cli.set_bearer_token_auth(new_token); + res = cli.Get("/me"); +} +``` + +> **Warning:** A Bearer token is itself a credential. Always send it over HTTPS, and never hard-code it into source or config files. + +> To set multiple headers at once, see C03. Set Default Headers. diff --git a/docs-src/pages/en/cookbook/c07-multipart-upload.md b/docs-src/pages/en/cookbook/c07-multipart-upload.md new file mode 100644 index 0000000..4f6f8f5 --- /dev/null +++ b/docs-src/pages/en/cookbook/c07-multipart-upload.md @@ -0,0 +1,52 @@ +--- +title: "C07. Upload a File as Multipart Form Data" +order: 7 +status: "draft" +--- + +When you want to send a file the same way an HTML `` does, use multipart form data (`multipart/form-data`). cpp-httplib offers two APIs — `UploadFormDataItems` and `FormDataProviderItems` — and you pick between them based on **file size**. + +## Send a small file + +Read the file into memory first, then send it. For small files, this is the simplest path. + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::ifstream ifs("avatar.png", std::ios::binary); +std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + +httplib::UploadFormDataItems items = { + {"name", "Alice", "", ""}, + {"avatar", content, "avatar.png", "image/png"}, +}; + +auto res = cli.Post("/upload", items); +``` + +Each `UploadFormData` entry is `{name, content, filename, content_type}`. For plain text fields, leave `filename` and `content_type` empty. + +## Stream a large file + +To avoid loading the whole file into memory, use `make_file_provider()`. It reads the file in chunks as it sends — so even huge files won't blow up your memory footprint. + +```cpp +httplib::Client cli("http://localhost:8080"); + +httplib::UploadFormDataItems items = { + {"name", "Alice", "", ""}, +}; + +httplib::FormDataProviderItems provider_items = { + httplib::make_file_provider("video", "large-video.mp4", "", "video/mp4"), +}; + +auto res = cli.Post("/upload", httplib::Headers{}, items, provider_items); +``` + +The arguments to `make_file_provider()` are `(form name, file path, file name, content type)`. Leave the file name empty to use the file path as-is. + +> **Note:** You can mix `UploadFormDataItems` and `FormDataProviderItems` in the same request. A clean split is: text fields in `UploadFormDataItems`, files in `FormDataProviderItems`. + +> To show upload progress, see C11. Use the progress callback. diff --git a/docs-src/pages/en/cookbook/c08-post-file-body.md b/docs-src/pages/en/cookbook/c08-post-file-body.md new file mode 100644 index 0000000..05cca98 --- /dev/null +++ b/docs-src/pages/en/cookbook/c08-post-file-body.md @@ -0,0 +1,34 @@ +--- +title: "C08. POST a File as Raw Binary" +order: 8 +status: "draft" +--- + +Sometimes you want to send a file's contents as the request body directly — no multipart wrapping. This is common for S3-compatible APIs or endpoints that take raw image data. For this, use `make_file_body()`. + +## Basic usage + +```cpp +httplib::Client cli("https://storage.example.com"); + +auto [size, provider] = httplib::make_file_body("backup.tar.gz"); +if (size == 0) { + std::cerr << "Failed to open file" << std::endl; + return 1; +} + +auto res = cli.Put("/bucket/backup.tar.gz", size, + provider, "application/gzip"); +``` + +`make_file_body()` returns a pair of file size and a `ContentProvider`. Pass them to `Post()` or `Put()` and the file contents flow straight into the request body. + +The `ContentProvider` reads the file in chunks, so even huge files never sit fully in memory. + +## When the file can't be opened + +If the file can't be opened, `make_file_body()` returns `size` as `0` and `provider` as an empty function object. Sending that would produce garbage — always check `size` first. + +> **Warning:** `make_file_body()` needs to fix the Content-Length up front, so it reads the file size ahead of time. If the file size might change mid-upload, this API isn't the right fit. + +> To send the file as multipart form data instead, see C07. Upload a File as Multipart Form Data. diff --git a/docs-src/pages/en/cookbook/c09-chunked-upload.md b/docs-src/pages/en/cookbook/c09-chunked-upload.md new file mode 100644 index 0000000..01aa966 --- /dev/null +++ b/docs-src/pages/en/cookbook/c09-chunked-upload.md @@ -0,0 +1,47 @@ +--- +title: "C09. Send the Body with Chunked Transfer" +order: 9 +status: "draft" +--- + +When you don't know the body size up front — for data generated on the fly or piped from another stream — use `ContentProviderWithoutLength`. The client sends the body with HTTP chunked transfer encoding. + +## Basic usage + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Post("/stream", + [&](size_t offset, httplib::DataSink &sink) { + std::string chunk = produce_next_chunk(); + if (chunk.empty()) { + sink.done(); // done sending + return true; + } + return sink.write(chunk.data(), chunk.size()); + }, + "application/octet-stream"); +``` + +The lambda's job is just: produce the next chunk and send it with `sink.write()`. When there's no more data, call `sink.done()` and you're finished. + +## When the size is known + +If you **do** know the total size ahead of time, use the `ContentProvider` overload (taking `size_t offset, size_t length, DataSink &sink`) and pass the total size as well. + +```cpp +size_t total_size = get_total_size(); + +auto res = cli.Post("/upload", total_size, + [&](size_t offset, size_t length, httplib::DataSink &sink) { + auto data = read_range(offset, length); + return sink.write(data.data(), data.size()); + }, + "application/octet-stream"); +``` + +With a known size, the request carries a Content-Length header — so the server can show progress. Prefer this form when you can. + +> **Detail:** `sink.write()` returns a `bool` indicating whether the write succeeded. If it returns `false`, the connection is gone — return `false` from the lambda to stop. + +> If you're just sending a file, `make_file_body()` is easier. See C08. POST a File as Raw Binary. diff --git a/docs-src/pages/en/cookbook/c10-stream-response.md b/docs-src/pages/en/cookbook/c10-stream-response.md new file mode 100644 index 0000000..c91d6e8 --- /dev/null +++ b/docs-src/pages/en/cookbook/c10-stream-response.md @@ -0,0 +1,52 @@ +--- +title: "C10. Receive a Response as a Stream" +order: 10 +status: "draft" +--- + +To receive a response body chunk by chunk, use a `ContentReceiver`. It's the obvious choice for large files, but it's equally handy for NDJSON (newline-delimited JSON) or log streams where you want to start processing data as it arrives. + +## Process each chunk + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Get("/logs/stream", + [](const char *data, size_t len) { + std::cout.write(data, len); + std::cout.flush(); + return true; // return false to stop receiving + }); +``` + +Data arrives in the lambda in the order it's received from the server. Return `false` from the callback to stop the download partway through. + +## Parse NDJSON line by line + +Here's a buffered approach for processing newline-delimited JSON one line at a time. + +```cpp +std::string buffer; + +auto res = cli.Get("/events", + [&](const char *data, size_t len) { + buffer.append(data, len); + size_t pos; + while ((pos = buffer.find('\n')) != std::string::npos) { + auto line = buffer.substr(0, pos); + buffer.erase(0, pos + 1); + if (!line.empty()) { + auto j = nlohmann::json::parse(line); + handle_event(j); + } + } + return true; + }); +``` + +Accumulate into a buffer, then pull out and parse one line each time you see a newline. This is the standard pattern for consuming a streaming API in real time. + +> **Warning:** When you pass a `ContentReceiver`, `res->body` stays **empty**. Store or process the body inside the callback yourself. + +> To track download progress, combine this with C11. Use the Progress Callback. +> For Server-Sent Events (SSE), see E04. Receive SSE on the Client. diff --git a/docs-src/pages/en/cookbook/c11-progress-callback.md b/docs-src/pages/en/cookbook/c11-progress-callback.md new file mode 100644 index 0000000..0b6adb6 --- /dev/null +++ b/docs-src/pages/en/cookbook/c11-progress-callback.md @@ -0,0 +1,59 @@ +--- +title: "C11. Use the Progress Callback" +order: 11 +status: "draft" +--- + +To display download or upload progress, pass a `DownloadProgress` or `UploadProgress` callback. Both take two arguments: `(current, total)`. + +## Download progress + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Get("/large-file", + [](size_t current, size_t total) { + auto percent = (total > 0) ? (current * 100 / total) : 0; + std::cout << "\rDownloading: " << percent << "% (" + << current << "/" << total << ")" << std::flush; + return true; // return false to abort + }); +std::cout << std::endl; +``` + +The callback fires each time data arrives. `total` comes from the Content-Length header — if the server doesn't send one, it may be `0`. In that case, you can't compute a percentage, so just display bytes received. + +## Upload progress + +Uploads work the same way. Pass an `UploadProgress` as the last argument to `Post()` or `Put()`. + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::string body = load_large_body(); + +auto res = cli.Post("/upload", body, "application/octet-stream", + [](size_t current, size_t total) { + auto percent = current * 100 / total; + std::cout << "\rUploading: " << percent << "%" << std::flush; + return true; + }); +std::cout << std::endl; +``` + +## Cancel mid-transfer + +Return `false` from the callback to abort the transfer. This is how you wire up a "Cancel" button in a UI — flip a flag, and the next progress tick stops the transfer. + +```cpp +std::atomic cancelled{false}; + +auto res = cli.Get("/large-file", + [&](size_t current, size_t total) { + return !cancelled.load(); + }); +``` + +> **Note:** `ContentReceiver` and the progress callback can be used together. When you want to stream to a file and show progress at the same time, pass both. + +> For a concrete example of saving to a file, see C01. Get the Response Body / Save to a File. diff --git a/docs-src/pages/en/cookbook/c12-timeouts.md b/docs-src/pages/en/cookbook/c12-timeouts.md new file mode 100644 index 0000000..ab3aae4 --- /dev/null +++ b/docs-src/pages/en/cookbook/c12-timeouts.md @@ -0,0 +1,50 @@ +--- +title: "C12. Set Timeouts" +order: 12 +status: "draft" +--- + +The client has three kinds of timeouts, each set independently. + +| Kind | API | Default | Meaning | +| --- | --- | --- | --- | +| Connection | `set_connection_timeout` | 300s | Time to wait for the TCP connection to establish | +| Read | `set_read_timeout` | 300s | Time to wait for a single `recv` when receiving the response | +| Write | `set_write_timeout` | 5s | Time to wait for a single `send` when sending the request | + +## Basic usage + +```cpp +httplib::Client cli("http://localhost:8080"); + +cli.set_connection_timeout(5, 0); // 5 seconds +cli.set_read_timeout(10, 0); // 10 seconds +cli.set_write_timeout(10, 0); // 10 seconds + +auto res = cli.Get("/api/data"); +``` + +Pass seconds and microseconds as two arguments. If you don't need the sub-second part, you can omit the second argument. + +## Use `std::chrono` + +There's also an overload that takes a `std::chrono` duration directly. It's easier to read — recommended. + +```cpp +using namespace std::chrono_literals; + +cli.set_connection_timeout(5s); +cli.set_read_timeout(10s); +cli.set_write_timeout(500ms); +``` + +## Watch out for the long 300s default + +Connection and read timeouts default to **300 seconds (5 minutes)**. If the server hangs, you'll be waiting five minutes by default. Shorter values are usually a better idea. + +```cpp +cli.set_connection_timeout(3s); +cli.set_read_timeout(10s); +``` + +> **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use C13. Set an Overall Timeout. diff --git a/docs-src/pages/en/cookbook/c13-max-timeout.md b/docs-src/pages/en/cookbook/c13-max-timeout.md new file mode 100644 index 0000000..dc6ef8b --- /dev/null +++ b/docs-src/pages/en/cookbook/c13-max-timeout.md @@ -0,0 +1,42 @@ +--- +title: "C13. Set an Overall Timeout" +order: 13 +status: "draft" +--- + +The three timeouts from C12. Set Timeouts all apply to a single `send` or `recv` call. To cap the total time a request can take, use `set_max_timeout()`. + +## Basic usage + +```cpp +httplib::Client cli("http://localhost:8080"); + +cli.set_max_timeout(5000); // 5 seconds (in milliseconds) + +auto res = cli.Get("/slow-endpoint"); +``` + +The value is in milliseconds. Connection, send, and receive together — the whole request is aborted if it exceeds the limit. + +## Use `std::chrono` + +There's also an overload that takes a `std::chrono` duration. + +```cpp +using namespace std::chrono_literals; +cli.set_max_timeout(5s); +``` + +## When to use which + +`set_read_timeout` fires when no data arrives for a while. If data keeps trickling in bit by bit, it will never fire. An endpoint that sends one byte per second can make `set_read_timeout` useless no matter how short you set it. + +`set_max_timeout` caps elapsed time, so it handles those cases cleanly. It's great for calls to external APIs or anywhere you don't want users waiting forever. + +```cpp +cli.set_connection_timeout(3s); +cli.set_read_timeout(10s); +cli.set_max_timeout(30s); // abort if the whole request takes over 30s +``` + +> **Note:** `set_max_timeout()` works alongside the regular timeouts. Short stalls get caught by `set_read_timeout`; long-running requests get capped by `set_max_timeout`. Use both for a safety net. diff --git a/docs-src/pages/en/cookbook/c14-keep-alive.md b/docs-src/pages/en/cookbook/c14-keep-alive.md new file mode 100644 index 0000000..9a35b8d --- /dev/null +++ b/docs-src/pages/en/cookbook/c14-keep-alive.md @@ -0,0 +1,53 @@ +--- +title: "C14. Understand Connection Reuse and Keep-Alive" +order: 14 +status: "draft" +--- + +When you send multiple requests through the same `httplib::Client` instance, the TCP connection is reused automatically. HTTP/1.1 Keep-Alive does the work for you — you don't pay the TCP and TLS handshake cost on every call. + +## Connections are reused automatically + +```cpp +httplib::Client cli("https://api.example.com"); + +auto res1 = cli.Get("/users/1"); +auto res2 = cli.Get("/users/2"); // reuses the same connection +auto res3 = cli.Get("/users/3"); // reuses the same connection +``` + +No special config required. Just hold on to `cli` — internally, the socket stays open across calls. The effect is especially noticeable over HTTPS, where the TLS handshake is expensive. + +## Disable Keep-Alive explicitly + +To force a fresh connection every time, call `set_keep_alive(false)`. Mostly useful for testing. + +```cpp +cli.set_keep_alive(false); +``` + +For normal use, leave it on (the default). + +## Don't create a `Client` per request + +If you create a `Client` inside a loop and let it fall out of scope each iteration, you lose the reuse benefit. Create the instance outside the loop. + +```cpp +// Bad: a new connection every iteration +for (auto id : ids) { + httplib::Client cli("https://api.example.com"); + cli.Get("/users/" + id); +} + +// Good: the connection is reused +httplib::Client cli("https://api.example.com"); +for (auto id : ids) { + cli.Get("/users/" + id); +} +``` + +## Concurrent requests + +If you want to send requests in parallel from multiple threads, give each thread its own `Client` instance. A single `Client` uses a single TCP connection, so firing concurrent requests at the same instance ends up serializing them anyway. + +> **Note:** If the server closes the connection after its Keep-Alive timeout, cpp-httplib reconnects and retries transparently. You don't need to handle this in application code. diff --git a/docs-src/pages/en/cookbook/c15-compression.md b/docs-src/pages/en/cookbook/c15-compression.md new file mode 100644 index 0000000..8933ac2 --- /dev/null +++ b/docs-src/pages/en/cookbook/c15-compression.md @@ -0,0 +1,47 @@ +--- +title: "C15. Enable Compression" +order: 15 +status: "draft" +--- + +cpp-httplib supports compression when sending and decompression when receiving. You just need to build it with zlib or Brotli enabled. + +## Build-time setup + +To use compression, define these macros before including `httplib.h`: + +```cpp +#define CPPHTTPLIB_ZLIB_SUPPORT // gzip / deflate +#define CPPHTTPLIB_BROTLI_SUPPORT // brotli +#include +``` + +You'll also need to link against `zlib` or `brotli`. + +## Compress the request body + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_compress(true); + +std::string big_payload = build_payload(); +auto res = cli.Post("/api/data", big_payload, "application/json"); +``` + +With `set_compress(true)`, the body of POST or PUT requests gets gzipped before sending. The server needs to handle compressed bodies too. + +## Decompress the response + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_decompress(true); // on by default + +auto res = cli.Get("/api/data"); +std::cout << res->body << std::endl; +``` + +With `set_decompress(true)`, the client automatically decompresses responses that arrive with `Content-Encoding: gzip` or similar. `res->body` contains the decompressed data. + +It's on by default, so normally you don't need to do anything. Set it to `false` only if you want the raw compressed bytes. + +> **Warning:** If you build without `CPPHTTPLIB_ZLIB_SUPPORT`, calling `set_compress()` or `set_decompress()` does nothing. If compression isn't working, check the macro definition first. diff --git a/docs-src/pages/en/cookbook/c16-proxy.md b/docs-src/pages/en/cookbook/c16-proxy.md new file mode 100644 index 0000000..a4cc6e3 --- /dev/null +++ b/docs-src/pages/en/cookbook/c16-proxy.md @@ -0,0 +1,52 @@ +--- +title: "C16. Send Requests Through a Proxy" +order: 16 +status: "draft" +--- + +To route traffic through a corporate network or a specific path, send requests via an HTTP proxy. Just pass the proxy host and port to `set_proxy()`. + +## Basic usage + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_proxy("proxy.internal", 8080); + +auto res = cli.Get("/users"); +``` + +The request goes through the proxy. For HTTPS, the client uses the CONNECT method to tunnel through — no extra setup required. + +## Proxy authentication + +If the proxy itself requires authentication, use `set_proxy_basic_auth()` or `set_proxy_bearer_token_auth()`. + +```cpp +cli.set_proxy("proxy.internal", 8080); +cli.set_proxy_basic_auth("user", "password"); +``` + +```cpp +cli.set_proxy_bearer_token_auth("token"); +``` + +If cpp-httplib is built with OpenSSL (or another TLS backend), you can also use Digest authentication for the proxy. + +```cpp +cli.set_proxy_digest_auth("user", "password"); +``` + +## Combine with end-server authentication + +Proxy authentication is separate from authenticating to the end server (C05, C06). When both are needed, set both. + +```cpp +cli.set_proxy("proxy.internal", 8080); +cli.set_proxy_basic_auth("proxy-user", "proxy-pass"); + +cli.set_bearer_token_auth("api-token"); // for the end server +``` + +`Proxy-Authorization` is sent to the proxy, `Authorization` to the end server. + +> **Note:** cpp-httplib does not read `HTTP_PROXY` or `HTTPS_PROXY` environment variables automatically. If you want to honor them, read them in your application and pass the values to `set_proxy()`. diff --git a/docs-src/pages/en/cookbook/c17-error-codes.md b/docs-src/pages/en/cookbook/c17-error-codes.md new file mode 100644 index 0000000..9847244 --- /dev/null +++ b/docs-src/pages/en/cookbook/c17-error-codes.md @@ -0,0 +1,63 @@ +--- +title: "C17. Handle Error Codes" +order: 17 +status: "draft" +--- + +`cli.Get()`, `cli.Post()`, and friends return a `Result`. When the request fails — can't reach the server, times out, etc. — the result is "falsy". To get the specific reason, use `Result::error()`. + +## Basic check + +```cpp +httplib::Client cli("http://localhost:8080"); +auto res = cli.Get("/api/data"); + +if (res) { + // the request was sent and a response came back + std::cout << "status: " << res->status << std::endl; +} else { + // the network layer failed + std::cerr << "error: " << httplib::to_string(res.error()) << std::endl; +} +``` + +Use `if (res)` to check success. On failure, `res.error()` returns a `httplib::Error` enum value. Pass it to `to_string()` to get a human-readable description. + +## Common errors + +| Value | Meaning | +| --- | --- | +| `Error::Connection` | Couldn't connect to the server | +| `Error::ConnectionTimeout` | Connection timeout (`set_connection_timeout`) | +| `Error::Read` / `Error::Write` | Error during send or receive | +| `Error::Timeout` | Overall timeout set via `set_max_timeout` | +| `Error::ExceedRedirectCount` | Too many redirects | +| `Error::SSLConnection` | TLS handshake failed | +| `Error::SSLServerVerification` | Server certificate verification failed | +| `Error::Canceled` | A progress callback returned `false` | + +## Network errors vs. HTTP errors + +Even when `res` is truthy, the HTTP status code can still be 4xx or 5xx. These are two different things. + +```cpp +auto res = cli.Get("/api/data"); +if (!res) { + // network error (no response received at all) + std::cerr << "network error: " << httplib::to_string(res.error()) << std::endl; + return 1; +} + +if (res->status >= 400) { + // HTTP error (response received, but the status is bad) + std::cerr << "http error: " << res->status << std::endl; + return 1; +} + +// success +std::cout << res->body << std::endl; +``` + +Keep them separated in your head: network-layer errors go through `res.error()`, HTTP-level errors through `res->status`. + +> To dig deeper into SSL-related errors, see C18. Handle SSL Errors. diff --git a/docs-src/pages/en/cookbook/c18-ssl-errors.md b/docs-src/pages/en/cookbook/c18-ssl-errors.md new file mode 100644 index 0000000..5a1d8cf --- /dev/null +++ b/docs-src/pages/en/cookbook/c18-ssl-errors.md @@ -0,0 +1,53 @@ +--- +title: "C18. Handle SSL Errors" +order: 18 +status: "draft" +--- + +When an HTTPS request fails, `res.error()` returns values like `Error::SSLConnection` or `Error::SSLServerVerification`. Sometimes that's not enough to pinpoint the cause. That's where `Result::ssl_error()` and `Result::ssl_backend_error()` help. + +## Get the SSL error details + +```cpp +httplib::Client cli("https://api.example.com"); +auto res = cli.Get("/"); + +if (!res) { + auto err = res.error(); + std::cerr << "error: " << httplib::to_string(err) << std::endl; + + if (err == httplib::Error::SSLConnection || + err == httplib::Error::SSLServerVerification) { + std::cerr << "ssl_error: " << res.ssl_error() << std::endl; + std::cerr << "ssl_backend_error: " << res.ssl_backend_error() << std::endl; + } +} +``` + +`ssl_error()` returns the error code from the SSL library (e.g., OpenSSL's `SSL_get_error()`). `ssl_backend_error()` gives you the backend's more detailed error value — for OpenSSL, that's `ERR_get_error()`. + +## Format OpenSSL errors as strings + +When you have a value from `ssl_backend_error()`, pass it to OpenSSL's `ERR_error_string()` to get a readable message. + +```cpp +#include + +if (res.ssl_backend_error() != 0) { + char buf[256]; + ERR_error_string_n(res.ssl_backend_error(), buf, sizeof(buf)); + std::cerr << "openssl: " << buf << std::endl; +} +``` + +## Common causes + +| Symptom | Usual suspect | +| --- | --- | +| `SSLServerVerification` | CA certificate path isn't configured, or the cert is self-signed | +| `SSLServerHostnameVerification` | The cert's CN/SAN doesn't match the host | +| `SSLConnection` | TLS version mismatch, no shared cipher suite | + +> **Note:** `ssl_backend_error()` was previously called `ssl_openssl_error()`. The old name is deprecated — use `ssl_backend_error()` now. + +> To change certificate verification settings, see T02. Control SSL Certificate Verification. diff --git a/docs-src/pages/en/cookbook/c19-client-logger.md b/docs-src/pages/en/cookbook/c19-client-logger.md new file mode 100644 index 0000000..e97d378 --- /dev/null +++ b/docs-src/pages/en/cookbook/c19-client-logger.md @@ -0,0 +1,57 @@ +--- +title: "C19. Set a Logger on the Client" +order: 19 +status: "draft" +--- + +To log requests sent and responses received by the client, use `set_logger()`. If you only care about errors, there's a separate `set_error_logger()`. + +## Log requests and responses + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_logger([](const httplib::Request &req, const httplib::Response &res) { + std::cout << req.method << " " << req.path + << " -> " << res.status << std::endl; +}); + +auto res = cli.Get("/users"); +``` + +The callback you pass to `set_logger()` fires once for each completed request. You get both the request and the response as arguments — so you can log the method, path, status, headers, body, or whatever else you need. + +## Catch errors only + +When a network-layer error happens (like `Error::Connection`), `set_logger()` is **not** called — there's no response to log. For those cases, use `set_error_logger()`. + +```cpp +cli.set_error_logger([](const httplib::Error &err, const httplib::Request *req) { + std::cerr << "error: " << httplib::to_string(err); + if (req) { + std::cerr << " (" << req->method << " " << req->path << ")"; + } + std::cerr << std::endl; +}); +``` + +The second argument `req` can be null — it happens when the failure occurred before the request was built. Always null-check before dereferencing. + +## Use both together + +A nice pattern is to log successes through one, failures through the other. + +```cpp +cli.set_logger([](const auto &req, const auto &res) { + std::cout << "[ok] " << req.method << " " << req.path + << " " << res.status << std::endl; +}); + +cli.set_error_logger([](const auto &err, const auto *req) { + std::cerr << "[ng] " << httplib::to_string(err); + if (req) std::cerr << " " << req->method << " " << req->path; + std::cerr << std::endl; +}); +``` + +> **Note:** The log callbacks run synchronously on the same thread as the request. Heavy work inside them slows the request down — push it to a background queue if you need to do anything expensive. diff --git a/docs-src/pages/en/cookbook/index.md b/docs-src/pages/en/cookbook/index.md index e28efbf..b7cd8ec 100644 --- a/docs-src/pages/en/cookbook/index.md +++ b/docs-src/pages/en/cookbook/index.md @@ -4,93 +4,93 @@ order: 0 status: "draft" --- -A collection of recipes that answer "How do I...?" questions. Each recipe is self-contained — read only what you need. +A collection of recipes that answer "How do I...?" questions. Each recipe is self-contained — read only what you need. For an introduction to the basics, see the [Tour](../tour/). ## Client ### Basics -- Get the response body as a string / save to a file -- Send and receive JSON -- Set default headers (`set_default_headers`) -- Follow redirects (`set_follow_location`) +- [C01. Get the response body / save to a file](c01-get-response-body) +- [C02. Send and receive JSON](c02-json) +- [C03. Set default headers](c03-default-headers) +- [C04. Follow redirects](c04-follow-location) ### Authentication -- Use Basic authentication (`set_basic_auth`) -- Call an API with a Bearer token +- [C05. Use Basic authentication](c05-basic-auth) +- [C06. Call an API with a Bearer token](c06-bearer-token) ### File Upload -- Upload a file as multipart form data (`make_file_provider`) -- POST a file as raw binary (`make_file_body`) -- Send the body with chunked transfer (Content Provider) +- [C07. Upload a file as multipart form data](c07-multipart-upload) +- [C08. POST a file as raw binary](c08-post-file-body) +- [C09. Send the body with chunked transfer](c09-chunked-upload) ### Streaming & Progress -- Receive a response as a stream -- Use the progress callback (`DownloadProgress` / `UploadProgress`) +- [C10. Receive a response as a stream](c10-stream-response) +- [C11. Use the progress callback](c11-progress-callback) ### Connection & Performance -- Set timeouts (`set_connection_timeout` / `set_read_timeout`) -- Set an overall timeout (`set_max_timeout`) -- Understand connection reuse and Keep-Alive behavior -- Enable compression (`set_compress` / `set_decompress`) -- Send requests through a proxy (`set_proxy`) +- [C12. Set timeouts](c12-timeouts) +- [C13. Set an overall timeout](c13-max-timeout) +- [C14. Understand connection reuse and Keep-Alive behavior](c14-keep-alive) +- [C15. Enable compression](c15-compression) +- [C16. Send requests through a proxy](c16-proxy) ### Error Handling & Debugging -- Handle error codes (`Result::error()`) -- Handle SSL errors (`ssl_error()` / `ssl_backend_error()`) -- Set up client logging (`set_logger` / `set_error_logger`) +- [C17. Handle error codes](c17-error-codes) +- [C18. Handle SSL errors](c18-ssl-errors) +- [C19. Set up client logging](c19-client-logger) ## Server ### Basics -- Register GET / POST / PUT / DELETE handlers -- Receive JSON requests and return JSON responses -- Use path parameters (`/users/:id`) -- Set up a static file server (`set_mount_point`) +- S01. Register GET / POST / PUT / DELETE handlers +- S02. Receive JSON requests and return JSON responses +- S03. Use path parameters (`/users/:id`) +- S04. Set up a static file server (`set_mount_point`) ### Streaming & Files -- Stream a large file in the response (`ContentProvider`) -- Return a file download response (`Content-Disposition`) -- Receive multipart data as a stream (`ContentReader`) -- Compress responses (gzip) +- S05. Stream a large file in the response (`ContentProvider`) +- S06. Return a file download response (`Content-Disposition`) +- S07. Receive multipart data as a stream (`ContentReader`) +- S08. Compress responses (gzip) ### Handler Chain -- Add pre-processing to all routes (Pre-routing handler) -- Add response headers with a Post-routing handler (CORS, etc.) -- Authenticate per route with a Pre-request handler (`matched_route`) -- Pass data between handlers with `res.user_data` +- S09. Add pre-processing to all routes (Pre-routing handler) +- S10. Add response headers with a Post-routing handler (CORS, etc.) +- S11. Authenticate per route with a Pre-request handler (`matched_route`) +- S12. Pass data between handlers with `res.user_data` ### Error Handling & Debugging -- Return custom error pages (`set_error_handler`) -- Catch exceptions (`set_exception_handler`) -- Log requests (Logger) -- Detect client disconnection (`req.is_connection_closed()`) +- S13. Return custom error pages (`set_error_handler`) +- S14. Catch exceptions (`set_exception_handler`) +- S15. Log requests (Logger) +- S16. Detect client disconnection (`req.is_connection_closed()`) ### Operations & Tuning -- Assign a port dynamically (`bind_to_any_port`) -- Control startup order with `listen_after_bind` -- Shut down gracefully (`stop()` and signal handling) -- Tune Keep-Alive (`set_keep_alive_max_count` / `set_keep_alive_timeout`) -- Configure the thread pool (`new_task_queue`) +- S17. Assign a port dynamically (`bind_to_any_port`) +- S18. Control startup order with `listen_after_bind` +- S19. Shut down gracefully (`stop()` and signal handling) +- S20. Tune Keep-Alive (`set_keep_alive_max_count` / `set_keep_alive_timeout`) +- S21. Configure the thread pool (`new_task_queue`) +- S22. Communicate over Unix domain sockets (`set_address_family(AF_UNIX)`) ## TLS / Security -- Choosing between OpenSSL, mbedTLS, and wolfSSL (build-time `#define` differences) -- Control SSL certificate verification (disable, custom CA, custom callback) -- Use a custom certificate verification callback (`set_server_certificate_verifier`) -- Set up an SSL/TLS server (certificate and private key) -- Configure mTLS (mutual TLS with client certificates) -- Access the peer certificate on the server (`req.peer_cert()` / SNI) +- T01. Choosing between OpenSSL, mbedTLS, and wolfSSL (build-time `#define` differences) +- T02. Control SSL certificate verification (disable, custom CA, custom callback) +- T03. Set up an SSL/TLS server (certificate and private key) +- T04. Configure mTLS (mutual TLS with client certificates) +- T05. Access the peer certificate on the server (`req.peer_cert()` / SNI) ## SSE -- Implement an SSE server -- Use event names to distinguish event types -- Handle reconnection (`Last-Event-ID`) -- Receive SSE events on the client +- E01. Implement an SSE server +- E02. Use event names to distinguish event types +- E03. Handle reconnection (`Last-Event-ID`) +- E04. Receive SSE events on the client ## WebSocket -- Implement a WebSocket echo server and client -- Configure heartbeats (`set_websocket_ping_interval` / `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND`) -- Handle connection close -- Send and receive binary frames +- W01. Implement a WebSocket echo server and client +- W02. Configure heartbeats (`set_websocket_ping_interval`) +- W03. Handle connection close +- W04. Send and receive binary frames diff --git a/docs-src/pages/ja/cookbook/c01-get-response-body.md b/docs-src/pages/ja/cookbook/c01-get-response-body.md new file mode 100644 index 0000000..ac7ee85 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c01-get-response-body.md @@ -0,0 +1,60 @@ +--- +title: "C01. レスポンスボディを取得する / ファイルに保存する" +order: 1 +status: "draft" +--- + +## 文字列として取得する + +```cpp +httplib::Client cli("http://localhost:8080"); +auto res = cli.Get("/hello"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +`res->body`は`std::string`なので、そのまま使えます。レスポンス全体がメモリに読み込まれます。 + +> **Warning:** 大きなファイルを`res->body`で受け取ると、まるごとメモリに載ってしまいます。サイズが大きい場合は次の`ContentReceiver`を使いましょう。 + +## ファイルに保存する + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::ofstream ofs("output.bin", std::ios::binary); +if (!ofs) { + std::cerr << "Failed to open file" << std::endl; + return 1; +} + +auto res = cli.Get("/large-file", + [&](const char *data, size_t len) { + ofs.write(data, len); + return static_cast(ofs); + }); +``` + +`ContentReceiver`を使うと、データをチャンクごとに受け取れます。ボディ全体をメモリに溜めずにファイルへ書き出せるので、大きなファイルのダウンロードにぴったりです。 + +コールバックから`false`を返すと、ダウンロードを途中で止められます。上の例では`ofs`への書き込みが失敗したら自動的に中断します。 + +> **Detail:** ダウンロード前にContent-Lengthなどのレスポンスヘッダーを確認したいときは、`ResponseHandler`を組み合わせましょう。 +> +> ```cpp +> auto res = cli.Get("/large-file", +> [](const httplib::Response &res) { +> auto len = res.get_header_value("Content-Length"); +> std::cout << "Size: " << len << std::endl; +> return true; // falseを返すとダウンロードを中止 +> }, +> [&](const char *data, size_t len) { +> ofs.write(data, len); +> return static_cast(ofs); +> }); +> ``` +> +> `ResponseHandler`はヘッダー受信後、ボディ受信前に呼ばれます。`false`を返せばダウンロード自体をスキップできます。 + +> ダウンロードの進捗を表示したい場合はC11. 進捗コールバックを使うを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c02-json.md b/docs-src/pages/ja/cookbook/c02-json.md new file mode 100644 index 0000000..3b18187 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c02-json.md @@ -0,0 +1,36 @@ +--- +title: "C02. JSONを送受信する" +order: 2 +status: "draft" +--- + +cpp-httplibにはJSONパーサーが含まれていません。JSONの組み立てや解析には[nlohmann/json](https://github.com/nlohmann/json)などのライブラリを使ってください。ここでは`nlohmann/json`を例に説明します。 + +## JSONを送信する + +```cpp +httplib::Client cli("http://localhost:8080"); + +nlohmann::json j = {{"name", "Alice"}, {"age", 30}}; +auto res = cli.Post("/api/users", j.dump(), "application/json"); +``` + +`Post()`の第2引数にJSON文字列、第3引数にContent-Typeを渡します。`Put()`や`Patch()`でも同じ形です。 + +> **Warning:** 第3引数のContent-Typeを省略すると、サーバー側でJSONとして認識されないことがあります。`"application/json"`を必ず指定しましょう。 + +## JSONレスポンスを受け取る + +```cpp +auto res = cli.Get("/api/users/1"); +if (res && res->status == 200) { + auto j = nlohmann::json::parse(res->body); + std::cout << j["name"] << std::endl; +} +``` + +`res->body`は`std::string`なので、そのままJSONライブラリに渡せます。 + +> **Note:** サーバーがエラー時にHTMLを返すことがあります。ステータスコードを確認してからパースすると安全です。また、APIによっては`Accept: application/json`ヘッダーが必要です。JSON APIを繰り返し呼ぶならC03. デフォルトヘッダーを設定するが便利です。 + +> サーバー側でJSONを受け取って返す方法はS02. JSONリクエストを受け取りJSONレスポンスを返すを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c03-default-headers.md b/docs-src/pages/ja/cookbook/c03-default-headers.md new file mode 100644 index 0000000..c391f1d --- /dev/null +++ b/docs-src/pages/ja/cookbook/c03-default-headers.md @@ -0,0 +1,55 @@ +--- +title: "C03. デフォルトヘッダーを設定する" +order: 3 +status: "draft" +--- + +同じヘッダーを毎回のリクエストに付けたいときは、`set_default_headers()`を使います。一度設定すれば、そのクライアントから送るすべてのリクエストに自動で付与されます。 + +## 基本の使い方 + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_default_headers({ + {"Accept", "application/json"}, + {"User-Agent", "my-app/1.0"}, +}); + +auto res = cli.Get("/users"); +``` + +`Accept`や`User-Agent`のように、APIを呼ぶたびに必要なヘッダーをまとめて登録できます。リクエストごとに指定する手間が省けます。 + +## Bearerトークンを毎回付ける + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_default_headers({ + {"Authorization", "Bearer " + token}, + {"Accept", "application/json"}, +}); + +auto res1 = cli.Get("/me"); +auto res2 = cli.Get("/projects"); +``` + +認証トークンを一度セットしておけば、以降のリクエストで自動的に送られます。複数のエンドポイントを叩くAPIクライアントを書くときに便利です。 + +> **Note:** `set_default_headers()`は既存のデフォルトヘッダーを**上書き**します。あとから1つだけ追加したい場合でも、全体を渡し直してください。 + +## リクエスト単位のヘッダーと組み合わせる + +デフォルトヘッダーを設定していても、個別のリクエストで追加のヘッダーを渡せます。 + +```cpp +httplib::Headers headers = { + {"X-Request-ID", "abc-123"}, +}; +auto res = cli.Get("/users", headers); +``` + +リクエスト単位で渡したヘッダーはデフォルトヘッダーに**追加**されます。両方がサーバーに送られます。 + +> Bearerトークンを使った認証の詳細はC06. BearerトークンでAPIを呼ぶを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c04-follow-location.md b/docs-src/pages/ja/cookbook/c04-follow-location.md new file mode 100644 index 0000000..6db01c2 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c04-follow-location.md @@ -0,0 +1,38 @@ +--- +title: "C04. リダイレクトを追従する" +order: 4 +status: "draft" +--- + +cpp-httplibはデフォルトではリダイレクト(HTTP 3xx)を追従しません。サーバーから`302 Found`が返ってきても、そのままステータスコード302のレスポンスとして受け取ります。 + +自動で追従してほしいときは、`set_follow_location(true)`を呼びましょう。 + +## リダイレクトを追従する + +```cpp +httplib::Client cli("http://example.com"); +cli.set_follow_location(true); + +auto res = cli.Get("/old-path"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +`set_follow_location(true)`を設定すると、`Location`ヘッダーを見て新しいURLに自動でリクエストを投げ直します。最終的なレスポンスが`res`に入ります。 + +## HTTPからHTTPSへのリダイレクト + +```cpp +httplib::Client cli("http://example.com"); +cli.set_follow_location(true); + +auto res = cli.Get("/"); +``` + +多くのサイトはHTTPアクセスをHTTPSへリダイレクトします。`set_follow_location(true)`を有効にしておけば、こうしたケースも透過的に扱えます。スキームやホストが変わっても自動で追従します。 + +> **Warning:** HTTPSへのリダイレクトを追従するには、cpp-httplibをOpenSSL(または他のTLSバックエンド)付きでビルドしておく必要があります。TLSサポートがないと、HTTPSへのリダイレクトは失敗します。 + +> **Note:** リダイレクトを追従すると、リクエストの実行時間は伸びます。タイムアウトの設定はC12. タイムアウトを設定するを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c05-basic-auth.md b/docs-src/pages/ja/cookbook/c05-basic-auth.md new file mode 100644 index 0000000..aaf05d6 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c05-basic-auth.md @@ -0,0 +1,46 @@ +--- +title: "C05. Basic認証を使う" +order: 5 +status: "draft" +--- + +Basic認証が必要なエンドポイントには、`set_basic_auth()`でユーザー名とパスワードを渡します。cpp-httplibが自動で`Authorization: Basic ...`ヘッダーを組み立ててくれます。 + +## 基本の使い方 + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_basic_auth("alice", "s3cret"); + +auto res = cli.Get("/private"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +一度設定すれば、そのクライアントから送るすべてのリクエストに認証情報が付きます。毎回ヘッダーを組み立てる必要はありません。 + +## リクエスト単位で使う + +特定のリクエストだけに認証情報を付けたいときは、Headersを直接渡す方法もあります。 + +```cpp +httplib::Headers headers = { + httplib::make_basic_authentication_header("alice", "s3cret"), +}; +auto res = cli.Get("/private", headers); +``` + +`make_basic_authentication_header()`がBase64エンコード済みのヘッダーを作ってくれます。 + +> **Warning:** Basic認証は資格情報をBase64で**エンコード**するだけで、暗号化しません。必ずHTTPS経由で使ってください。平文HTTPで使うと、パスワードがネットワーク上を丸見えで流れます。 + +## Digest認証 + +より安全なDigest認証を使いたいときは`set_digest_auth()`を使います。こちらはOpenSSL(または他のTLSバックエンド)付きでビルドしたときだけ利用できます。 + +```cpp +cli.set_digest_auth("alice", "s3cret"); +``` + +> BearerトークンでAPIを呼びたい場合はC06. BearerトークンでAPIを呼ぶを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c06-bearer-token.md b/docs-src/pages/ja/cookbook/c06-bearer-token.md new file mode 100644 index 0000000..8920075 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c06-bearer-token.md @@ -0,0 +1,50 @@ +--- +title: "C06. BearerトークンでAPIを呼ぶ" +order: 6 +status: "draft" +--- + +OAuth 2.0やモダンなWeb APIでよく使われるBearerトークン認証には、`set_bearer_token_auth()`を使います。トークンを渡すと、cpp-httplibが`Authorization: Bearer `ヘッダーを自動で組み立ててくれます。 + +## 基本の使い方 + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_bearer_token_auth("eyJhbGciOiJIUzI1NiIs..."); + +auto res = cli.Get("/me"); +if (res && res->status == 200) { + std::cout << res->body << std::endl; +} +``` + +一度設定すれば、以降のリクエストすべてにトークンが付きます。GitHub APIやSlack API、自前のOAuthサービスなど、トークンベースのAPIを叩くときの定番です。 + +## リクエスト単位で使う + +特定のリクエストだけにトークンを付けたい、あるいはリクエストごとに違うトークンを使いたいときは、Headersで直接渡せます。 + +```cpp +httplib::Headers headers = { + httplib::make_bearer_token_authentication_header(token), +}; +auto res = cli.Get("/me", headers); +``` + +`make_bearer_token_authentication_header()`が`Authorization`ヘッダーを組み立ててくれます。 + +## トークンをリフレッシュする + +トークンの有効期限が切れたら、新しいトークンで`set_bearer_token_auth()`を呼び直すだけで更新できます。 + +```cpp +if (res && res->status == 401) { + auto new_token = refresh_token(); + cli.set_bearer_token_auth(new_token); + res = cli.Get("/me"); +} +``` + +> **Warning:** Bearerトークンはそれ自体が認証情報です。必ずHTTPS経由で送ってください。また、ソースコードや設定ファイルにトークンをハードコードしないようにしましょう。 + +> 複数のヘッダーをまとめて設定したいときはC03. デフォルトヘッダーを設定するも便利です。 diff --git a/docs-src/pages/ja/cookbook/c07-multipart-upload.md b/docs-src/pages/ja/cookbook/c07-multipart-upload.md new file mode 100644 index 0000000..d25a9a9 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c07-multipart-upload.md @@ -0,0 +1,52 @@ +--- +title: "C07. ファイルをマルチパートフォームとしてアップロードする" +order: 7 +status: "draft" +--- + +HTMLフォームの``と同じ形式でファイルを送りたいときは、マルチパートフォーム(`multipart/form-data`)を使います。cpp-httplibは`UploadFormDataItems`と`FormDataProviderItems`の2種類のAPIを用意しています。使い分けの基準は**ファイルサイズ**です。 + +## 小さなファイルを送る + +ファイル内容をメモリに読み込んでから送る方法です。サイズが小さいなら、これが一番シンプルです。 + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::ifstream ifs("avatar.png", std::ios::binary); +std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + +httplib::UploadFormDataItems items = { + {"name", "Alice", "", ""}, + {"avatar", content, "avatar.png", "image/png"}, +}; + +auto res = cli.Post("/upload", items); +``` + +`UploadFormData`の各要素は`{name, content, filename, content_type}`の4つです。テキストフィールドなら`filename`と`content_type`を空文字にしておきます。 + +## 大きなファイルをストリーミングで送る + +ファイル全体をメモリに載せずに、チャンクごとに送りたいときは`make_file_provider()`を使います。内部でファイルを少しずつ読みながら送信するので、巨大なファイルでもメモリを圧迫しません。 + +```cpp +httplib::Client cli("http://localhost:8080"); + +httplib::UploadFormDataItems items = { + {"name", "Alice", "", ""}, +}; + +httplib::FormDataProviderItems provider_items = { + httplib::make_file_provider("video", "large-video.mp4", "", "video/mp4"), +}; + +auto res = cli.Post("/upload", httplib::Headers{}, items, provider_items); +``` + +`make_file_provider()`の引数は`(フォーム名, ファイルパス, ファイル名, Content-Type)`です。ファイル名を空にするとファイルパスがそのまま使われます。 + +> **Note:** `UploadFormDataItems`と`FormDataProviderItems`は同じリクエスト内で併用できます。テキストフィールドは`UploadFormDataItems`、ファイルは`FormDataProviderItems`、という使い分けがきれいです。 + +> アップロードの進捗を表示したい場合はC11. 進捗コールバックを使うを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c08-post-file-body.md b/docs-src/pages/ja/cookbook/c08-post-file-body.md new file mode 100644 index 0000000..074d2c9 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c08-post-file-body.md @@ -0,0 +1,34 @@ +--- +title: "C08. ファイルを生バイナリとしてPOSTする" +order: 8 +status: "draft" +--- + +マルチパートではなく、ファイルの中身をそのままリクエストボディとして送りたいときがあります。S3互換APIへのアップロードや、生の画像データを受け付けるエンドポイントなどです。このときは`make_file_body()`を使いましょう。 + +## 基本の使い方 + +```cpp +httplib::Client cli("https://storage.example.com"); + +auto [size, provider] = httplib::make_file_body("backup.tar.gz"); +if (size == 0) { + std::cerr << "Failed to open file" << std::endl; + return 1; +} + +auto res = cli.Put("/bucket/backup.tar.gz", size, + provider, "application/gzip"); +``` + +`make_file_body()`はファイルサイズと`ContentProvider`のペアを返します。そのまま`Post()`や`Put()`に渡せば、ファイルの中身がリクエストボディとしてそのまま送られます。 + +`ContentProvider`はファイルをチャンクごとに読み込むので、巨大なファイルでもメモリに全体を載せません。 + +## ファイルが開けなかったとき + +`make_file_body()`は開けなかった場合、`size`を`0`、`provider`を空の関数オブジェクトとして返します。そのまま送信するとおかしな結果になるので、必ず`size`をチェックしてください。 + +> **Warning:** `make_file_body()`はContent-Lengthを最初に確定させる必要があるため、ファイルサイズをあらかじめ取得します。送信中にファイルサイズが変わる可能性がある場合は、このAPIには向きません。 + +> マルチパート形式で送りたい場合はC07. ファイルをマルチパートフォームとしてアップロードするを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c09-chunked-upload.md b/docs-src/pages/ja/cookbook/c09-chunked-upload.md new file mode 100644 index 0000000..5157f85 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c09-chunked-upload.md @@ -0,0 +1,47 @@ +--- +title: "C09. チャンク転送でボディを送る" +order: 9 +status: "draft" +--- + +送信するボディのサイズが事前にわからないとき、たとえばリアルタイムに生成されるデータや別のストリームから流し込むデータを送りたいときは、`ContentProviderWithoutLength`を使います。HTTPのチャンク転送エンコーディング(chunked transfer-encoding)として送信されます。 + +## 基本の使い方 + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Post("/stream", + [&](size_t offset, httplib::DataSink &sink) { + std::string chunk = produce_next_chunk(); + if (chunk.empty()) { + sink.done(); // 送信終了 + return true; + } + return sink.write(chunk.data(), chunk.size()); + }, + "application/octet-stream"); +``` + +ラムダは「次のチャンクを作って`sink.write()`で送る」だけです。データがもう無くなったら`sink.done()`を呼べば送信が完了します。 + +## サイズがわかっている場合 + +送信するボディの**合計サイズが事前にわかっている**ときは、`ContentProvider`(`size_t offset, size_t length, DataSink &sink`を取るタイプ)と合計サイズを渡す別のオーバーロードを使います。 + +```cpp +size_t total_size = get_total_size(); + +auto res = cli.Post("/upload", total_size, + [&](size_t offset, size_t length, httplib::DataSink &sink) { + auto data = read_range(offset, length); + return sink.write(data.data(), data.size()); + }, + "application/octet-stream"); +``` + +サイズがわかっているとContent-Lengthヘッダーが付くので、サーバー側で進捗を把握しやすくなります。可能ならこちらを使いましょう。 + +> **Detail:** `sink.write()`は書き込みが成功したかどうかを`bool`で返します。`false`が返ったら回線が切れています。ラムダはそのまま`false`を返して終了しましょう。 + +> ファイルをそのまま送るだけなら、`make_file_body()`が便利です。C08. ファイルを生バイナリとしてPOSTするを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c10-stream-response.md b/docs-src/pages/ja/cookbook/c10-stream-response.md new file mode 100644 index 0000000..abbbd7e --- /dev/null +++ b/docs-src/pages/ja/cookbook/c10-stream-response.md @@ -0,0 +1,52 @@ +--- +title: "C10. レスポンスをストリーミングで受信する" +order: 10 +status: "draft" +--- + +レスポンスのボディをチャンクごとに受け取りたいときは、`ContentReceiver`を使います。大きなファイルを扱うときはもちろん、NDJSON(改行区切りJSON)やログストリームのように「届いた分だけ先に処理したい」ケースでも便利です。 + +## チャンクごとに処理する + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Get("/logs/stream", + [](const char *data, size_t len) { + std::cout.write(data, len); + std::cout.flush(); + return true; // falseを返すと受信を中止 + }); +``` + +サーバーから届いたデータが、到着した順にラムダに渡されます。コールバックが`false`を返すとダウンロードを途中で止められます。 + +## NDJSONを行単位でパースする + +バッファを使って改行区切りのJSONを1行ずつ処理する例です。 + +```cpp +std::string buffer; + +auto res = cli.Get("/events", + [&](const char *data, size_t len) { + buffer.append(data, len); + size_t pos; + while ((pos = buffer.find('\n')) != std::string::npos) { + auto line = buffer.substr(0, pos); + buffer.erase(0, pos + 1); + if (!line.empty()) { + auto j = nlohmann::json::parse(line); + handle_event(j); + } + } + return true; + }); +``` + +バッファに貯めながら、改行が見つかるたびに1行を取り出してパースします。ストリーミングAPIをリアルタイムに処理する基本パターンです。 + +> **Warning:** `ContentReceiver`を渡すと、`res->body`は**空のまま**になります。ボディは自分でコールバック内で保存するか処理するかしてください。 + +> ダウンロードの進捗を知りたい場合はC11. 進捗コールバックを使うと組み合わせましょう。 +> Server-Sent Events(SSE)を扱うときはE04. SSEをクライアントで受信するも参考になります。 diff --git a/docs-src/pages/ja/cookbook/c11-progress-callback.md b/docs-src/pages/ja/cookbook/c11-progress-callback.md new file mode 100644 index 0000000..4968f79 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c11-progress-callback.md @@ -0,0 +1,59 @@ +--- +title: "C11. 進捗コールバックを使う" +order: 11 +status: "draft" +--- + +ダウンロードやアップロードの進捗を表示したいときは、`DownloadProgress`または`UploadProgress`コールバックを渡します。どちらも`(current, total)`の2引数を取る関数オブジェクトです。 + +## ダウンロードの進捗 + +```cpp +httplib::Client cli("http://localhost:8080"); + +auto res = cli.Get("/large-file", + [](size_t current, size_t total) { + auto percent = (total > 0) ? (current * 100 / total) : 0; + std::cout << "\rDownloading: " << percent << "% (" + << current << "/" << total << ")" << std::flush; + return true; // falseを返すとダウンロードを中止 + }); +std::cout << std::endl; +``` + +コールバックはデータを受信するたびに呼ばれます。`total`はContent-Lengthから取得した値で、サーバーが送ってこない場合は`0`になることがあります。その場合は進捗率が計算できないので、受信済みバイト数だけを表示するのが無難です。 + +## アップロードの進捗 + +アップロード側も同じ形です。`Post()`や`Put()`の最後の引数に`UploadProgress`を渡します。 + +```cpp +httplib::Client cli("http://localhost:8080"); + +std::string body = load_large_body(); + +auto res = cli.Post("/upload", body, "application/octet-stream", + [](size_t current, size_t total) { + auto percent = current * 100 / total; + std::cout << "\rUploading: " << percent << "%" << std::flush; + return true; + }); +std::cout << std::endl; +``` + +## 中断する + +コールバックから`false`を返すと、転送を中止できます。UI側で「キャンセル」ボタンが押されたら`false`を返す、といった使い方ができます。 + +```cpp +std::atomic cancelled{false}; + +auto res = cli.Get("/large-file", + [&](size_t current, size_t total) { + return !cancelled.load(); + }); +``` + +> **Note:** `ContentReceiver`と進捗コールバックは同時に使えます。ファイルに書き出しながら進捗を表示したいときは、両方を渡しましょう。 + +> ファイル保存と組み合わせる具体例はC01. レスポンスボディを取得する / ファイルに保存するも参照してください。 diff --git a/docs-src/pages/ja/cookbook/c12-timeouts.md b/docs-src/pages/ja/cookbook/c12-timeouts.md new file mode 100644 index 0000000..33f2ea1 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c12-timeouts.md @@ -0,0 +1,50 @@ +--- +title: "C12. タイムアウトを設定する" +order: 12 +status: "draft" +--- + +クライアントには3種類のタイムアウトがあります。それぞれ別々に設定できます。 + +| 種類 | API | デフォルト | 意味 | +| --- | --- | --- | --- | +| 接続タイムアウト | `set_connection_timeout` | 300秒 | TCP接続の確立までの待ち時間 | +| 読み取りタイムアウト | `set_read_timeout` | 300秒 | レスポンスを受信する際の1回の`recv`待ち時間 | +| 書き込みタイムアウト | `set_write_timeout` | 5秒 | リクエストを送信する際の1回の`send`待ち時間 | + +## 基本の使い方 + +```cpp +httplib::Client cli("http://localhost:8080"); + +cli.set_connection_timeout(5, 0); // 5秒 +cli.set_read_timeout(10, 0); // 10秒 +cli.set_write_timeout(10, 0); // 10秒 + +auto res = cli.Get("/api/data"); +``` + +秒数とマイクロ秒を2引数で渡します。細かい指定が不要なら第2引数は省略できます。 + +## `std::chrono`で指定する + +`std::chrono`の期間を直接渡すオーバーロードもあります。こちらのほうが読みやすいのでおすすめです。 + +```cpp +using namespace std::chrono_literals; + +cli.set_connection_timeout(5s); +cli.set_read_timeout(10s); +cli.set_write_timeout(500ms); +``` + +## デフォルトでは300秒と長めな点に注意 + +接続タイムアウトと読み取りタイムアウトはデフォルトで**300秒(5分)**です。サーバーが反応しない場合、このままだと5分待たされます。短めに設定したほうが良いことが多いです。 + +```cpp +cli.set_connection_timeout(3s); +cli.set_read_timeout(10s); +``` + +> **Warning:** 読み取りタイムアウトは「1回の受信待ち」に対するタイムアウトです。大きなファイルのダウンロードで途中ずっとデータが流れている限り、リクエスト全体で30分かかっても発火しません。リクエスト全体の時間制限を設けたい場合はC13. 全体タイムアウトを設定するを使ってください。 diff --git a/docs-src/pages/ja/cookbook/c13-max-timeout.md b/docs-src/pages/ja/cookbook/c13-max-timeout.md new file mode 100644 index 0000000..1bfc5a7 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c13-max-timeout.md @@ -0,0 +1,42 @@ +--- +title: "C13. 全体タイムアウトを設定する" +order: 13 +status: "draft" +--- + +C12. タイムアウトを設定するで紹介した3種類のタイムアウトは、いずれも「1回の`send`や`recv`」に対するものです。リクエスト全体の所要時間に上限を設けたい場合は、`set_max_timeout()`を使います。 + +## 基本の使い方 + +```cpp +httplib::Client cli("http://localhost:8080"); + +cli.set_max_timeout(5000); // 5秒(ミリ秒単位) + +auto res = cli.Get("/slow-endpoint"); +``` + +ミリ秒単位で指定します。接続、送信、受信をすべて含めて、リクエスト全体が指定時間を超えたら打ち切られます。 + +## `std::chrono`で指定する + +こちらも`std::chrono`の期間を受け取るオーバーロードがあります。 + +```cpp +using namespace std::chrono_literals; +cli.set_max_timeout(5s); +``` + +## どう使い分けるか + +`set_read_timeout`は「データが来ない時間」のタイムアウトなので、少しずつデータが流れ続ける状況では発火しません。たとえば1秒ごとに1バイト届くようなエンドポイントは、`set_read_timeout`をいくら短くしてもタイムアウトしません。 + +一方、`set_max_timeout`は「経過時間」に対する上限なので、こうしたケースでも確実に止められます。外部APIを叩くときや、ユーザーを待たせすぎたくないときに重宝します。 + +```cpp +cli.set_connection_timeout(3s); +cli.set_read_timeout(10s); +cli.set_max_timeout(30s); // 全体で30秒を超えたら中断 +``` + +> **Note:** `set_max_timeout()`は通常のタイムアウトと併用できます。短期的な無反応は`set_read_timeout`で、長時間の処理は`set_max_timeout`で、という二段構えにするのが安全です。 diff --git a/docs-src/pages/ja/cookbook/c14-keep-alive.md b/docs-src/pages/ja/cookbook/c14-keep-alive.md new file mode 100644 index 0000000..e182ea9 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c14-keep-alive.md @@ -0,0 +1,53 @@ +--- +title: "C14. 接続の再利用とKeep-Aliveの挙動を理解する" +order: 14 +status: "draft" +--- + +`httplib::Client`は同じインスタンスで複数回リクエストを送ると、TCP接続を自動的に再利用します。HTTP/1.1のKeep-Aliveが有効に働くので、TCPハンドシェイクやTLSハンドシェイクのオーバーヘッドを毎回払わずに済みます。 + +## 接続は自動で使い回される + +```cpp +httplib::Client cli("https://api.example.com"); + +auto res1 = cli.Get("/users/1"); +auto res2 = cli.Get("/users/2"); // 同じ接続を再利用 +auto res3 = cli.Get("/users/3"); // 同じ接続を再利用 +``` + +特別な設定は要りません。`cli`を使い回すだけで、内部的には同じソケットで通信が続きます。とくにHTTPSでは、TLSハンドシェイクのコストが大きいので効果が顕著です。 + +## Keep-Aliveを明示的にオフにする + +毎回新しい接続を張り直したい場合は、`set_keep_alive(false)`を呼びます。テスト目的などで使うことがあります。 + +```cpp +cli.set_keep_alive(false); +``` + +ただし、普段はオン(デフォルト)のままで問題ありません。 + +## リクエストごとに`Client`を作らない + +1回のリクエストのたびに`Client`をスコープから抜けて破棄すると、接続の再利用は効きません。ループの外でインスタンスを作り、中で使い回しましょう。 + +```cpp +// NG: 毎回接続が切れる +for (auto id : ids) { + httplib::Client cli("https://api.example.com"); + cli.Get("/users/" + id); +} + +// OK: 接続が再利用される +httplib::Client cli("https://api.example.com"); +for (auto id : ids) { + cli.Get("/users/" + id); +} +``` + +## 並行リクエスト + +複数のスレッドから並行にリクエストを送りたいときは、スレッドごとに別々の`Client`インスタンスを持つのが無難です。1つの`Client`は1本のTCP接続を使い回すので、同じインスタンスに複数スレッドから同時にリクエストを投げると、結局どこかで直列化されます。 + +> **Note:** サーバー側のKeep-Aliveタイムアウトを超えると、サーバーが接続を切ります。その場合cpp-httplibは自動で再接続して再試行するので、アプリケーションコードで気にする必要はありません。 diff --git a/docs-src/pages/ja/cookbook/c15-compression.md b/docs-src/pages/ja/cookbook/c15-compression.md new file mode 100644 index 0000000..cfc1a70 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c15-compression.md @@ -0,0 +1,47 @@ +--- +title: "C15. 圧縮を有効にする" +order: 15 +status: "draft" +--- + +cpp-httplibは送信時の圧縮と受信時の解凍をサポートしています。ただし、zlibまたはBrotliを有効にしてビルドしておく必要があります。 + +## ビルド時の準備 + +圧縮機能を使うには、`httplib.h`をインクルードする前に次のマクロを定義しておきます。 + +```cpp +#define CPPHTTPLIB_ZLIB_SUPPORT // gzip / deflate +#define CPPHTTPLIB_BROTLI_SUPPORT // brotli +#include +``` + +リンク時に`zlib`や`brotli`のライブラリも必要です。 + +## リクエストボディを圧縮して送る + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_compress(true); + +std::string big_payload = build_payload(); +auto res = cli.Post("/api/data", big_payload, "application/json"); +``` + +`set_compress(true)`を呼んでおくと、POSTやPUTのリクエストボディがgzipで圧縮されて送信されます。サーバー側が対応している必要があります。 + +## レスポンスを解凍する + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_decompress(true); // デフォルトで有効 + +auto res = cli.Get("/api/data"); +std::cout << res->body << std::endl; +``` + +`set_decompress(true)`を呼ぶと、サーバーが`Content-Encoding: gzip`などで圧縮したレスポンスを自動で解凍してくれます。`res->body`には解凍済みのデータが入ります。 + +デフォルトで有効なので、通常は何もしなくても解凍されます。あえて生の圧縮データを触りたいときだけ`set_decompress(false)`にしましょう。 + +> **Warning:** `CPPHTTPLIB_ZLIB_SUPPORT`を定義せずにビルドすると、`set_compress()`や`set_decompress()`を呼んでも何も起こりません。マクロの定義を忘れていないか、最初に確認しましょう。 diff --git a/docs-src/pages/ja/cookbook/c16-proxy.md b/docs-src/pages/ja/cookbook/c16-proxy.md new file mode 100644 index 0000000..375b233 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c16-proxy.md @@ -0,0 +1,52 @@ +--- +title: "C16. プロキシを経由してリクエストを送る" +order: 16 +status: "draft" +--- + +社内ネットワークや特定の経路を通したい場合、HTTPプロキシを経由してリクエストを送れます。`set_proxy()`でプロキシのホストとポートを指定するだけです。 + +## 基本の使い方 + +```cpp +httplib::Client cli("https://api.example.com"); +cli.set_proxy("proxy.internal", 8080); + +auto res = cli.Get("/users"); +``` + +プロキシ経由でリクエストが送られます。HTTPSの場合はCONNECTメソッドでトンネルが張られるので、cpp-httplib側で特別な設定は要りません。 + +## プロキシに認証を設定する + +プロキシ自体が認証を要求する場合は、`set_proxy_basic_auth()`や`set_proxy_bearer_token_auth()`を使います。 + +```cpp +cli.set_proxy("proxy.internal", 8080); +cli.set_proxy_basic_auth("user", "password"); +``` + +```cpp +cli.set_proxy_bearer_token_auth("token"); +``` + +OpenSSL(または他のTLSバックエンド)付きでビルドしていれば、Digest認証も使えます。 + +```cpp +cli.set_proxy_digest_auth("user", "password"); +``` + +## エンドのサーバー認証と組み合わせる + +プロキシ認証と、エンドサーバーへの認証(C05やC06)は別物です。両方が必要なら、両方設定します。 + +```cpp +cli.set_proxy("proxy.internal", 8080); +cli.set_proxy_basic_auth("proxy-user", "proxy-pass"); + +cli.set_bearer_token_auth("api-token"); // エンドサーバー向け +``` + +プロキシには`Proxy-Authorization`、エンドサーバーには`Authorization`ヘッダーが送られます。 + +> **Note:** 環境変数の`HTTP_PROXY`や`HTTPS_PROXY`は自動的には読まれません。必要ならアプリケーション側で読み取って`set_proxy()`に渡してください。 diff --git a/docs-src/pages/ja/cookbook/c17-error-codes.md b/docs-src/pages/ja/cookbook/c17-error-codes.md new file mode 100644 index 0000000..5890b14 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c17-error-codes.md @@ -0,0 +1,63 @@ +--- +title: "C17. エラーコードをハンドリングする" +order: 17 +status: "draft" +--- + +`cli.Get()`や`cli.Post()`は`Result`型を返します。リクエストが失敗したとき(サーバーに到達できなかった、タイムアウトしたなど)、返り値は「falsy」になります。詳しい原因を知りたい場合は`Result::error()`を使います。 + +## 基本の判定 + +```cpp +httplib::Client cli("http://localhost:8080"); +auto res = cli.Get("/api/data"); + +if (res) { + // リクエストが送れて、レスポンスも受け取れた + std::cout << "status: " << res->status << std::endl; +} else { + // ネットワーク層で失敗した + std::cerr << "error: " << httplib::to_string(res.error()) << std::endl; +} +``` + +`if (res)`で成功・失敗を判定し、失敗時は`res.error()`で`httplib::Error`列挙値を取り出せます。`to_string()`に渡すと人間が読める文字列になります。 + +## 代表的なエラー + +| 値 | 意味 | +| --- | --- | +| `Error::Connection` | サーバーに接続できなかった | +| `Error::ConnectionTimeout` | 接続タイムアウト(`set_connection_timeout`) | +| `Error::Read` / `Error::Write` | 送受信中のエラー | +| `Error::Timeout` | `set_max_timeout`で設定した全体タイムアウト | +| `Error::ExceedRedirectCount` | リダイレクト回数が上限を超えた | +| `Error::SSLConnection` | TLSハンドシェイクに失敗 | +| `Error::SSLServerVerification` | サーバー証明書の検証に失敗 | +| `Error::Canceled` | 進捗コールバックから`false`が返された | + +## ステータスコードとの使い分け + +`res`が truthy でも、HTTPステータスコードが4xxや5xxのこともあります。この2つは別物です。 + +```cpp +auto res = cli.Get("/api/data"); +if (!res) { + // ネットワークエラー(そもそもレスポンスを受け取れていない) + std::cerr << "network error: " << httplib::to_string(res.error()) << std::endl; + return 1; +} + +if (res->status >= 400) { + // HTTPエラー(レスポンスは受け取った) + std::cerr << "http error: " << res->status << std::endl; + return 1; +} + +// 正常系 +std::cout << res->body << std::endl; +``` + +ネットワーク層のエラーは`res.error()`、HTTPのエラーは`res->status`、と頭の中で分けておきましょう。 + +> SSL関連のエラーをさらに詳しく調べたい場合はC18. SSLエラーをハンドリングするを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c18-ssl-errors.md b/docs-src/pages/ja/cookbook/c18-ssl-errors.md new file mode 100644 index 0000000..3d1b6ac --- /dev/null +++ b/docs-src/pages/ja/cookbook/c18-ssl-errors.md @@ -0,0 +1,53 @@ +--- +title: "C18. SSLエラーをハンドリングする" +order: 18 +status: "draft" +--- + +HTTPSリクエストで失敗したとき、`res.error()`は`Error::SSLConnection`や`Error::SSLServerVerification`といった値を返します。ただ、これだけだと原因の切り分けが難しいこともあります。そんなときは`Result::ssl_error()`と`Result::ssl_backend_error()`が役に立ちます。 + +## SSLエラーの詳細を取得する + +```cpp +httplib::Client cli("https://api.example.com"); +auto res = cli.Get("/"); + +if (!res) { + auto err = res.error(); + std::cerr << "error: " << httplib::to_string(err) << std::endl; + + if (err == httplib::Error::SSLConnection || + err == httplib::Error::SSLServerVerification) { + std::cerr << "ssl_error: " << res.ssl_error() << std::endl; + std::cerr << "ssl_backend_error: " << res.ssl_backend_error() << std::endl; + } +} +``` + +`ssl_error()`はSSLライブラリが返したエラーコード(OpenSSLの`SSL_get_error()`の値など)、`ssl_backend_error()`はバックエンドがさらに詳しく提供するエラー値です。OpenSSLなら`ERR_get_error()`の値が入ります。 + +## OpenSSLのエラーを文字列化する + +`ssl_backend_error()`で取得した値を、OpenSSLの`ERR_error_string()`で文字列にするとデバッグに便利です。 + +```cpp +#include + +if (res.ssl_backend_error() != 0) { + char buf[256]; + ERR_error_string_n(res.ssl_backend_error(), buf, sizeof(buf)); + std::cerr << "openssl: " << buf << std::endl; +} +``` + +## よくある原因 + +| 症状 | ありがちな原因 | +| --- | --- | +| `SSLServerVerification` | CA証明書のパスが通っていない、自己署名証明書 | +| `SSLServerHostnameVerification` | 証明書のCN/SANとホスト名が一致しない | +| `SSLConnection` | TLSバージョンの不一致、対応スイートが無い | + +> **Note:** `ssl_backend_error()`は以前は`ssl_openssl_error()`と呼ばれていました。後者はdeprecatedで、現在は`ssl_backend_error()`を使ってください。 + +> 証明書の検証設定を変えたい場合はT02. SSL証明書の検証を制御するを参照してください。 diff --git a/docs-src/pages/ja/cookbook/c19-client-logger.md b/docs-src/pages/ja/cookbook/c19-client-logger.md new file mode 100644 index 0000000..e81b2a1 --- /dev/null +++ b/docs-src/pages/ja/cookbook/c19-client-logger.md @@ -0,0 +1,57 @@ +--- +title: "C19. クライアントにログを設定する" +order: 19 +status: "draft" +--- + +クライアントから送ったリクエストと受け取ったレスポンスをログに残したいときは、`set_logger()`を使います。エラーだけを拾いたいなら`set_error_logger()`が別に用意されています。 + +## リクエストとレスポンスをログに残す + +```cpp +httplib::Client cli("https://api.example.com"); + +cli.set_logger([](const httplib::Request &req, const httplib::Response &res) { + std::cout << req.method << " " << req.path + << " -> " << res.status << std::endl; +}); + +auto res = cli.Get("/users"); +``` + +`set_logger()`に渡したコールバックは、リクエストが完了するたびに呼ばれます。リクエストとレスポンスの両方を引数で受け取れるので、メソッドやパス、ステータスコード、ヘッダー、ボディなど好きな情報をログに残せます。 + +## エラーだけを拾う + +ネットワーク層のエラーが起きたとき(`Error::Connection`など)は、`set_logger()`は呼ばれません。レスポンスが無いからです。こうしたエラーを拾いたいときは`set_error_logger()`を使います。 + +```cpp +cli.set_error_logger([](const httplib::Error &err, const httplib::Request *req) { + std::cerr << "error: " << httplib::to_string(err); + if (req) { + std::cerr << " (" << req->method << " " << req->path << ")"; + } + std::cerr << std::endl; +}); +``` + +第2引数の`req`はヌルポインタのこともあります。リクエストを組み立てる前の段階で失敗した場合です。使う前に必ずヌルチェックしてください。 + +## 両方を組み合わせる + +成功時は通常のログ、失敗時はエラーログ、という2本立てにすると便利です。 + +```cpp +cli.set_logger([](const auto &req, const auto &res) { + std::cout << "[ok] " << req.method << " " << req.path + << " " << res.status << std::endl; +}); + +cli.set_error_logger([](const auto &err, const auto *req) { + std::cerr << "[ng] " << httplib::to_string(err); + if (req) std::cerr << " " << req->method << " " << req->path; + std::cerr << std::endl; +}); +``` + +> **Note:** ログコールバックはリクエスト処理と同じスレッドで同期的に呼ばれます。重い処理を入れるとリクエストがその分遅くなるので、必要なら別スレッドのキューに流しましょう。 diff --git a/docs-src/pages/ja/cookbook/index.md b/docs-src/pages/ja/cookbook/index.md index 3ecf52a..d500047 100644 --- a/docs-src/pages/ja/cookbook/index.md +++ b/docs-src/pages/ja/cookbook/index.md @@ -4,93 +4,93 @@ order: 0 status: "draft" --- -「〇〇をするには?」という問いに答えるレシピ集です。各レシピは独立しているので、必要なページだけ読めます。 +「〇〇をするには?」という問いに答えるレシピ集です。各レシピは独立しているので、必要なページだけ読めます。基本的な使い方は[Tour](../tour/)で紹介しています。 ## クライアント ### 基本 -- レスポンスボディを文字列で取得する / ファイルに保存する -- JSON を送受信する -- デフォルトヘッダーを設定する(`set_default_headers`) -- リダイレクトを追従する(`set_follow_location`) +- [C01. レスポンスボディを取得する / ファイルに保存する](c01-get-response-body) +- [C02. JSONを送受信する](c02-json) +- [C03. デフォルトヘッダーを設定する](c03-default-headers) +- [C04. リダイレクトを追従する](c04-follow-location) ### 認証 -- Basic 認証を使う(`set_basic_auth`) -- Bearer トークンで API を呼ぶ +- [C05. Basic認証を使う](c05-basic-auth) +- [C06. BearerトークンでAPIを呼ぶ](c06-bearer-token) ### ファイル送信 -- ファイルをマルチパートフォームとしてアップロードする(`make_file_provider`) -- ファイルを生バイナリとして POST する(`make_file_body`) -- チャンク転送でボディを送る(Content Provider) +- [C07. ファイルをマルチパートフォームとしてアップロードする](c07-multipart-upload) +- [C08. ファイルを生バイナリとしてPOSTする](c08-post-file-body) +- [C09. チャンク転送でボディを送る](c09-chunked-upload) ### ストリーミング・進捗 -- レスポンスをストリーミングで受信する -- 進捗コールバックを使う(`DownloadProgress` / `UploadProgress`) +- [C10. レスポンスをストリーミングで受信する](c10-stream-response) +- [C11. 進捗コールバックを使う](c11-progress-callback) ### 接続・パフォーマンス -- タイムアウトを設定する(`set_connection_timeout` / `set_read_timeout`) -- 全体タイムアウトを設定する(`set_max_timeout`) -- 接続の再利用と Keep-Alive の挙動を理解する -- 圧縮を有効にする(`set_compress` / `set_decompress`) -- プロキシを経由してリクエストを送る(`set_proxy`) +- [C12. タイムアウトを設定する](c12-timeouts) +- [C13. 全体タイムアウトを設定する](c13-max-timeout) +- [C14. 接続の再利用とKeep-Aliveの挙動を理解する](c14-keep-alive) +- [C15. 圧縮を有効にする](c15-compression) +- [C16. プロキシを経由してリクエストを送る](c16-proxy) ### エラー処理・デバッグ -- エラーコードをハンドリングする(`Result::error()`) -- SSL エラーをハンドリングする(`ssl_error()` / `ssl_backend_error()`) -- クライアントにログを設定する(`set_logger` / `set_error_logger`) +- [C17. エラーコードをハンドリングする](c17-error-codes) +- [C18. SSLエラーをハンドリングする](c18-ssl-errors) +- [C19. クライアントにログを設定する](c19-client-logger) ## サーバー ### 基本 -- GET / POST / PUT / DELETE ハンドラを登録する -- JSON リクエストを受け取り JSON レスポンスを返す -- パスパラメーターを使う(`/users/:id`) -- 静的ファイルサーバーを設定する(`set_mount_point`) +- S01. GET / POST / PUT / DELETEハンドラを登録する +- S02. JSONリクエストを受け取りJSONレスポンスを返す +- S03. パスパラメーターを使う(`/users/:id`) +- S04. 静的ファイルサーバーを設定する(`set_mount_point`) ### ストリーミング・ファイル -- 大きなファイルをストリーミングで返す(`ContentProvider`) -- ファイルダウンロードレスポンスを返す(`Content-Disposition`) -- マルチパートデータをストリーミングで受け取る(`ContentReader`) -- レスポンスを圧縮して返す(gzip) +- S05. 大きなファイルをストリーミングで返す(`ContentProvider`) +- S06. ファイルダウンロードレスポンスを返す(`Content-Disposition`) +- S07. マルチパートデータをストリーミングで受け取る(`ContentReader`) +- S08. レスポンスを圧縮して返す(gzip) ### ハンドラチェーン -- 全ルートに共通の前処理をする(Pre-routing handler) -- Post-routing handler でレスポンスヘッダーを追加する(CORS など) -- Pre-request handler でルート単位の認証を行う(`matched_route`) -- `res.user_data` でハンドラ間データを渡す +- S09. 全ルートに共通の前処理をする(Pre-routing handler) +- S10. Post-routing handlerでレスポンスヘッダーを追加する(CORSなど) +- S11. Pre-request handlerでルート単位の認証を行う(`matched_route`) +- S12. `res.user_data`でハンドラ間データを渡す ### エラー処理・デバッグ -- カスタムエラーページを返す(`set_error_handler`) -- 例外をキャッチする(`set_exception_handler`) -- リクエストをログに記録する(Logger) -- クライアントが切断したか検出する(`req.is_connection_closed()`) +- S13. カスタムエラーページを返す(`set_error_handler`) +- S14. 例外をキャッチする(`set_exception_handler`) +- S15. リクエストをログに記録する(Logger) +- S16. クライアントが切断したか検出する(`req.is_connection_closed()`) ### 運用・チューニング -- ポートを動的に割り当てる(`bind_to_any_port`) -- `listen_after_bind` で起動順序を制御する -- グレースフルシャットダウンする(`stop()` とシグナルハンドリング) -- Keep-Alive を調整する(`set_keep_alive_max_count` / `set_keep_alive_timeout`) -- マルチスレッド数を設定する(`new_task_queue`) +- S17. ポートを動的に割り当てる(`bind_to_any_port`) +- S18. `listen_after_bind`で起動順序を制御する +- S19. グレースフルシャットダウンする(`stop()`とシグナルハンドリング) +- S20. Keep-Aliveを調整する(`set_keep_alive_max_count` / `set_keep_alive_timeout`) +- S21. マルチスレッド数を設定する(`new_task_queue`) +- S22. Unix domain socketで通信する(`set_address_family(AF_UNIX)`) ## TLS / セキュリティ -- OpenSSL・mbedTLS・wolfSSL の選択指針(ビルド時の `#define` の違い) -- SSL 証明書の検証を制御する(証明書の無効化・カスタム CA・カスタムコールバック) -- カスタム証明書検証コールバックを使う(`set_server_certificate_verifier`) -- SSL/TLS サーバーを立ち上げる(証明書・秘密鍵の設定) -- mTLS(クライアント証明書による相互認証)を設定する -- サーバー側でピア証明書を参照する(`req.peer_cert()` / SNI) +- T01. OpenSSL・mbedTLS・wolfSSLの選択指針(ビルド時の`#define`の違い) +- T02. SSL証明書の検証を制御する(無効化・カスタムCA・カスタムコールバック) +- T03. SSL/TLSサーバーを立ち上げる(証明書・秘密鍵の設定) +- T04. mTLS(クライアント証明書による相互認証)を設定する +- T05. サーバー側でピア証明書を参照する(`req.peer_cert()` / SNI) ## SSE -- SSE サーバーを実装する -- SSE でイベント名を使い分ける -- SSE の再接続を処理する(`Last-Event-ID`) -- SSE をクライアントで受信する +- E01. SSEサーバーを実装する +- E02. SSEでイベント名を使い分ける +- E03. SSEの再接続を処理する(`Last-Event-ID`) +- E04. SSEをクライアントで受信する ## WebSocket -- WebSocket エコーサーバー/クライアントを実装する -- ハートビートを設定する(`set_websocket_ping_interval` / `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND`) -- 接続クローズをハンドリングする -- バイナリフレームを送受信する +- W01. WebSocketエコーサーバー/クライアントを実装する +- W02. ハートビートを設定する(`set_websocket_ping_interval`) +- W03. 接続クローズをハンドリングする +- W04. バイナリフレームを送受信する