From 051ecfbff6ab624032d38ce2b5b3458991b9fb2e Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Sun, 1 Feb 2026 14:54:48 +0300 Subject: [PATCH] feat(http): add session & cookies examples and update middleware docs --- examples/auth/session_cookies.cpp | 18 ++ examples/cookies/cookie.cpp | 21 +++ examples/middleware_http/README.md | 173 ++++++++++++++++++ .../mega_middleware_routes.cpp | 135 ++++++++++++++ examples/session/session.cpp | 23 +++ modules/core | 2 +- modules/middleware | 2 +- 7 files changed, 372 insertions(+), 2 deletions(-) create mode 100644 examples/auth/session_cookies.cpp create mode 100644 examples/cookies/cookie.cpp create mode 100644 examples/middleware_http/README.md rename examples/{ => middleware_http}/mega_middleware_routes.cpp (81%) create mode 100644 examples/session/session.cpp diff --git a/examples/auth/session_cookies.cpp b/examples/auth/session_cookies.cpp new file mode 100644 index 0000000..e820f73 --- /dev/null +++ b/examples/auth/session_cookies.cpp @@ -0,0 +1,18 @@ +// vix::middleware::HttpPipeline p; + +// p.use(vix::middleware::auth::session({ +// .store = std::make_shared(), +// .secret = "change-me-32bytes-min", +// .cookie_name = "sid", +// .secure = true, +// .same_site = "Lax" +// })); + +// p.use(vix::middleware::auth::jwt({ +// .secret = "jwt-secret", +// })); +// auto *sess = ctx.state_ptr(); +// if (sess) +// { +// sess->set("user_id", "123"); +// } diff --git a/examples/cookies/cookie.cpp b/examples/cookies/cookie.cpp new file mode 100644 index 0000000..82f68bd --- /dev/null +++ b/examples/cookies/cookie.cpp @@ -0,0 +1,21 @@ +#include +#include +using namespace vix; + +int main() +{ + App app; + + app.get("/cookie", [](Request &, Response &res){ + + vix::middleware::cookies::Cookie c; + c.name = "hello"; + c.value = "vix"; + c.max_age = 3600; + vix::middleware::cookies::set(res, c); + + res.text("cookie set"); + }); + + app.run(8080); +} diff --git a/examples/middleware_http/README.md b/examples/middleware_http/README.md new file mode 100644 index 0000000..ab24ae2 --- /dev/null +++ b/examples/middleware_http/README.md @@ -0,0 +1,173 @@ +# HTTP Middleware Mega Example (Vix.cpp) + +This directory contains a **single, large, practical example** demonstrating how to +build HTTP routes and middleware with **Vix.cpp**. + +The goal is to show, in one place, how everything fits together: +routing, middleware, security, parsing, cookies, sessions, caching, etc. + +--- + +## Run the example + +```bash +vix run examples/http_middleware/mega_middleware_routes.cpp +``` + +Server starts on: + +``` +http://127.0.0.1:8080 +``` + +--- + +## Quick sanity check + +```bash +curl -i http://127.0.0.1:8080/ +curl -i http://127.0.0.1:8080/_routes +``` + +--- + +## Cookies example + +### Set a cookie + +```bash +curl -i http://127.0.0.1:8080/api/cookie/set +``` + +You should see a `Set-Cookie` header: + +``` +Set-Cookie: demo=hello; Path=/; Max-Age=3600; HttpOnly; SameSite=Lax +``` + +### Read the cookie + +```bash +curl -i --cookie "demo=hello" http://127.0.0.1:8080/api/cookie/get +``` + +Response: + +```json +{ + "ok": true, + "cookie_demo": "hello", + "has_cookie": true +} +``` + +--- + +## Session example (signed cookie + server store) + +Sessions are handled by the **session middleware**: + +- Session id stored in a signed cookie (`sid`) +- Session data stored server-side +- Automatic creation on first request +- Persisted across requests +- Destroyable (logout) + +### First request (creates session) + +```bash +curl -i http://127.0.0.1:8080/api/session/whoami +``` + +Response example: + +```json +{ + "ok": true, + "session": true, + "sid": "e4a1f0...", + "is_new": true, + "name": "guest", + "visits": 1 +} +``` + +### Persist session using cookie jar + +```bash +curl -i -c jar.txt http://127.0.0.1:8080/api/session/whoami +curl -i -b jar.txt http://127.0.0.1:8080/api/session/whoami +``` + +You should see `visits` increase on each request. + +--- + +### Update session data + +```bash +curl -i -b jar.txt -X POST http://127.0.0.1:8080/api/session/setname/gaspard +``` + +Then: + +```bash +curl -i -b jar.txt http://127.0.0.1:8080/api/session/whoami +``` + +Response: + +```json +{ + "name": "gaspard", + "visits": 3 +} +``` + +--- + +### Logout (destroy session) + +```bash +curl -i -b jar.txt -X POST http://127.0.0.1:8080/api/session/logout +``` + +This will: + +- Delete server-side session +- Clear the session cookie (`Max-Age=0`) + +Next request creates a new session. + +--- + +## What this example demonstrates + +- App routing (`GET`, `POST`, path params) +- Global middleware vs prefix middleware +- Context-based middleware (`adapt_ctx`) +- Legacy HTTP middleware adaptation +- RequestState (typed state storage) +- Cookies (parse + set) +- Sessions (signed cookie + store) +- JSON / Form / Multipart parsing +- API key protection +- RBAC-style guards +- HTTP GET cache +- CSRF, CORS, rate limiting +- Debug and observability patterns + +--- + +## Philosophy + +This file is intentionally **big and repetitive**. + +It is meant to answer: + +> "How do I actually write routes and middleware with Vix.cpp?" + +By reading a single file. + +If you understand this example, you understand **90% of Vix.cpp HTTP middleware usage**. + diff --git a/examples/mega_middleware_routes.cpp b/examples/middleware_http/mega_middleware_routes.cpp similarity index 81% rename from examples/mega_middleware_routes.cpp rename to examples/middleware_http/mega_middleware_routes.cpp index 01035f3..c37bd89 100644 --- a/examples/mega_middleware_routes.cpp +++ b/examples/middleware_http/mega_middleware_routes.cpp @@ -53,6 +53,8 @@ #include #include #include +#include +#include // Some projects place these in different paths; keep includes minimal. // ---------------------------------------------------------------------------- @@ -491,6 +493,113 @@ static void register_api_routes(vix::App &app) "t2_after_api_mw_ms", t2, }), })); }); + + // GET /api/cookie/set + // Sets a cookie "demo" = "hello" + app.get("/api/cookie/set", [](Request &, Response &res) + { + vix::middleware::cookies::Cookie c; + c.name = "demo"; + c.value = "hello"; + c.path = "/"; + c.http_only = true; + c.secure = false; + c.same_site = "Lax"; + c.max_age = 3600; + + vix::middleware::cookies::set(res, c); + + res.json(J::obj({ + "ok", true, + "cookie_set", true, + "name", "demo", + "value", "hello", + })); }); + + // GET /api/cookie/get + // Reads cookie "demo" from request header "cookie" + app.get("/api/cookie/get", [](Request &req, Response &res) + { + auto v = vix::middleware::cookies::get(req, "demo"); + res.json(J::obj({ + "ok", true, + "cookie_demo", v ? *v : "", + "has_cookie", (bool)v, + })); }); + + // GET /api/session/whoami + // Uses Session stored in RequestState by session middleware + app.get("/api/session/whoami", [](Request &req, Response &res) + { + auto *s = req.try_state(); + if (!s) + { + res.status(500).json(J::obj({ + "ok", false, + "error", "session_missing", + "hint", "Session middleware not installed on this route", + })); + return; + } + + // read or init + auto name = s->get("name").value_or("guest"); + long long visits = 0; + + if (auto v = s->get("visits")) + { + try { visits = std::stoll(*v); } catch (...) { visits = 0; } + } + + visits += 1; + s->set("name", name); + s->set("visits", std::to_string(visits)); + + res.json(J::obj({ + "ok", true, + "session", true, + "sid", s->id, + "is_new", s->is_new, + "name", name, + "visits", visits, + })); }); + + // POST /api/session/setname/{name} + app.post("/api/session/setname/{name}", [](Request &req, Response &res) + { + auto *s = req.try_state(); + if (!s) + { + res.status(500).json(J::obj({"ok", false, "error", "session_missing"})); + return; + } + + const std::string name = req.param("name", "guest"); + s->set("name", name); + + res.json(J::obj({ + "ok", true, + "updated", true, + "name", name, + })); }); + + // POST /api/session/logout + // Destroys session and clears cookie + app.post("/api/session/logout", [](Request &req, Response &res) + { + auto *s = req.try_state(); + if (!s) + { + res.status(500).json(J::obj({"ok", false, "error", "session_missing"})); + return; + } + + s->destroy(); + + res.json(J::obj({ + "ok", true, + "logout", true, + })); }); } static void register_dev_routes(vix::App &app) @@ -614,6 +723,20 @@ static void install_api_middlewares(vix::App &app) // 7) Example: legacy HttpMiddleware adaptation (header gate) on exact path // Protect /api/ping with x-demo: 1 install_exact(app, "/api/ping", adapt(mw_require_header("x-demo", "1"))); + // 8) Session middleware for /api/session/ + { + vix::middleware::auth::SessionOptions sopt{}; + sopt.secret = "dev_session_secret"; // required + sopt.cookie_name = "sid"; + sopt.cookie_path = "/"; + sopt.secure = false; // set true behind https + sopt.http_only = true; + sopt.same_site = "Lax"; + sopt.auto_create = true; + sopt.ttl = std::chrono::hours(24 * 7); + + install(app, "/api/session/", adapt_ctx(vix::middleware::auth::session(std::move(sopt)))); + } } static void install_dev_middlewares(vix::App &app) @@ -681,6 +804,12 @@ int main() push("GET", "/dev/trace", "debug route; may be IP-filtered"); push("GET", "/dev/boom", "throws to test dev error handling"); + push("GET", "/api/cookie/set", "sets demo cookie"); + push("GET", "/api/cookie/get", "reads demo cookie"); + + push("GET", "/api/session/whoami", "session counter demo (needs session middleware)"); + push("POST", "/api/session/setname/{name}", "writes session key 'name'"); + push("POST", "/api/session/logout", "destroy session + clear cookie"); res.json(J::obj({ "ok", true, @@ -692,6 +821,12 @@ int main() "For /api/secure/whoami add: -H 'x-api-key: dev_key_123'", "For /api/admin/stats add: -H 'x-user: gaspard' -H 'x-role: admin'", "To bypass cache: -H 'x-vix-cache: bypass'", + "Cookie demo: curl -i http://127.0.0.1:8080/api/cookie/set", + "Then: curl -i --cookie 'demo=hello' http://127.0.0.1:8080/api/cookie/get", + "Session: curl -i http://127.0.0.1:8080/api/session/whoami", + "Reuse cookie: curl -i -c jar.txt http://127.0.0.1:8080/api/session/whoami && curl -i -b jar.txt http://127.0.0.1:8080/api/session/whoami", + "Set name: curl -i -b jar.txt -X POST http://127.0.0.1:8080/api/session/setname/gaspard", + "Logout: curl -i -b jar.txt -X POST http://127.0.0.1:8080/api/session/logout", }), })); }); diff --git a/examples/session/session.cpp b/examples/session/session.cpp new file mode 100644 index 0000000..34d8fcd --- /dev/null +++ b/examples/session/session.cpp @@ -0,0 +1,23 @@ +#include +#include +using namespace vix; + +int main() +{ + App app; + + app.use(middleware::app::adapt_ctx( + middleware::auth::session({.secret = "dev"}) + )); + + app.get("/session", [](Request &req, Response &res){ + auto &s = req.state(); + + int n = s.get("n") ? std::stoi(*s.get("n")) : 0; + s.set("n", std::to_string(++n)); + + res.text("n=" + std::to_string(n)); + }); + + app.run(8080); +} diff --git a/modules/core b/modules/core index 79145bc..ce61119 160000 --- a/modules/core +++ b/modules/core @@ -1 +1 @@ -Subproject commit 79145bc3864d221fa7545f326d290d5ed3b3cbf9 +Subproject commit ce611199e083dc7344901e3167ae871e54421c20 diff --git a/modules/middleware b/modules/middleware index e2adec4..1872527 160000 --- a/modules/middleware +++ b/modules/middleware @@ -1 +1 @@ -Subproject commit e2adec4bedf4c1370bc682df6493a1ba23268d4d +Subproject commit 1872527ecc4a127d4c71bfc469048ee893022481