Sessions
This page explains HTTP sessions in Vix Core.
Use it when you want to understand how one client connection is processed, how requests are read, how responses are written, how keep-alive works, how timeouts are handled, and how sessions use transports.
Public header
#include <vix.hpp>You can also include the session header directly:
#include <vix/session/Session.hpp>What sessions provide
A session represents one connected client.
It is responsible for:
- reading bytes from a transport
- parsing HTTP request headers
- reading request bodies
- creating
vix::Request - applying basic request validation
- dispatching requests to the router
- writing
vix::Response - handling keep-alive
- handling request timeouts
- closing the connection safely
Most applications do not create sessions directly.
They use:
vix::App app;
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Hello");
});
app.run(8080);Session role in Core
A session sits between the HTTP server and the router.
HTTPServer
-> Session
-> Transport
-> Request parser
-> Router
-> RequestHandler
-> Response writerThe server accepts a TCP connection.
The session owns the lifecycle of that connection.
accepted TCP stream
-> Session
-> read request
-> dispatch request
-> send response
-> repeat or closeBasic request flow
For one request, the session flow is:
read raw bytes
-> parse request head
-> read body
-> build Request
-> dispatch to Router
-> receive Response
-> serialize response
-> write bytesApplication code only sees:
app.get("/hello", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Hello from Vix");
});Session lifecycle
A session starts with run().
Session::run
-> read_request
-> dispatch_request
-> send_response
-> repeat while transport is open
-> close_stream_gracefullyA session can process more than one request if the connection stays open.
request 1
-> response 1
request 2
-> response 2
closeSession and Transport
The session does not read directly from a concrete TCP or TLS type.
It uses the Transport interface.
Transport
-> async_read
-> async_write
-> is_open
-> closeThis keeps the HTTP session independent from whether the connection is plain HTTP or HTTPS.
PlainTransport
-> normal TCP
TlsTransport
-> TLS-encrypted TCPPlain session
For normal HTTP:
tcp_stream
-> PlainTransport
-> SessionThe plain transport adapts vix::async::net::tcp_stream to the generic session transport interface.
TLS session
For HTTPS:
tcp_stream
-> TlsSession
-> TlsTransport
-> TLS handshake
-> SessionAfter the TLS handshake, the same Session logic is used.
This avoids duplicating HTTP parsing and response writing.
Reading a request
The session reads a request in stages.
read_header_block
-> parse_request_head
-> read_request_body
-> make_requestThis produces a native Vix request object:
vix::http::RequestApplication handlers receive it as:
vix::Request &reqHeader block
The session reads raw bytes until it finds the HTTP header terminator.
\r\n\r\nThe header block contains:
GET /path HTTP/1.1
Host: localhost:8080
Connection: keep-aliveOnce the header terminator is found, the session can parse the request line and headers.
Request head
The parsed request head contains:
method
target
version
headers
content_length
keep_aliveExample:
GET /users/42?page=1 HTTP/1.1
Host: localhost:8080
Connection: keep-alivebecomes:
method = GET
target = /users/42?page=1
version = HTTP/1.1
keep_alive = trueRequest body
If the request has a body, the session reads it according to Content-Length.
Example:
POST /api/users HTTP/1.1
Content-Type: application/json
Content-Length: 16
{"name":"Ada"}The session reads the body and stores it in the Request.
req.body();Request size limits
The session protects the server from oversized requests.
The default maximum request body size is:
10 MBIf the request body is too large, the session can return:
413 Payload Too LargeBuilding Request
After parsing, the session builds a native request.
ParsedRequestHead + body
-> vix::http::RequestThe request contains:
- method
- target
- path
- query string
- headers
- body
- route parameters later added by the handler adapter
- request state
Example handler:
app.post("/echo", [](vix::Request &req, vix::Response &res)
{
res.json({
{"method", req.method()},
{"path", req.path()},
{"body", req.body()}
});
});Dispatching a request
Once a request is built, the session dispatches it to the router.
Session::dispatch_request
-> Router::handle_request
-> RequestHandler
-> user handlerExample:
app.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"status", "ok"}
});
});The router matches:
GET /statusthen runs the registered handler.
Sending a response
After the handler writes a response, the session serializes it.
The session writes:
status line
headers
blank line
bodyExample response:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 2
Server: Vix.cpp
OKApplication code writes the response through vix::Response.
res.text("OK");The session handles serialization and transport writes.
Small responses
For small responses, the session can combine headers and body into one write buffer.
headers + body
-> write_allThis reduces overhead for small HTTP responses.
Larger responses
For larger responses, the session can write headers first, then the body.
write headers
write bodyThis avoids unnecessary extra copying for bigger response bodies.
Writing all bytes
The session writes until the full response is sent.
while written < size
-> async_write remaining bytesIf the transport writes 0 bytes or fails, the session closes the connection.
Keep-alive
The session can keep a connection open after sending a response.
Simplified flow:
read request
-> send response
-> if keep-alive
read next request
else
close connectionThis allows multiple HTTP requests on the same connection.
Connection close
A request or response can ask to close the connection.
Example request header:
Connection: closeThe response can also mark itself as close.
res.res.set_should_close(true);
res.header("Connection", "close");Most application code does not need to control this manually.
Default response headers
Before sending, the session ensures important headers exist.
Common headers include:
Server: Vix.cpp
Date: <http date>
Content-Length: <body size>
Connection: keep-aliveIf the handler already set a header, the session keeps the handler value.
Timeouts
The session can start a per-request timeout.
The timeout protects the server from slow or stuck connections.
Conceptually:
start timer
-> wait for configured timeout
-> close connection if request is still activeWhen the request finishes, the timer is cancelled.
read request complete
-> cancel timer
write response complete
-> cancel timerCancellation
Session timeouts use cancellation tokens.
cancel_source
-> cancel_token
-> async read/write/timer operationsWhen cancellation is requested, pending operations can stop.
This keeps shutdown and timeout behavior predictable.
Normal disconnects
Some disconnects are normal and should not be treated as fatal server errors.
Examples:
client closed connection
EOF
connection reset
broken pipe
operation canceled
timeoutThe session treats these as expected connection lifecycle events.
Malformed requests
If a request is malformed, the session can send:
400 Bad RequestExample response:
{
"message": "Malformed HTTP request"
}After that, the session can close the connection.
WAF checks
The session applies basic request checks before dispatching to the router.
These checks can block suspicious requests before they reach user handlers.
The checks can include:
- target length
- invalid target characters
- suspicious URL patterns
- suspicious body patterns
- maximum body size
When a request is blocked, the session can return:
400 Bad RequestBenchmark mode
In benchmark mode, the session can use a very fast path for benchmark routes.
This is useful for measuring the raw HTTP stack with minimal application overhead.
Normal application code does not need to interact with benchmark mode directly.
Closing a session
A session closes the stream gracefully when it exits.
close_stream_gracefully
-> cancel active timer
-> close transportThe close path is best-effort.
Close errors are ignored because the connection is already ending.
Session with plain HTTP
Example flow:
HTTPServer accepts tcp_stream
-> creates Session
-> Session creates PlainTransport
-> Session::run
-> read HTTP request
-> router dispatch
-> write HTTP responseApplication code:
#include <vix.hpp>
int main()
{
vix::App app;
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("plain HTTP");
});
app.run(8080);
return 0;
}Session with HTTPS
Example flow:
HTTPServer accepts tcp_stream
-> creates TlsSession
-> TlsSession creates TlsTransport
-> TLS handshake
-> creates Session with TLS transport
-> Session::runApplication code remains the same.
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("secure HTTP");
});TLS is configured through server configuration.
Session and Router
The session sends parsed requests to the router.
Session
-> Router::handle_requestThe router finds the correct handler.
method + path
-> RouteNode
-> RequestHandlerThen the handler writes a response.
res.json({
{"status", "ok"}
});Session and Response
The session is responsible for turning a response object into HTTP bytes.
vix::http::Response
-> status line
-> headers
-> body
-> transport.async_writeThis means handlers do not write raw HTTP manually.
They use:
res.text("OK");
res.json({{"ok", true}});
res.file("public/index.html");Session and async
Sessions are asynchronous.
They use vix::async::core::task<void>.
Important operations are awaited:
co_await read_request()
co_await dispatch_request()
co_await send_response()
co_await close_stream_gracefully()The session runs on the HTTP server I/O context.
Session and runtime executor
The session receives the runtime executor from the HTTP server.
HTTPServer
-> Session
-> RuntimeExecutorThe executor exists so application work can be connected to vix::runtime.
This keeps the architecture ready for separating I/O work from runtime execution.
Session architecture
The simplified architecture is:
Session
-> Transport
-> Config
-> Router
-> RuntimeExecutor
-> read buffer
-> cancel sourceThe session depends on these components but keeps its main job focused:
read request
dispatch request
send response
close connectionComplete example
#include <vix.hpp>
int main()
{
vix::App app;
app.get("/", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.text("Home");
});
app.get("/api/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"status", "ok"}
});
});
app.post("/api/echo", [](vix::Request &req, vix::Response &res)
{
res.json({
{"body", req.body()}
});
});
app.run(8080);
return 0;
}Request flow for /api/status:
client
-> TCP connection
-> HTTPServer
-> Session
-> read request
-> Router
-> handler
-> response JSON
-> Session writes response
-> clientAPI summary
| API | Purpose |
|---|---|
Session(stream, router, config, executor) | Create a session from a TCP stream. |
Session(transport, router, config, executor) | Create a session from a generic transport. |
run() | Start the session lifecycle. |
read_request() | Read and parse the next request. |
dispatch_request(req) | Dispatch a request to the router. |
send_response(res) | Serialize and write a response. |
send_error(status, msg) | Send a standard error response. |
close_stream_gracefully() | Close the transport safely. |
Most of these methods are internal to Core.
Application code usually works with:
vix::App
vix::Request
vix::ResponseBest practices
Use vix::App instead of creating sessions manually.
vix::App app;
app.run(8080);Keep handlers small and clear.
app.get("/status", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({{"status", "ok"}});
});Return immediately after sending errors.
if (!allowed)
{
res.status(403).json({{"error", "forbidden"}});
return;
}Do not write raw HTTP from handlers.
res.text("OK");Use configuration for timeouts, WAF, TLS, and server behavior.
vix::config::Config cfg;
app.run(cfg);Next steps
Read the next pages: