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 )