The fasthttp module is a high-performance HTTP server library for V that provides low-level socket management and non-blocking I/O.
epoll on Linux for efficient connection handlingkqueue on macOS and BSD for high-performance event notificationThe module is part of the standard V library. Import it in your V code:
import fasthttp
Here's a minimal HTTP server example:
import fasthttp
fn handle_request(req fasthttp.HttpRequest) ![]u8 {
path := req.buffer[req.path.start..req.path.start + req.path.len].bytestr()
mut body := ''
mut status_line := ''
if path == '/' {
body = 'Hello, World!\n'
status_line = 'HTTP/1.1 200 OK'
} else {
body = '${path} not found\n'
status_line = 'HTTP/1.1 404 Not Found'
}
headers := [
status_line,
'Content-Type: text/plain',
'Content-Length: ${body.len}',
'Connection: close',
]
header_string := headers.join('\r\n')
return '${header_string}\r\n\r\n${body}'.bytes()
}
fn main() {
mut server := fasthttp.new_server(fasthttp.ServerConfig{
port: 3000
handler: handle_request
}) or {
eprintln('Failed to create server: ${err}')
return
}
println('Server listening on http://localhost:3000')
server.run() or { eprintln('error: ${err}') }
}
HttpRequest StructRepresents an incoming HTTP request.
Fields:
buffer: []u8 - The raw request buffer containing the complete HTTP requestmethod: Slice - The HTTP method (GET, POST, etc.)path: Slice - The request pathversion: Slice - The HTTP version (e.g., "HTTP/1.1")client_conn_fd: int - Internal socket file descriptorSlice StructRepresents a slice of the request buffer.
Fields:
start: int - Starting index in the bufferlen: int - Length of the sliceUsage:method := req.buffer[req.method.start..req.method.start + req.method.len].bytestr()
path := req.buffer[req.path.start..req.path.start + req.path.len].bytestr()
The handler function receives an HttpRequest and must return either:
[]u8 - A byte array containing the HTTP response bodyfn my_handler(req fasthttp.HttpRequest) ![]u8 {
method := req.buffer[req.method.start..req.method.start + req.method.len].bytestr()
path := req.buffer[req.path.start..req.path.start + req.path.len].bytestr()
match method {
'GET' {
if path == '/' {
return 'Home page'.bytes()
}
}
'POST' {
if path == '/api/data' {
return 'Data received'.bytes()
}
}
else {}
}
return '404 Not Found'.bytes()
}
Responses should be returned as byte arrays. The server will send them directly to the client as HTTP response bodies.
// Simple text response
return 'Hello, World!'.bytes()
// HTML response
return '</html>'.bytes()
// JSON response
return '{"message": "success"}'.bytes()
See the complete example in examples/fasthttp/ for a more
detailed server implementation with multiple routes and controllers.
./v examples/fasthttp
./examples/fasthttp/fasthttp
epoll for high-performance I/O multiplexingkqueue for event notificationfasthttp module is designed for high throughput and low latency-preallocWhen an application is compiled with -prealloc, fasthttp starts a scoped
prealloc arena for each request before decoding the HTTP request and before
calling the request handler. All V allocations made by the request parser, the
handler, and code called by the handler use that request arena while the handler
is running.
The arena is freed as a unit after the response no longer needs request-owned
data. On Linux the normal response path sends the response synchronously, then
ends the request arena. On macOS and BSD the response buffer can be kept by the
connection until kqueue finishes writing it; in that case fasthttp detaches
the scope from the request thread and frees it after the write completes.
This means request-local V allocations are cheap bump-pointer allocations, and
freeing them does not require walking individual objects. Startup state, server
state, and allocations made directly by C libraries are not part of a request
arena. If a handler starts V spawn work while the request scope is active, the
generated thread wrapper retains that scope until the spawned function returns;
void spawned functions also run inside their own scoped arena, which is freed at
thread exit. Manual takeover responses transfer ownership to user code and
currently abandon the request arena, so long-lived takeover handlers should
manage their own allocation lifetime explicitly.
To inspect request arena usage while developing, build with:
v -prealloc -d trace_prealloc run .