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_file and key_file. Default: 0

cert_file

Path to TLS certificate file (PEM format). Required if tls is enabled.

key_file

Path to TLS private key file (PEM format). Required if tls is 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.

$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 .gz files 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: gzip header
3. If response body is larger than min_size, it's gzip compressed
4. Content-Encoding: gzip header 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.