NAME
Hypersonic - Blazing fast HTTP server with JIT-compiled C event loop
SYNOPSIS
use Hypersonic;
my $server = Hypersonic->new();
# Handlers return STRINGS - they run ONCE at compile time
$server->get('/api/hello' => sub {
'{"message":"Hello, World!"}'
});
$server->get('/health' => sub {
'OK'
});
# Compile routes - generates C code and compiles via XS::JIT
$server->compile();
# Run the server
$server->run(
port => 8080,
workers => 4,
);
DESCRIPTION
Hypersonic is a benchmark-focused micro HTTP server that uses XS::JIT to generate and compile C code at runtime. The entire event loop runs in C with no Perl in the hot path.
What it does:
- 1. Static route handlers run ONCE at
compile()time - 2. Response strings (including HTTP headers) are baked into C as static constants
- 3. Dynamic routes run Perl handlers per-request with JIT-compiled request objects
- 4. A pure C event loop (kqueue/epoll) is generated and compiled
- 5. Security headers are pre-computed and baked into responses at compile time
- 6. Optional TLS/HTTPS support via OpenSSL with JIT-compiled wrappers
Performance: ~290,000 requests/second on a single core (macOS/kqueue).
METHODS
new
my $server = Hypersonic->new(%options);
Create a new Hypersonic server instance.
Options:
- cache_dir
-
Directory for caching compiled XS modules. Default:
_hypersonic_cache - tls
-
Enable TLS/HTTPS support. Requires
cert_fileandkey_file. Default:0 - cert_file
-
Path to TLS certificate file (PEM format). Required if
tlsis enabled. - key_file
-
Path to TLS private key file (PEM format). Required if
tlsis enabled. - max_connections
-
Maximum number of concurrent connections. Default:
10000 - max_request_size
-
Maximum request size in bytes. Default:
8192 - keepalive_timeout
-
Keep-alive connection timeout in seconds. Default:
30 - recv_timeout
-
Receive timeout in seconds. Default:
30 - drain_timeout
-
Graceful shutdown drain timeout in seconds. Default:
5 - enable_security_headers
-
Enable security headers (X-Frame-Options, X-Content-Type-Options, etc.). Default:
1 - security_headers
-
HashRef of custom security header values. Example:
security_headers => { 'X-Frame-Options' => 'SAMEORIGIN', 'Content-Security-Policy' => "default-src 'self'", }
Example with TLS:
my $server = Hypersonic->new(
tls => 1,
cert_file => '/path/to/cert.pem',
key_file => '/path/to/key.pem',
);
get
$server->get('/path' => sub { ... });
$server->get('/path' => sub { ... }, \%options);
Register a GET route handler.
post
$server->post('/path' => sub { ... });
Register a POST route handler.
put
$server->put('/path' => sub { ... });
Register a PUT route handler.
del
$server->del('/path' => sub { ... });
Register a DELETE route handler.
patch
$server->patch('/path' => sub { ... });
Register a PATCH route handler.
head
$server->head('/path' => sub { ... });
Register a HEAD route handler.
options
$server->options('/path' => sub { ... });
Register an OPTIONS route handler.
Route Handler Options
All route methods accept an optional hashref as the third argument:
$server->get('/path' => sub { ... }, {
dynamic => 1, # Force dynamic handler
parse_query => 1, # Parse query string
parse_headers => 1, # Parse HTTP headers
parse_cookies => 1, # Parse Cookie header
parse_json => 1, # Parse JSON body
parse_form => 1, # Parse form-urlencoded body
before => [\&mw1], # Per-route before middleware
after => [\&mw2], # Per-route after middleware
});
static
$server->static('/static' => './public');
$server->static('/assets' => './assets', \%options);
Serve static files from a directory. Files are read at compile time and baked into C string constants for maximum performance.
Arguments:
- url_prefix
-
URL path prefix for static files (e.g.,
/static) - directory
-
Filesystem directory containing files
- options (optional)
-
HashRef with options:
- max_age
-
Cache-Control max-age in seconds. Default:
3600 - etag
-
Generate ETag headers (MD5 hash). Default:
1 - index
-
Directory index file. Default:
index.html - gzip
-
Serve
.gzfiles if available. Default:0
Example:
# Serve files from ./public at /static/*
$server->static('/static' => './public', {
max_age => 86400, # Cache for 1 day
etag => 1, # Enable ETags
});
# Multiple static directories
$server->static('/assets' => './assets');
$server->static('/images' => './img');
Supported MIME types:
HTML, CSS, JavaScript, JSON, XML, PNG, JPEG, GIF, SVG, WebP, WOFF2, PDF, and many more. Unknown extensions default to application/octet-stream.
Performance:
Static files are fully JIT-compiled - the complete HTTP response (headers + body) is baked into C as a string constant. Zero Perl overhead at request time.
Static vs Dynamic Routes
Static routes have handlers that run once at compile time:
# Handler runs ONCE, response is baked into C
$server->get('/health' => sub { '{"status":"ok"}' });
Dynamic routes have handlers that run per-request:
# Automatic: path parameters make it dynamic
$server->get('/users/:id' => sub {
my ($req) = @_;
return '{"id":"' . $req->param('id') . '"}';
});
# Explicit: force dynamic with option
$server->post('/api/data' => sub {
my ($req) = @_;
return '{"received":"' . $req->body . '"}';
}, { dynamic => 1 });
Dynamic handlers receive a Hypersonic::Request object with:
$req->method # HTTP method
$req->path # Request path
$req->body # Request body
$req->param('name') # Path parameter by name
$req->query_param('key') # Query string parameter
$req->header('name') # Request header
$req->cookie('name') # Cookie value
$req->json # Parsed JSON body (hashref)
$req->form_param('key') # Form field value
Response Formats
Handlers can return several formats:
# Simple string (status 200, text/plain or auto-detect JSON)
return '{"status":"ok"}';
# ArrayRef: [status, headers, body]
return [201, { 'Content-Type' => 'application/json' }, '{"id":1}'];
# HashRef: { status, headers, body }
return { status => 200, headers => {}, body => 'hello' };
# Hypersonic::Response object
use Hypersonic::Response 'res';
return res->status(201)->json({ id => 1 });
before
$server->before(sub {
my ($req) = @_;
# Return undef to continue, or a response to short-circuit
return;
});
Register global before middleware. Runs before every dynamic route handler. Return a response to short-circuit (skip the handler and after middleware). Return undef to continue to the handler.
after
$server->after(sub {
my ($req, $response) = @_;
# Can modify and return the response
return $response;
});
Register global after middleware. Runs after every dynamic route handler. Receives the request and response, can modify and return the response.
session_config
$server->session_config(
secret => 'your-secret-key-at-least-16-chars',
cookie_name => 'sid', # Default: 'hsid'
max_age => 86400, # Default: 86400 (1 day)
path => '/', # Default: '/'
httponly => 1, # Default: 1
secure => 1, # Default: 0
samesite => 'Strict', # Default: 'Lax'
);
Enable session support with signed cookies and in-memory storage. Sessions are automatically loaded before each request and saved after.
Configuration options:
secret (required) - HMAC-SHA256 signing key (minimum 16 chars)
cookie_name - Session cookie name (default: 'hsid')
max_age - Session lifetime in seconds (default: 86400)
path - Cookie path (default: '/')
httponly - Set HttpOnly flag (default: 1)
secure - Set Secure flag for HTTPS (default: 0)
samesite - SameSite attribute: 'Strict', 'Lax', 'None' (default: 'Lax')
Usage in handlers:
$server->post('/login' => sub {
my ($req) = @_;
my $data = $req->json;
if (authenticate($data->{user}, $data->{pass})) {
$req->session('user', $data->{user});
$req->session('logged_in', 1);
$req->session_regenerate; # Security: regenerate after login
return res->json({ success => 1 });
}
return res->unauthorized('Invalid credentials');
}, { dynamic => 1, parse_json => 1 });
$server->get('/profile' => sub {
my ($req) = @_;
my $user = $req->session('user') // 'guest';
return res->json({ user => $user });
}, { dynamic => 1 });
$server->post('/logout' => sub {
my ($req) = @_;
$req->session_clear;
return res->json({ logged_out => 1 });
}, { dynamic => 1 });
See Hypersonic::Session for more details.
compress
$server->compress(
min_size => 1024, # Default: 1024 bytes
level => 6, # Default: 6 (1=fastest, 9=smallest)
);
Enable JIT-compiled gzip compression for dynamic responses. Responses are compressed in C using zlib for maximum performance.
Configuration options:
min_size - Minimum response size to compress (default: 1024 bytes)
level - Compression level 1-9 (default: 6)
How it works:
- 1. At compile time, zlib compression code is JIT-compiled into the server
- 2. For each request, the C code checks
Accept-Encoding: gzipheader - 3. If response body is larger than
min_size, it's gzip compressed - 4.
Content-Encoding: gzipheader is added automatically
Requirements: zlib library must be installed (standard on most systems).
See Hypersonic::Compress for more details.
Middleware Examples
# Authentication middleware
$server->before(sub {
my ($req) = @_;
my $token = $req->header('Authorization');
unless ($token && validate_token($token)) {
return res->unauthorized('Invalid token');
}
return; # Continue to handler
});
# Logging middleware
$server->after(sub {
my ($req, $res) = @_;
warn "[" . $req->method . "] " . $req->path . "\n";
return $res;
});
# Per-route middleware
$server->get('/admin/:id' => sub { ... }, {
before => [\&require_admin],
after => [\&audit_log],
});
compile
$server->compile();
Compile all registered routes into JIT'd native code. This:
- 1. Executes static handlers once to get response strings
- 2. Analyzes which features each route needs (JIT philosophy)
- 3. Generates C code with responses as static constants
- 4. Generates dynamic handler caller with only needed parsing
- 5. Compiles via XS::JIT
Must be called after all routes are registered, before run().
dispatch
my $response = $server->dispatch($request_arrayref);
Dispatch a request and return the response. Primarily for testing.
Request is an arrayref: [method, path, body, keep_alive, fd]
run
$server->run(port => 8080, workers => 4);
Start the HTTP server event loop.
Options:
- port
-
Port to listen on. Default:
8080 - workers
-
Number of worker processes. Default:
1
FULL EXAMPLE
use Hypersonic;
use Hypersonic::Response 'res';
my $server = Hypersonic->new(
max_request_size => 16384,
enable_security_headers => 1,
);
# Global middleware
$server->before(sub {
my ($req) = @_;
# Log request
warn $req->method . ' ' . $req->path . "\n";
return; # Continue
});
# Static route (runs once at compile time)
$server->get('/health' => sub {
'{"status":"ok"}'
});
# Dynamic route with path parameter
$server->get('/users/:id' => sub {
my ($req) = @_;
my $id = $req->param('id');
return res->json({ id => $id, name => "User $id" });
});
# POST with JSON body
$server->post('/users' => sub {
my ($req) = @_;
my $data = $req->json;
return res->status(201)->json({ created => $data->{name} });
}, { parse_json => 1 });
# Query parameters
$server->get('/search' => sub {
my ($req) = @_;
my $q = $req->query_param('q') // '';
return res->json({ query => $q });
}, { dynamic => 1, parse_query => 1 });
$server->compile();
$server->run(port => 8080, workers => 4);
BENCHMARK
======================================================================
Benchmark: Route matching for GET /api/hello
======================================================================
Comparison (higher is better):
Rate Dancer2 HTTP_Router Mojolicious Plack Hypersonic
Dancer2 17713/s -- -83% -91% -100% -100%
HTTP_Router 107178/s 505% -- -45% -97% -99%
Mojolicious 196110/s 1007% 83% -- -95% -98%
Plack 3937159/s 22127% 3573% 1908% -- -58%
Hypersonic 9336325/s 52608% 8611% 4661% 137% --
SEE ALSO
Hypersonic::Request - JIT-compiled request object
Hypersonic::Response - Fluent response builder
Hypersonic::Socket - Low-level socket operations
Hypersonic::TLS - TLS/HTTPS support
XS::JIT - The JIT compiler used by Hypersonic
AUTHOR
LNATION <email@lnation.org>
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.