Appearance
HTTP Server Guide (vix::server::HTTPServer)
This guide explains how the Vix HTTP server works, how it connects to the Router and Executor, and how to run and stop it cleanly.
This guide matches the current code in:
vix/server/HTTPServer.hppvix/server/HTTPServer.cpp
What HTTPServer is
vix::server::HTTPServer is an asynchronous HTTP server built on Boost.Asio and Boost.Beast.
It is responsible for:
- Binding a TCP acceptor on the configured port
- Accepting connections asynchronously
- Creating a
vix::session::Sessionfor each client socket - Running the
io_contexton a pool of I/O threads - Exposing a shared
vix::router::Routerused to dispatch requests - Periodically reporting executor metrics
The server delegates request parsing and per-connection HTTP handling to vix::session::Session.
Key dependencies
Boost
boost::asioprovides the I/O event loop (net::io_context), TCP sockets, acceptor, and async accept.boost::beast::httpprovides HTTP request/response types.
Vix components
vix::router::Router: matches method + path and invokes the registered handler.vix::executor::IExecutor: schedules work (used by sessions and for timers/metrics).vix::session::Session: handles one client connection and drives HTTP reads/writes.
Public API
Constructor
cpp
explicit HTTPServer(vix::config::Config &config,
std::shared_ptr<vix::executor::IExecutor> exec);What it does:
- Stores references to
Configand the executor - Creates
io_context - Creates an empty
Router - Installs a default JSON 404 handler via
router_->setNotFoundHandler(...)- Special case: if the request method is
HEAD, it returns no body
- Special case: if the request method is
run()
Starts the server:
- Initializes and binds the acceptor if not yet open
- Starts the async accept loop
- Starts periodic metrics reporting
- Starts I/O worker threads that call
io_context_->run()
Important:
run()does not block. It starts threads and returns.
start_accept()
Starts an async accept operation:
- Creates a
tcp::socket - Calls
acceptor_->async_accept(...) - On success, calls
handle_client(socket) - If stop is not requested, schedules the next accept
calculate_io_thread_count()
Thread count policy:
- If
config_.getIOThreads()is set and > 0, it forces that value - Otherwise it uses
std::thread::hardware_concurrency()(minimum 1)
getRouter()
Returns the shared router:
cpp
std::shared_ptr<vix::router::Router> getRouter();Use this to register routes before calling run().
monitor_metrics()
Schedules a recurring timer using the executor:
- Every 5 seconds, reads
executor_->metrics() - Logs: pending, active, timed_out
This uses:
cpp
vix::timers::interval(*executor_, std::chrono::seconds(5), ...);stop_async()
Requests shutdown:
- Sets
stop_requested_ = true - Closes acceptor (if open)
- Stops the io_context
This stops accepting new connections and causes io_context_->run() to return on I/O threads.
stop_blocking()
Stops and blocks until clean:
executor_->wait_idle()join_threads()
Note:
- In the current implementation,
stop_blocking()does not callstop_async(). You typically callstop_async()first, thenstop_blocking().
join_threads()
Joins all I/O worker threads.
bound_port()
Returns the actual bound port:
- Useful when config port is
0(ephemeral port) - Returns
0if not bound yet
Internal flow
1) Acceptor initialization
init_acceptor(port):
open()set_option(reuse_address=true)bind()listen()- stores the effective port in
bound_port_usinglocal_endpoint().port()
Special handling:
- If bind fails with address-in-use, a clearer exception message is thrown.
2) Accept loop
start_accept() calls async_accept and re-arms itself.
Stop logic:
- If
stop_requested_is true, the loop stops re-arming accepts.
3) Session creation
When a socket is accepted:
handle_client(socket)creates avix::session::Session- It passes:
- socket
- router
- config
- executor
- Calls
session->run()
All request routing happens inside the session using the router.
4) I/O threads
start_io_threads():
- Spawns N threads
- Each thread calls
io_context_->run()
Linux only:
set_affinity(i)pins each I/O thread to a CPU usingpthread_setaffinity_np
Typical usage
Setup and run
Pseudo example:
cpp
vix::config::Config cfg;
auto exec = vix::experimental::make_threadpool_executor(4, 32, 0);
vix::server::HTTPServer server(cfg, exec);
auto r = server.getRouter();
r->add_route(boost::beast::http::verb::get, "/health",
std::make_shared<vix::vhttp::RequestHandler<...>>(...));
server.run();
// keep main alive, or wait on a signalGraceful shutdown
A typical pattern:
- request stop
- wait for threads to finish
cpp
server.stop_async();
server.stop_blocking();Notes and gotchas
Port validation
In run():
- If port is not 0 and < 1024, or > 65535, it throws.
That means:
- privileged ports (<1024) are intentionally rejected unless the config uses port 0 or >=1024.
HEAD requests
Not found handler:
- For
HEAD, it does not write a body.
Router layer:
- The router already has special handling for
HEADby trying to matchGETif noHEADroute exists. - The not-found handler still treats HEAD separately.
stop_blocking() does not stop
Current behavior:
stop_blocking()only waits for the executor to become idle and joins threads.- It does not close the acceptor or stop the io_context.
- Call
stop_async()beforestop_blocking()if you want the threads to exit.
close_socket()
There is a helper to shutdown and close a socket safely, but it is not used inside HTTPServer.cpp right now. Sessions likely manage their own socket lifecycle.
How to extend
Custom 404
Replace the default:
cpp
server.getRouter()->setNotFoundHandler([](auto& req, auto& res) {
// build your response
});Dedicated executors (heavy routes)
Heavy routes are detected and stored in Router nodes as heavy. A session or dispatcher can use:
router.is_heavy(req)to decide which executor to schedule on.
Checklist for production
- Set
config_.getIOThreads()explicitly if you need predictable I/O thread count. - Make sure your executor is sized for your workload (CPU-bound vs I/O-bound).
- Always implement a signal handler to call
stop_async()thenstop_blocking(). - Ensure logging level is appropriate (metrics logs currently use Debug).