| --- |
| title: "Basic Server" |
| order: 3 |
| --- |
| |
| In the previous chapter, you sent requests from a client to a test server. Now let's walk through how that server actually works. |
| |
| ## Starting the Server |
| |
| Once you've registered your routes, call `svr.listen()` to start the server. |
| |
| ```cpp |
| svr.listen("0.0.0.0", 8080); |
| ``` |
| |
| The first argument is the host, and the second is the port. `"0.0.0.0"` listens on all network interfaces. Use `"127.0.0.1"` if you want to accept connections from your own machine only. |
| |
| `listen()` is a blocking call. It won't return until the server stops. The server keeps running until you press `Ctrl+C` in your terminal or call `svr.stop()` from another thread. |
| |
| ## Routing |
| |
| Routing is the heart of any server. It's how you tell cpp-httplib: when a request comes in for this URL with this HTTP method, run this code. |
| |
| ```cpp |
| httplib::Server svr; |
| |
| svr.Get("/hi", [](const httplib::Request &req, httplib::Response &res) { |
| res.set_content("Hello!", "text/plain"); |
| }); |
| ``` |
| |
| `svr.Get()` registers a handler for GET requests. The first argument is the path, the second is the handler function. When a GET request arrives at `/hi`, your lambda runs. |
| |
| There's a method for each HTTP verb. |
| |
| ```cpp |
| svr.Get("/path", handler); // GET |
| svr.Post("/path", handler); // POST |
| svr.Put("/path", handler); // PUT |
| svr.Delete("/path", handler); // DELETE |
| ``` |
| |
| The handler signature is `(const httplib::Request &req, httplib::Response &res)`. You can use `auto` to keep it short. |
| |
| ```cpp |
| svr.Get("/hi", [](const auto &req, auto &res) { |
| res.set_content("Hello!", "text/plain"); |
| }); |
| ``` |
| |
| The handler only runs when the path matches. Requests to unregistered paths automatically return 404. |
| |
| ## The Request Object |
| |
| The first parameter `req` gives you everything the client sent. |
| |
| ### Body |
| |
| `req.body` holds the request body as a `std::string`. |
| |
| ```cpp |
| svr.Post("/post", [](const auto &req, auto &res) { |
| // Echo the body back to the client |
| res.set_content(req.body, "text/plain"); |
| }); |
| ``` |
| |
| ### Headers |
| |
| Use `req.get_header_value()` to read a request header. |
| |
| ```cpp |
| svr.Get("/check", [](const auto &req, auto &res) { |
| auto auth = req.get_header_value("Authorization"); |
| res.set_content("Auth: " + auth, "text/plain"); |
| }); |
| ``` |
| |
| ### Query Parameters and Form Data |
| |
| `req.get_param_value()` retrieves a parameter by name. It works for both GET query parameters and POST form data. |
| |
| ```cpp |
| svr.Get("/search", [](const auto &req, auto &res) { |
| auto q = req.get_param_value("q"); |
| res.set_content("Query: " + q, "text/plain"); |
| }); |
| ``` |
| |
| A request to `/search?q=cpp-httplib` gives you `"cpp-httplib"` for `q`. |
| |
| To loop over all parameters, use `req.params`. |
| |
| ```cpp |
| 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"); |
| }); |
| ``` |
| |
| ### File Uploads |
| |
| Files uploaded via multipart form data are available through `req.form.get_file()`. |
| |
| ```cpp |
| 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"); |
| }); |
| ``` |
| |
| `f.filename` gives you the filename, and `f.content` gives you the file data. |
| |
| ## Path Parameters |
| |
| Sometimes you want to capture part of the URL as a variable -- for example, the `42` in `/users/42`. Use the `:param` syntax to do that. |
| |
| ```cpp |
| svr.Get("/users/:id", [](const auto &req, auto &res) { |
| auto id = req.path_params.at("id"); |
| res.set_content("User ID: " + id, "text/plain"); |
| }); |
| ``` |
| |
| A request to `/users/42` gives you `"42"` from `req.path_params.at("id")`. `/users/100` gives you `"100"`. |
| |
| You can capture multiple segments at once. |
| |
| ```cpp |
| svr.Get("/users/:user_id/posts/:post_id", [](const auto &req, auto &res) { |
| auto user_id = req.path_params.at("user_id"); |
| auto post_id = req.path_params.at("post_id"); |
| res.set_content("User: " + user_id + ", Post: " + post_id, "text/plain"); |
| }); |
| ``` |
| |
| ### Regex Patterns |
| |
| You can also write a regular expression directly in the path instead of `:param`. Capture group values are available via `req.matches`, which is a `std::smatch`. |
| |
| ```cpp |
| // Only accept numeric IDs |
| svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) { |
| auto id = req.matches[1]; // First capture group |
| res.set_content("File ID: " + std::string(id), "text/plain"); |
| }); |
| ``` |
| |
| `/files/42` matches, but `/files/abc` doesn't. This is handy when you want to constrain what values are accepted. |
| |
| ## Building a Response |
| |
| The second parameter `res` is how you send data back to the client. |
| |
| ### Body and Content-Type |
| |
| `res.set_content()` sets the body and Content-Type. That's all you need for a 200 response. |
| |
| ```cpp |
| svr.Get("/hi", [](const auto &req, auto &res) { |
| res.set_content("Hello!", "text/plain"); |
| }); |
| ``` |
| |
| ### Status Code |
| |
| To return a different status code, assign to `res.status`. |
| |
| ```cpp |
| svr.Get("/not-found", [](const auto &req, auto &res) { |
| res.status = 404; |
| res.set_content("Not found", "text/plain"); |
| }); |
| ``` |
| |
| ### Response Headers |
| |
| Add response headers with `res.set_header()`. |
| |
| ```cpp |
| svr.Get("/with-header", [](const auto &req, auto &res) { |
| res.set_header("X-Custom", "my-value"); |
| res.set_content("Hello!", "text/plain"); |
| }); |
| ``` |
| |
| ## Walking Through the Test Server |
| |
| Now let's use what we've learned to read through the test server from the previous chapter. |
| |
| ### GET /hi |
| |
| ```cpp |
| svr.Get("/hi", [](const auto &, auto &res) { |
| res.set_content("Hello!", "text/plain"); |
| }); |
| ``` |
| |
| The simplest possible handler. We don't need any information from the request, so the `req` parameter is left unnamed. It just returns `"Hello!"`. |
| |
| ### GET /search |
| |
| ```cpp |
| svr.Get("/search", [](const auto &req, auto &res) { |
| auto q = req.get_param_value("q"); |
| res.set_content("Query: " + q, "text/plain"); |
| }); |
| ``` |
| |
| `req.get_param_value("q")` pulls out the query parameter `q`. A request to `/search?q=cpp-httplib` returns `"Query: cpp-httplib"`. |
| |
| ### POST /post |
| |
| ```cpp |
| svr.Post("/post", [](const auto &req, auto &res) { |
| res.set_content(req.body, "text/plain"); |
| }); |
| ``` |
| |
| An echo server. Whatever body the client sends, `req.body` holds it, and we send it straight back. |
| |
| ### POST /submit |
| |
| ```cpp |
| 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"); |
| }); |
| ``` |
| |
| Loops over the form data in `req.params` using structured bindings (`auto &[key, val]`) to unpack each key-value pair. |
| |
| ### POST /upload |
| |
| ```cpp |
| 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"); |
| }); |
| ``` |
| |
| Receives a file uploaded via multipart form data. `req.form.get_file("file")` fetches the field named `"file"`, and we respond with the filename and size. |
| |
| ### GET /users/:id |
| |
| ```cpp |
| svr.Get("/users/:id", [](const auto &req, auto &res) { |
| auto id = req.path_params.at("id"); |
| res.set_content("User ID: " + id, "text/plain"); |
| }); |
| ``` |
| |
| `:id` is the path parameter. `req.path_params.at("id")` retrieves its value. `/users/42` gives you `"42"`, `/users/alice` gives you `"alice"`. |
| |
| ### GET /files/(\d+) |
| |
| ```cpp |
| 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"); |
| }); |
| ``` |
| |
| The regex `(\d+)` matches numeric IDs only. `/files/42` hits this handler, but `/files/abc` returns 404. `req.matches[1]` retrieves the first capture group. |
| |
| ## Next Steps |
| |
| You now have the full picture of how a server works. Routing, reading requests, building responses -- that's enough to build a real API server. |
| |
| Next, let's look at serving static files. We'll build a server that delivers HTML and CSS. |
| |
| **Next:** [Static File Server](../04-static-file-server) |