title: “Basic Client” order: 2

cpp-httplib isn‘t just for servers -- it also comes with a full HTTP client. Let’s use httplib::Client to send GET and POST requests.

Preparing a Test Server

To try out the client, you need a server that accepts requests. Save the following code as test_server.cpp, then compile and run it the same way you did in the previous chapter. We'll cover the server details in the next chapter.

#include "httplib.h"
#include <iostream>

int main() {
    httplib::Server svr;

    svr.Get("/hi", [](const auto &, auto &res) {
        res.set_content("Hello!", "text/plain");
    });

    svr.Get("/search", [](const auto &req, auto &res) {
        auto q = req.get_param_value("q");
        res.set_content("Query: " + q, "text/plain");
    });

    svr.Post("/post", [](const auto &req, auto &res) {
        res.set_content(req.body, "text/plain");
    });

    svr.Post("/submit", [](const auto &req, auto &res) {
        std::string result;
        for (auto &[key, val] : req.params) {
            result += key + " = " + val + "\n";
        }
        res.set_content(result, "text/plain");
    });

    svr.Post("/upload", [](const auto &req, auto &res) {
        auto f = req.form.get_file("file");
        auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
        res.set_content(content, "text/plain");
    });

    svr.Get("/users/:id", [](const auto &req, auto &res) {
        auto id = req.path_params.at("id");
        res.set_content("User ID: " + id, "text/plain");
    });

    svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
        auto id = req.matches[1];
        res.set_content("File ID: " + std::string(id), "text/plain");
    });

    std::cout << "Listening on port 8080..." << std::endl;
    svr.listen("0.0.0.0", 8080);
}

GET Request

Once the server is running, open a separate terminal and give it a try. Let's start with the simplest GET request.

#include "httplib.h"
#include <iostream>

int main() {
    httplib::Client cli("http://localhost:8080");

    auto res = cli.Get("/hi");
    if (res) {
        std::cout << res->status << std::endl;  // 200
        std::cout << res->body << std::endl;    // Hello!
    }
}

Pass the server address to the httplib::Client constructor, then call Get() to send a request. You can retrieve the status code and body from the returned res.

Here's the equivalent curl command.

curl http://localhost:8080/hi
# Hello!

Checking the Response

A response contains header information in addition to the status code and body.

auto res = cli.Get("/hi");
if (res) {
    // Status code
    std::cout << res->status << std::endl;  // 200

    // Body
    std::cout << res->body << std::endl;  // Hello!

    // Headers
    std::cout << res->get_header_value("Content-Type") << std::endl;  // text/plain
}

res->body is a std::string, so if you want to parse a JSON response, you can pass it directly to a JSON library like nlohmann/json.

Query Parameters

To add query parameters to a GET request, you can either write them directly in the URL or use httplib::Params.

auto res = cli.Get("/search", httplib::Params{{"q", "cpp-httplib"}});
if (res) {
    std::cout << res->body << std::endl;  // Query: cpp-httplib
}

httplib::Params automatically URL-encodes special characters for you.

curl "http://localhost:8080/search?q=cpp-httplib"
# Query: cpp-httplib

Path Parameters

When values are embedded directly in the URL path, no special client API is needed. Just pass the path to Get() as-is.

auto res = cli.Get("/users/42");
if (res) {
    std::cout << res->body << std::endl;  // User ID: 42
}
curl http://localhost:8080/users/42
# User ID: 42

The test server also has a /files/(\d+) route that uses a regex to accept numeric IDs only.

auto res = cli.Get("/files/42");
if (res) {
    std::cout << res->body << std::endl;  // File ID: 42
}
curl http://localhost:8080/files/42
# File ID: 42

Pass a non-numeric ID like /files/abc and you‘ll get a 404. We’ll cover how that works in the next chapter.

Request Headers

To add custom HTTP headers, pass an httplib::Headers object. This works with both Get() and Post().

auto res = cli.Get("/hi", httplib::Headers{
    {"Authorization", "Bearer my-token"}
});
curl -H "Authorization: Bearer my-token" http://localhost:8080/hi

POST Request

Let's POST some text data. Pass the body as the second argument to Post() and the Content-Type as the third.

auto res = cli.Post("/post", "Hello, Server!", "text/plain");
if (res) {
    std::cout << res->status << std::endl;  // 200
    std::cout << res->body << std::endl;    // Hello, Server!
}

The test server's /post endpoint echoes the body back, so you get the same string you sent.

curl -X POST -H "Content-Type: text/plain" -d "Hello, Server!" http://localhost:8080/post
# Hello, Server!

Sending Form Data

You can send key-value pairs just like an HTML form. Use httplib::Params for this.

auto res = cli.Post("/submit", httplib::Params{
    {"name", "Alice"},
    {"age", "30"}
});
if (res) {
    std::cout << res->body << std::endl;
    // age = 30
    // name = Alice
}

This sends the data in application/x-www-form-urlencoded format.

curl -X POST -d "name=Alice&age=30" http://localhost:8080/submit

POSTing a File

To upload a file, use httplib::UploadFormDataItems to send it as multipart form data.

auto res = cli.Post("/upload", httplib::UploadFormDataItems{
    {"file", "Hello, File!", "hello.txt", "text/plain"}
});
if (res) {
    std::cout << res->body << std::endl;  // hello.txt (12 bytes)
}

Each element in UploadFormDataItems has four fields: {name, content, filename, content_type}.

curl -F "file=Hello, File!;filename=hello.txt;type=text/plain" http://localhost:8080/upload

Error Handling

Network communication can fail -- the server might not be reachable. Always check whether res is valid.

httplib::Client cli("http://localhost:9999");  // Non-existent port
auto res = cli.Get("/hi");

if (!res) {
    // Connection error
    std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
    // Error: Connection
    return 1;
}

// If we reach here, we have a response
if (res->status != 200) {
    std::cout << "HTTP Error: " << res->status << std::endl;
    return 1;
}

std::cout << res->body << std::endl;

There are two levels of errors.

  • Connection error: The client couldn't reach the server. res evaluates to false, and you can call res.error() to find out what went wrong.
  • HTTP error: The server returned an error status (404, 500, etc.). res evaluates to true, but you need to check res->status.

Next Steps

Now you know how to send requests from a client. Next, let‘s take a closer look at the server side. We’ll dig into routing, path parameters, and more.

Next: Basic Server