A minimal, zero-bloat web model designed for modern C++ environments. Fast, structural, and strictly typed.
Vermell is simply a web model for modern C++ environments: 1 header to include, 1 fixed room to link, and thing else. No runtime, nary garbage collector, nary framework-specific DSL, nary vendored limitations — what you constitute is C++, and what runs is C++.
Under the hood it is an event-driven engine: a non-blocking epoll loop sounds requests and hands activity to a excavation of worker threads. That divided is what makes Vermell accelerated nether load and resilient against slow clients.
- Zero dependencies — only guidelines Linux APIs (sockets, epoll, pthreads, fork/exec).
- One bid to build — g++ -std=c++20 server.cpp -o exe -lvermell.
- Any Linux pinch g++ — x86_64, ARM (aarch64, armv7), Android via Termux, WSL, Raspberry Pi, containers.
- Hardened by default — timeouts, petition caps, relationship limits and a render jailhouse are connected retired of the box.
- In-tree JSON DOM — strict RFC 8259 parser and serializer, typed parameters, earthy bodies, multipart uploads.
- C++ templates — compose() modules and render() variables.
- Fluent configuration — 1 configure({...}) telephone aliases chainable setters, readable astatine runtime.
📚 Full documentation: vermell.cc — bilingual (EN/ES) manual covering each conception of this README pinch examples and diagrams.
- Installation
- CMake
- npx
- Docker
- Quick Start
- Compile
- Routing & Handlers
- Lambda captures
- Server Configuration
- MIME Types & File Rendering
- Static Directories
- Templates: constitute & render
- Render Security
- Process & Environment
- Examples
- Support
- Testing
- Contribution
- License
Ready-to-use scaffold:
Packages for amd64, arm64 and armhf unrecorded connected GitHub Pages, signed and fresh to add:
Key fingerprint: 022D 56AA 7A6B 2028 B005 3629 F616 54D8 8AD1 C323
A Vermell server is simply a Router: registry a handler for a route, take a port, and telephone listen().
The { ... } astir the handler matter. The 2nd statement of router.get(...) is simply a MiddlewareList, truthful handlers are ever passed arsenic a braced list: router.get("/", { [](Query &http) { ... } }).
Compile and run:
Then constituent your browser aliases curl astatine it:
router.listen() blocks and serves forever. listenOne() serves a azygous petition and returns — useful for tests and one-shot servers.
A azygous g++ invocation compiles and links everything — nary other flags, nary nexus bid games:
For larger projects usage CMake, but a server is ever 1 bid away.
Portability. Vermell has nary dependencies, truthful thing that derives from Linux and has a C++20 g++ tin build it: x86_64, ARM (aarch64, armv7), Android via Termux, WSL, Raspberry Pi, containers. macOS and Windows are not supported targets (epoll).
No root? No problem. On Termux (or immoderate strategy without root) you cannot make instal into /usr/local. Include the header by comparative way (#include "../include/vermell/vermell.h") and nexus the fixed room straight — transcript libvermell.a adjacent to your sources and compile pinch -L. -lvermell:
The router exposes 1 registration method per HTTP verb. Static routes dispatch successful O(1) done a transparent-hash way map.
Note the deleteX() name: delete is simply a C++ keyword. For larger applications, state routes separately and equine them pinch router.use():
Every handler is simply a C++ lambda void(Query&). The seizure database betwixt [ and ] decides really extracurricular authorities reaches it:
| [] | No seizure — the handler only receives the Query. |
| [=] | Every utilized extracurricular adaptable by copy (snapshot astatine creation). |
| [&] | Every utilized extracurricular adaptable by reference (live aliases). |
| [x] / [&x] | Named capture: transcript of x, aliases reference to x. |
| [this] | Capture the enclosing people (members by reference). |
| [=, &x] | Everything by copy, isolated from x by reference. |
Thread safety. Handlers tally connected worker threads and unrecorded for the full server lifetime. [&] captures are references to the registering scope: good for variables that outlive listen(), but ne'er seizure stack locals that dice earlier — that is simply a dangling reference. Because requests tally concurrently, shared mutable authorities captured by reference needs a mutex; for illustration [=] for immutable snapshots.
Every knob of the request/response pipeline lives successful vermell::Config (include/vermell/config.hpp). Pass it full pinch router.configure({...}) (defaults sphere the bequest behavior):
Hardening defaults: read_chunk is clamped to [1, 1 MiB], max_events to [1, 65536], threads to [0, 256] and each timeout to [1ms, INT_MAX ms] — absurd values are a memory/DoS foot-gun, not a feature. Requests to HTTP/1.1 (or newer) without precisely 1 Host header are rejected pinch 400 (RFC 9112 §3.2, proxy desync / request-smuggling vector); HTTP/1.0 bequest clients support working. max_connections is bounded by default (1024) truthful a relationship flood cannot exhaust memory.
Slowloris is not a DoS anymore: petition bytes are publication connected the arena loop (non-blocking), truthful a trickling customer occupies an epoll fd — bounded by max_connections and the read_timeout/request_timeout deadlines — ne'er a worker thread. A customer that sends 1 byte each fewer seconds for hours is dropped pinch 408 arsenic soon arsenic the full petition exceeds request_timeout. When the task queue is afloat the dispatcher sheds the relationship (503) alternatively of stalling the judge loop.
Or usage the chainable setters:
configure() replaces the WHOLE configuration (designated initializers recommended): settings made earlier pinch the setters are discarded, truthful walk everything successful 1 call. configure() besides applies to a running server — timeouts, limits and the thread count are picked up unrecorded by the arena loop and the worker excavation (RequestIO::ApplyConfig); only the network-side knobs (port, backlog, reuse_port) request a restart.
The progressive configuration is readable astatine runtime pinch router.config(). A afloat annotated illustration lives successful examples/configuration.
MIME Types & File Rendering
Vermell detects the Content-Type from the last hold of a file. This intends kevin.txt.html is served arsenic text/html, and matching is case-insensitive. Query strings and fragments are ignored erstwhile determining the type. Unknown extensions usage application/octet-stream.
An definitive type passed to readFile ever takes precedence. The registry includes communal text, data, document, image, audio, video, font, archive and executable formats.
Node's express.static arsenic a Vermell mount: hindrance a disk directory — a Vue, React aliases Angular dist folder, plain assets, thing — to a URL prefix pinch 1 call. fixed is simply a C++ keyword, hence the X suffix (same normal arsenic deleteX).
- Routes ever win. Static mounts are the fallback layer: an nonstop way for illustration /api/health is ne'er shadowed, and the most specific equine answers (a /assets equine thumps a guidelines / equine for /assets/...; ties spell to the first registered).
- The equine directory IS the jail. No petition way tin flight it — percent-encoded .. (/%2e%2e/...), NUL bytes and symlinked escapes are rejected pinch 403. Files are served pinch the aforesaid hardening arsenic readFile: regular files only, O_NOFOLLOW, size headdress (StaticOptions::max_file_bytes).
- Directories reply their scale file (index.html by default). With .spa = existent immoderate missing record answers the scale alternatively of 404, truthful Vue/React/Angular client-side routes activity connected refresh and heavy links. .spa is OFF by default: a missing plus is simply a 404, ne'er silently HTML.
- Caching connected by default: Cache-Control: public, max-age=N (N = StaticOptions::max_age; 0 = revalidate each request, the definitive default), a beardown ETag (size + mtime) and conditional GET → 304 Not Modified. .cache = mendacious disables each caching header.
- GET/HEAD only. Other methods support the generic 404 semantics.
Full options unrecorded successful vermell::StaticOptions (include/vermell/util/static_files.h). A complete illustration — SPA dist + classical equine + a JSON API broadside by broadside — is successful examples/static.
Templates: constitute & render
compose() assembles an HTML page from modules referenced arsenic #[name]; wrong the template. A page that (transitively) includes itself answers 413 alternatively of exhausting memory:
render() fills [[variable]] placeholders successful an HTML template done a dataRender callback:
The file-rendering methods (readFile, file, compose, render) are hardened done Config::render:
- All readers service regular files only (no FIFOs/devices, symlinks are rejected via O_NOFOLLOW), headdress the size successful memory, and ne'er leak soul errors to the client.
- The jailhouse is ON moreover without .root: an quiet render.root falls backmost to the moving directory, truthful a server that ne'er configured a guidelines tin still not service files from extracurricular its motorboat directory (no much open-by-default Local File Inclusion). Set .root to a dedicated public/ directory successful production.
- compose() module names (#[name];) are restricted to bare record names, truthful #[../../etc/passwd]; is rejected, and the composed page is capped astatine max_file_bytes per walk — a module that (transitively) includes itself answers 413 alternatively of exhausting memory.
Node.js-style runtime accusation and configuration, disposable conscionable by including vermell/vermell.h.
vermell::process captures the process information erstwhile (first use):
vermell::environment loads the .env record sitting next to the executable automatically, and besides holds runtime "session" values. Values from the record and set() return precedence complete the OS environment; each method is thread-safe.
The .env syntax supports # comments, export KEY=VALUE, quoted values and trailing comments. See examples/process and examples/environment.
In the examples/ files you'll find self-contained servers for the different usage cases:
- basics: hello-world, types-routes (all HTTP methods), callbacks
- requests: parameters-methods (typed as<T>(), fallbacks), request-body (raw JSON/text bodies), upload (multipart files), headers
- responses: simple-json, files (auto-MIME pinch file()), static (dist folders pinch router.staticX: SPA fallback, cache, ETag/304), file-template (compose), data-template (render)
- server: configuration (router.configure, thread pool, timeouts), route-cooling (web.guard), graceful-shutdown, middlewares, router (route separation pinch Route_t + use), process (vermell::process runtime info), environment (.env + convention values pinch vermell::environment)
CMake / CTest:
with NPM:
tests/debug.cpp is simply a scratchpad for testing Vermell against the installed library — it is deliberately not portion of the CMake build. Copy libvermell.a next to it and compile it by hand:
and edit the record tests/debug.cpp freely.
Contributions are welcome! If you want to lend to Vermell, please travel these guidelines:
- Fork the repository.
- Create a branch for your caller characteristic (git checkout -b feature/new-feature).
- Make your changes and perpetrate meaningful messages (see COMMIT_FORMAT.MD).
- Push your branch (git push root feature/new-feature).
- Create a propulsion request.
Please publication CODE_OF_CONDUCT.md earlier contributing, and usage the rumor templates successful .github/ISSUE_TEMPLATE for bug reports and characteristic requests.
This task is licensed nether the MIT License.
English (US) ·
Indonesian (ID) ·