Hypersonic
view release on metacpan or search on metacpan
lib/Hypersonic.pm view on Meta::CPAN
return bless {
routes => [],
compiled => 0,
cache_dir => $opts{cache_dir} // '_hypersonic_cache',
# Legacy fallback id: only used by compile() when Digest::MD5
# isn't installable (extremely rare). The active code path uses
# a content hash of the generated C source so identical server
# configurations share the same JIT cache entry across fresh
# perl processes - see compile() for details.
id => int(rand(100000)),
# Server options
host => $opts{host} // '0.0.0.0',
port => $opts{port} // 8080,
# TLS options
tls => $opts{tls} // 0,
cert_file => $opts{cert_file},
key_file => $opts{key_file},
# HTTP/2 support
http2 => $opts{http2} // 0,
# Security hardening options
max_connections => $opts{max_connections} // 10000,
max_request_size => $opts{max_request_size} // 8192,
keepalive_timeout => $opts{keepalive_timeout} // 30,
recv_timeout => $opts{recv_timeout} // 30,
# WebSocket JIT options - granular control
websocket_rooms => $opts{websocket_rooms} // 0, # Enable Room support
max_rooms => $opts{max_rooms} // 1000,
max_clients_per_room => $opts{max_clients_per_room} // 10000,
# Graceful shutdown
drain_timeout => $opts{drain_timeout} // 5,
# JIT extension points
c_helpers => $opts{c_helpers}, # User C helper functions
# Security headers (JIT optimized - pre-computed at compile time)
security_headers => {
'X-Frame-Options' => $security_headers->{'X-Frame-Options'} // 'DENY',
'X-Content-Type-Options' => $security_headers->{'X-Content-Type-Options'} // 'nosniff',
'X-XSS-Protection' => $security_headers->{'X-XSS-Protection'} // '1; mode=block',
'Referrer-Policy' => $security_headers->{'Referrer-Policy'} // 'strict-origin-when-cross-origin',
'Content-Security-Policy' => $security_headers->{'Content-Security-Policy'}, # User must set this
'Strict-Transport-Security' => ($opts{tls} ? ($security_headers->{'Strict-Transport-Security'} // 'max-age=31536000; includeSubDomains') : undef),
'Permissions-Policy' => $security_headers->{'Permissions-Policy'}, # User can optionally set
},
enable_security_headers => $opts{enable_security_headers} // 1, # Enabled by default
# Middleware support
before_middleware => [], # Global before hooks
after_middleware => [], # Global after hooks
# Event backend (optional override)
event_backend => $opts{event_backend},
}, $class;
}
# Route registration methods
sub get { shift->_add_route('GET', @_) }
sub post { shift->_add_route('POST', @_) }
sub put { shift->_add_route('PUT', @_) }
sub del { shift->_add_route('DELETE', @_) }
sub patch { shift->_add_route('PATCH', @_) }
sub head { shift->_add_route('HEAD', @_) }
sub options { shift->_add_route('OPTIONS', @_) }
# Health check endpoint - built-in route for load balancer / k8s probes
sub health_check {
my ($self, $path, $handler) = @_;
$path //= '/health';
# Default handler returns JSON string (JIT compiled as constant)
$handler //= sub {
return '{"status":"ok"}';
};
return $self->get($path => $handler);
}
# Readiness check endpoint - separate from health for k8s
sub ready_check {
my ($self, $path, $handler) = @_;
$path //= '/ready';
$handler //= sub {
return '{"ready":true}';
};
return $self->get($path => $handler);
}
# WebSocket route registration
sub websocket {
my ($self, $path, $handler) = @_;
die "WebSocket path must start with /" unless $path =~ m{^/};
die "WebSocket handler must be a coderef" unless ref($handler) eq 'CODE';
push @{$self->{websocket_routes} //= []}, {
path => $path,
handler => $handler,
pattern => $self->_compile_path_pattern($path),
};
return $self;
}
# Check if any WebSocket routes are registered
sub _has_websocket_routes {
my ($self) = @_;
return scalar @{$self->{websocket_routes} // []};
}
# Match a path against WebSocket routes
sub _match_websocket_route {
my ($self, $path) = @_;
for my $route (@{$self->{websocket_routes} // []}) {
my $pattern = $route->{pattern};
if ($path =~ $pattern) {
# Extract params
my %params;
my @captures = ($path =~ $pattern);
my @param_names = $route->{path} =~ /:(\w+)/g;
for my $i (0..$#param_names) {
$params{$param_names[$i]} = $captures[$i] if defined $captures[$i];
}
return ($route->{handler}, \%params);
}
}
return;
}
# Compile path pattern (reuse for HTTP routes)
sub _compile_path_pattern {
my ($self, $path) = @_;
my $pattern = $path;
$pattern =~ s{:(\w+)}{([^/]+)}g; # :param -> capture group
$pattern =~ s{\*}{(.+)}g; # * -> greedy capture
( run in 1.872 second using v1.01-cache-2.11-cpan-524268b4103 )