File Coverage

blib/lib/Hypersonic.pm
Criterion Covered Total %
statement 580 939 61.7
branch 212 408 51.9
condition 94 259 36.2
subroutine 36 57 63.1
pod 18 21 85.7
total 940 1684 55.8


line stmt bran cond sub pod time code
1             package Hypersonic;
2              
3 35     35   4426742 use strict;
  35         58  
  35         1137  
4 35     35   151 use warnings;
  35         51  
  35         1810  
5 35     35   553 use 5.010;
  35         105  
6              
7             our $VERSION = '0.15';
8              
9 35     35   171 use Scalar::Util qw(blessed);
  35         92  
  35         1887  
10 35     35   13359 use XS::JIT;
  35         31407  
  35         1179  
11 35     35   18208 use XS::JIT::Builder;
  35         43697  
  35         1810  
12 35     35   16413 use Hypersonic::Socket;
  35         75  
  35         212  
13 35     35   17150 use Hypersonic::Protocol::HTTP1;
  35         87  
  35         1114  
14 35     35   174 use Hypersonic::JIT::Util;
  35         43  
  35         147847  
15              
16             # Cache deparser instance for handler analysis (B::Deparse lazy-loaded)
17             my $DEPARSER;
18              
19             # Protocol module for HTTP/1.1 (extensible for HTTP/2 in future)
20             my $PROTOCOL = 'Hypersonic::Protocol::HTTP1';
21              
22             # Optional TLS support
23             my $HAS_TLS = 0;
24             eval { require Hypersonic::TLS; $HAS_TLS = Hypersonic::TLS::check_openssl(); };
25              
26             # Check for HTTP/2 support (nghttp2)
27             my $HAS_HTTP2 = 0;
28             eval { require Hypersonic::Protocol::HTTP2; $HAS_HTTP2 = Hypersonic::Protocol::HTTP2::check_nghttp2() ? 1 : 0; };
29              
30             sub new {
31 48     48 1 1975776 my ($class, %opts) = @_;
32            
33             # Validate TLS options
34 48 50       797 if ($opts{tls}) {
35 0 0       0 die "TLS support not available (OpenSSL not found)" unless $HAS_TLS;
36 0 0       0 die "cert_file required for TLS" unless $opts{cert_file};
37 0 0       0 die "key_file required for TLS" unless $opts{key_file};
38 0 0       0 die "cert_file not found: $opts{cert_file}" unless -f $opts{cert_file};
39 0 0       0 die "key_file not found: $opts{key_file}" unless -f $opts{key_file};
40             }
41            
42             # Validate HTTP/2 options
43 48 50       491 if ($opts{http2}) {
44 0 0       0 die "HTTP/2 requires TLS (set tls => 1)" unless $opts{tls};
45 0 0       0 die "HTTP/2 not available (nghttp2 not found)" unless $HAS_HTTP2;
46             }
47            
48             # Security headers configuration
49 48   100     972 my $security_headers = $opts{security_headers} // {};
50            
51             return bless {
52             routes => [],
53             compiled => 0,
54             cache_dir => $opts{cache_dir} // '_hypersonic_cache',
55             id => int(rand(100000)),
56             # Server options
57             host => $opts{host} // '0.0.0.0',
58             port => $opts{port} // 8080,
59             # TLS options
60             tls => $opts{tls} // 0,
61             cert_file => $opts{cert_file},
62             key_file => $opts{key_file},
63             # HTTP/2 support
64             http2 => $opts{http2} // 0,
65             # Security hardening options
66             max_connections => $opts{max_connections} // 10000,
67             max_request_size => $opts{max_request_size} // 8192,
68             keepalive_timeout => $opts{keepalive_timeout} // 30,
69             recv_timeout => $opts{recv_timeout} // 30,
70             # WebSocket JIT options - granular control
71             websocket_rooms => $opts{websocket_rooms} // 0, # Enable Room support
72             max_rooms => $opts{max_rooms} // 1000,
73             max_clients_per_room => $opts{max_clients_per_room} // 10000,
74             # Graceful shutdown
75             drain_timeout => $opts{drain_timeout} // 5,
76             # JIT extension points
77             c_helpers => $opts{c_helpers}, # User C helper functions
78             # Security headers (JIT optimized - pre-computed at compile time)
79             security_headers => {
80             'X-Frame-Options' => $security_headers->{'X-Frame-Options'} // 'DENY',
81             'X-Content-Type-Options' => $security_headers->{'X-Content-Type-Options'} // 'nosniff',
82             'X-XSS-Protection' => $security_headers->{'X-XSS-Protection'} // '1; mode=block',
83             'Referrer-Policy' => $security_headers->{'Referrer-Policy'} // 'strict-origin-when-cross-origin',
84             'Content-Security-Policy' => $security_headers->{'Content-Security-Policy'}, # User must set this
85             'Strict-Transport-Security' => ($opts{tls} ? ($security_headers->{'Strict-Transport-Security'} // 'max-age=31536000; includeSubDomains') : undef),
86             'Permissions-Policy' => $security_headers->{'Permissions-Policy'}, # User can optionally set
87             },
88             enable_security_headers => $opts{enable_security_headers} // 1, # Enabled by default
89             # Middleware support
90             before_middleware => [], # Global before hooks
91             after_middleware => [], # Global after hooks
92             # Event backend (optional override)
93             event_backend => $opts{event_backend},
94 48 50 100     9523 }, $class;
      50        
      100        
      50        
      50        
      100        
      100        
      100        
      100        
      50        
      50        
      50        
      100        
      100        
      50        
      50        
      50        
      0        
      100        
95             }
96              
97             # Route registration methods
98 56     56 1 1719 sub get { shift->_add_route('GET', @_) }
99 8     8 1 242 sub post { shift->_add_route('POST', @_) }
100 1     1 1 10 sub put { shift->_add_route('PUT', @_) }
101 1     1 1 18 sub del { shift->_add_route('DELETE', @_) }
102 0     0 1 0 sub patch { shift->_add_route('PATCH', @_) }
103 0     0 1 0 sub head { shift->_add_route('HEAD', @_) }
104 0     0 1 0 sub options { shift->_add_route('OPTIONS', @_) }
105              
106             # Health check endpoint - built-in route for load balancer / k8s probes
107             sub health_check {
108 1     1 0 391 my ($self, $path, $handler) = @_;
109 1   50     15 $path //= '/health';
110            
111             # Default handler returns JSON string (JIT compiled as constant)
112             $handler //= sub {
113 0     0   0 return '{"status":"ok"}';
114 1   33     11 };
115            
116 1         5 return $self->get($path => $handler);
117             }
118              
119             # Readiness check endpoint - separate from health for k8s
120             sub ready_check {
121 1     1 0 286 my ($self, $path, $handler) = @_;
122 1   50     6 $path //= '/ready';
123            
124             $handler //= sub {
125 0     0   0 return '{"ready":true}';
126 1   33     5 };
127            
128 1         3 return $self->get($path => $handler);
129             }
130              
131             # WebSocket route registration
132             sub websocket {
133 4     4 1 50 my ($self, $path, $handler) = @_;
134            
135 4 50       17 die "WebSocket path must start with /" unless $path =~ m{^/};
136 4 50       12 die "WebSocket handler must be a coderef" unless ref($handler) eq 'CODE';
137            
138 4   100     2 push @{$self->{websocket_routes} //= []}, {
  4         31  
139             path => $path,
140             handler => $handler,
141             pattern => $self->_compile_path_pattern($path),
142             };
143            
144 4         11 return $self;
145             }
146              
147             # Check if any WebSocket routes are registered
148             sub _has_websocket_routes {
149 21     21   64 my ($self) = @_;
150 21   100     38 return scalar @{$self->{websocket_routes} // []};
  21         279  
151             }
152              
153             # Match a path against WebSocket routes
154             sub _match_websocket_route {
155 3     3   2381 my ($self, $path) = @_;
156            
157 3   50     6 for my $route (@{$self->{websocket_routes} // []}) {
  3         13  
158 5         7 my $pattern = $route->{pattern};
159 5 100       36 if ($path =~ $pattern) {
160             # Extract params
161 2         2 my %params;
162 2         8 my @captures = ($path =~ $pattern);
163 2         13 my @param_names = $route->{path} =~ /:(\w+)/g;
164 2         6 for my $i (0..$#param_names) {
165 1 50       7 $params{$param_names[$i]} = $captures[$i] if defined $captures[$i];
166             }
167 2         16 return ($route->{handler}, \%params);
168             }
169             }
170 1         2 return;
171             }
172              
173             # Compile path pattern (reuse for HTTP routes)
174             sub _compile_path_pattern {
175 6     6   1075 my ($self, $path) = @_;
176            
177 6         6 my $pattern = $path;
178 6         21 $pattern =~ s{:(\w+)}{([^/]+)}g; # :param -> capture group
179 6         9 $pattern =~ s{\*}{(.+)}g; # * -> greedy capture
180 6         157 return qr{^$pattern$};
181             }
182              
183             # Static file serving - JIT-compiled for maximum performance
184             # Files are read at compile time and baked into C constants
185             sub static {
186 0     0 1 0 my ($self, $url_prefix, $directory, $opts) = @_;
187            
188 0   0     0 $opts //= {};
189 0 0       0 die "URL prefix must start with /" unless $url_prefix =~ m{^/};
190 0 0       0 die "Directory does not exist: $directory" unless -d $directory;
191            
192             # Normalize paths
193 0         0 $url_prefix =~ s{/$}{}; # Remove trailing slash
194 0         0 $directory =~ s{/$}{};
195            
196             # Store static config for compile-time processing
197 0   0     0 push @{$self->{static_dirs} //= []}, {
198             prefix => $url_prefix,
199             directory => $directory,
200             # Options
201             max_age => $opts->{max_age} // 3600, # Cache-Control max-age
202             index => $opts->{index} // 'index.html', # Directory index file
203             etag => $opts->{etag} // 1, # Generate ETags
204 0   0     0 gzip => $opts->{gzip} // 0, # Serve .gz files if available
      0        
      0        
      0        
205             };
206            
207 0         0 return $self;
208             }
209              
210             # MIME type lookup - JIT baked into C at compile time
211             my %MIME_TYPES = (
212             # Text
213             html => 'text/html; charset=utf-8',
214             htm => 'text/html; charset=utf-8',
215             css => 'text/css; charset=utf-8',
216             js => 'application/javascript; charset=utf-8',
217             mjs => 'application/javascript; charset=utf-8',
218             json => 'application/json; charset=utf-8',
219             xml => 'application/xml; charset=utf-8',
220             txt => 'text/plain; charset=utf-8',
221             csv => 'text/csv; charset=utf-8',
222             md => 'text/markdown; charset=utf-8',
223             # Images
224             png => 'image/png',
225             jpg => 'image/jpeg',
226             jpeg => 'image/jpeg',
227             gif => 'image/gif',
228             svg => 'image/svg+xml',
229             ico => 'image/x-icon',
230             webp => 'image/webp',
231             avif => 'image/avif',
232             # Fonts
233             woff => 'font/woff',
234             woff2 => 'font/woff2',
235             ttf => 'font/ttf',
236             otf => 'font/otf',
237             eot => 'application/vnd.ms-fontobject',
238             # Media
239             mp3 => 'audio/mpeg',
240             mp4 => 'video/mp4',
241             webm => 'video/webm',
242             ogg => 'audio/ogg',
243             wav => 'audio/wav',
244             # Documents
245             pdf => 'application/pdf',
246             zip => 'application/zip',
247             gz => 'application/gzip',
248             tar => 'application/x-tar',
249             # Web
250             wasm => 'application/wasm',
251             map => 'application/json',
252             );
253              
254             sub _get_mime_type {
255 0     0   0 my ($path) = @_;
256 0         0 my ($ext) = $path =~ /\.(\w+)$/;
257 0   0     0 return $MIME_TYPES{lc($ext // '')} // 'application/octet-stream';
      0        
258             }
259              
260             # Middleware registration methods
261             # before() - runs before route handler, can short-circuit by returning a response
262             # after() - runs after route handler, can modify response
263             # Accepts either:
264             # - CODE ref (traditional Perl middleware, called via call_sv at runtime)
265             # - Builder object (has build_before/build_after methods, generates inline C at compile time)
266             sub before {
267 0     0 1 0 my ($self, $handler) = @_;
268 0   0     0 my $is_builder = blessed($handler) && ($handler->can('build_before') || $handler->can('build_after'));
269 0 0 0     0 die "Middleware must be a code ref or builder object"
270             unless ref($handler) eq 'CODE' || $is_builder;
271 0         0 push @{$self->{before_middleware}}, $handler;
  0         0  
272 0         0 return $self;
273             }
274              
275             sub after {
276 0     0 1 0 my ($self, $handler) = @_;
277 0   0     0 my $is_builder = blessed($handler) && ($handler->can('build_before') || $handler->can('build_after'));
278 0 0 0     0 die "Middleware must be a code ref or builder object"
279             unless ref($handler) eq 'CODE' || $is_builder;
280 0         0 push @{$self->{after_middleware}}, $handler;
  0         0  
281 0         0 return $self;
282             }
283              
284             # Enable request ID tracing - adds X-Request-ID header
285             # Only loads middleware module when called (JIT philosophy)
286             sub enable_request_id {
287 1     1 0 343 my ($self, %opts) = @_;
288              
289 1         6 require Hypersonic::Middleware::RequestId;
290              
291             # Builder pattern: middleware() returns a builder object, not a coderef
292             # The builder generates inline C at compile time - zero Perl in hot path
293 1         2 push @{$self->{before_middleware}}, Hypersonic::Middleware::RequestId::middleware(%opts);
  1         4  
294 1         2 push @{$self->{after_middleware}}, Hypersonic::Middleware::RequestId::after_middleware(%opts);
  1         2  
295              
296 1         2 return $self;
297             }
298              
299             # Session configuration - enables session support with signed cookies
300             # Only loads session module when called (JIT philosophy)
301             sub session_config {
302 1     1 1 16 my ($self, %opts) = @_;
303            
304 1         6 require Hypersonic::Session;
305            
306             # Configure the session module
307 1         5 my $config = Hypersonic::Session->configure(%opts);
308            
309             # Inject session middleware
310             # Before: Load session from cookie/store
311 1         2 unshift @{$self->{before_middleware}}, Hypersonic::Session::before_middleware();
  1         30  
312            
313             # After: Save session to cookie/store
314 1         2 push @{$self->{after_middleware}}, Hypersonic::Session::after_middleware();
  1         4  
315            
316             # Mark that we need cookies parsed for all dynamic routes
317 1         2 $self->{_session_enabled} = 1;
318            
319 1         3 return $self;
320             }
321              
322             # Async/Future Pool configuration - JIT-compiled thread pool for async operations
323             # Only loads Future/Pool when called (JIT philosophy)
324             sub async_pool {
325 0     0 1 0 my ($self, %opts) = @_;
326              
327 0         0 require Hypersonic::Future;
328 0         0 require Hypersonic::Future::Pool;
329              
330             # Create a Pool instance (OO - allows multiple pools)
331             my $pool = Hypersonic::Future::Pool->new(
332             workers => $opts{workers} // 8,
333 0   0     0 queue_size => $opts{queue_size} // 4096,
      0        
334             );
335              
336             # Store in array for event loop registration
337 0   0     0 push @{$self->{_async_pools} //= []}, $pool;
  0         0  
338              
339             # Mark that async pool is enabled - JIT code gen will include thread pool
340 0         0 $self->{_async_enabled} = 1;
341              
342             # Return the Pool object (not $self anymore)
343 0         0 return $pool;
344             }
345              
346             # Compression configuration - JIT-compiled gzip compression in C
347             # Only loads compression module when called (JIT philosophy)
348             sub compress {
349 0     0 1 0 my ($self, %opts) = @_;
350            
351 0         0 require Hypersonic::Compress;
352            
353             # Check for zlib
354 0 0       0 unless (Hypersonic::Compress::check_zlib()) {
355 0         0 warn "Warning: zlib not found, compression disabled\n";
356 0         0 return $self;
357             }
358            
359             # Configure compression
360 0         0 my $config = Hypersonic::Compress->configure(%opts);
361            
362             # Mark that compression is enabled - JIT code gen will include zlib code
363 0         0 $self->{_compression_enabled} = 1;
364 0         0 $self->{_compression_config} = $config;
365            
366 0         0 return $self;
367             }
368              
369             sub _add_route {
370 66     66   402 my ($self, $method, $path, $handler, $opts) = @_;
371              
372 66 100       629 die "Path must start with /" unless $path =~ m{^/};
373 65 100       276 die "Handler must be a code ref" unless ref($handler) eq 'CODE';
374              
375             # Check for dynamic option and feature flags
376 64         125 my $dynamic = 0;
377 64         810 my %features = (
378             parse_query => 0, # Parse ?key=value query strings
379             parse_headers => 0, # Parse HTTP headers
380             parse_cookies => 0, # Parse Cookie header
381             parse_json => 0, # Parse JSON body (requires Cpanel::JSON::XS)
382             parse_form => 0, # Parse form-urlencoded body
383             response_helpers => 0, # JIT compile response helper methods
384             streaming => 0, # Streaming response handler
385             need_xs_builder => 0, # Handler receives XS::JIT::Builder
386             );
387            
388 64 100       188 if (ref($opts) eq 'HASH') {
389 18 100       62 $dynamic = $opts->{dynamic} ? 1 : 0;
390             # Copy feature flags from options
391 18         90 for my $feat (keys %features) {
392 144 50       249 $features{$feat} = $opts->{$feat} ? 1 : 0 if exists $opts->{$feat};
    100          
393             }
394             }
395              
396             # Parse path parameters (supports multiple: /users/:user_id/posts/:post_id)
397 64         197 my @params;
398 64         510 my @segments = split '/', $path;
399 64         133 shift @segments; # Remove leading empty string
400            
401 64         208 for my $i (0 .. $#segments) {
402 80 100       266 if ($segments[$i] =~ /^:(\w+)$/) {
403 5         23 push @params, { name => $1, position => $i };
404 5         9 $dynamic = 1; # Path params imply dynamic
405             }
406             }
407              
408             # Streaming handlers are always dynamic
409 64 100       213 if ($features{streaming}) {
410 7         8 $dynamic = 1;
411             }
412              
413             # need_xs_builder handlers are always dynamic (but handled specially at compile time)
414 64 50       196 if ($features{need_xs_builder}) {
415 0         0 $dynamic = 1;
416             }
417              
418 64         1681 push @{$self->{routes}}, {
419             method => $method,
420             path => $path,
421             handler => $handler,
422             dynamic => $dynamic,
423             streaming => $features{streaming},
424             need_xs_builder => $features{need_xs_builder},
425             params => \@params,
426             segments => \@segments,
427             features => \%features,
428             # Per-route middleware (optional)
429             before => $opts->{before} // [],
430 64   50     91 after => $opts->{after} // [],
      50        
431             };
432              
433 64         281 return $self;
434             }
435              
436             sub compile {
437 23     23 1 1275 my ($self) = @_;
438              
439 23 100 50     93 die "No routes defined" unless @{$self->{routes}} || @{$self->{static_dirs} // []};
  23   66     183  
  1         17  
440 22 100       104 die "Already compiled" if $self->{compiled};
441              
442             # ============================================================
443             # STATIC FILE PROCESSING - bake files into C at compile time
444             # ============================================================
445 21 50       96 if (my $static_dirs = $self->{static_dirs}) {
446 0         0 $self->_compile_static_files($static_dirs);
447             }
448              
449             # ============================================================
450             # ROUTE ANALYSIS - determine what code to generate (JIT philosophy)
451             # ============================================================
452             my %analysis = (
453             methods_used => {}, # GET => 1, POST => 1, etc.
454             has_dynamic => 0, # Any dynamic routes?
455             has_static => 0, # Any static routes?
456             has_path_params => 0, # Any routes with :param?
457             has_body_access => 0, # Any routes that need body?
458 21         90 route_count => scalar(@{$self->{routes}}),
459             all_same_prefix => undef, # Common prefix like /api/*
460             single_method => undef, # Only one HTTP method used?
461             # JIT feature flags - only generate code for features actually used
462             needs_query => 0, # Any route needs query string parsing?
463             needs_headers => 0, # Any route needs header access?
464             needs_cookies => 0, # Any route needs cookie parsing?
465             needs_json => 0, # Any route needs JSON body parsing?
466             needs_form => 0, # Any route needs form data parsing?
467             needs_response_helpers => 0, # Any route needs response helper methods?
468             needs_streaming => 0, # Any route uses streaming responses?
469             needs_xs_builder => 0, # Any route uses need_xs_builder?
470             # Middleware flags - JIT: only generate middleware code if actually used
471 21         67 has_global_before => scalar(@{$self->{before_middleware}}) > 0,
472 21         59 has_global_after => scalar(@{$self->{after_middleware}}) > 0,
  21         881  
473             has_route_middleware => 0, # Any route has before/after hooks?
474             has_any_middleware => 0, # Global OR per-route middleware?
475             );
476              
477             # First pass: collect method usage and route characteristics
478 21         57 for my $route (@{$self->{routes}}) {
  21         196  
479 47         163 $analysis{methods_used}{$route->{method}} = 1;
480              
481 47 100       192 if ($route->{dynamic}) {
482 11         24 $analysis{has_dynamic} = 1;
483             # Dynamic routes might need body access (POST/PUT/PATCH typically do)
484 11 100       42 if ($route->{method} =~ /^(POST|PUT|PATCH)$/) {
485 3         6 $analysis{has_body_access} = 1;
486             }
487            
488             # JIT FEATURE DETECTION: Analyze handler code to detect what request
489             # features it actually uses. This avoids generating unused parsing code.
490 11   50     45 my $f = $route->{features} // {};
491            
492             # Explicit flags take precedence
493 11 50       75 $analysis{needs_query} = 1 if $f->{parse_query};
494 11 50       26 $analysis{needs_headers} = 1 if $f->{parse_headers};
495 11 50       35 $analysis{needs_cookies} = 1 if $f->{parse_cookies};
496 11 100       26 $analysis{needs_json} = 1 if $f->{parse_json};
497 11 50       23 $analysis{needs_form} = 1 if $f->{parse_form};
498 11 50       147 $analysis{needs_response_helpers} = 1 if $f->{response_helpers};
499 11 100       33 $analysis{needs_streaming} = 1 if $f->{streaming};
500 11 50       21 $analysis{needs_xs_builder} = 1 if $f->{need_xs_builder};
501            
502             # Auto-detect by analyzing handler code
503 11         59 my $handler_code = _deparse_handler($route->{handler});
504 11 50       47 if ($handler_code) {
505             # Look for $req->{query} or ->{query} access patterns
506 11 50       53 $analysis{needs_query} = 1 if $handler_code =~ /\{['"]*query['"]*\}/;
507 11 50       49 $analysis{needs_headers} = 1 if $handler_code =~ /\{['"]*headers['"]*\}/;
508 11 50       45 $analysis{needs_cookies} = 1 if $handler_code =~ /\{['"]*cookies['"]*\}/;
509 11 50       78 $analysis{needs_json} = 1 if $handler_code =~ /\{['"]*json['"]*\}/;
510 11 50       57 $analysis{needs_form} = 1 if $handler_code =~ /\{['"]*form['"]*\}/;
511             }
512             } else {
513 36         68 $analysis{has_static} = 1;
514             }
515              
516 47 50       65 if (@{$route->{params}}) {
  47         156  
517 0         0 $analysis{has_path_params} = 1;
518             }
519            
520             # Check for per-route middleware
521 47 50 33     65 if (@{$route->{before}} || @{$route->{after}}) {
  47         263  
  47         209  
522 0         0 $analysis{has_route_middleware} = 1;
523             }
524             }
525            
526             # Session support: force cookie parsing when sessions are enabled
527 21 100       98 if ($self->{_session_enabled}) {
528 1         4 $analysis{needs_cookies} = 1;
529             }
530            
531             # Compression support: force header parsing to check Accept-Encoding
532 21 50       83 if ($self->{_compression_enabled}) {
533 0         0 $analysis{needs_headers} = 1;
534             }
535              
536             # Async Pool support: enable thread pool integration.
537             # Disabled on Windows - Pool uses pthread + eventfd, neither of
538             # which exists there. (Future/Pool deserve a Win32 port; not in
539             # the 0.14 minimum-viable Windows scope.)
540 21 50       76 if ($self->{_async_enabled}) {
541 0 0       0 if ($^O eq 'MSWin32') {
542 0         0 warn "Hypersonic: async pool not supported on Windows; "
543             . "ignoring.\n";
544             } else {
545 0         0 $analysis{needs_async_pool} = 1;
546             }
547             }
548              
549             # Classify middleware as builder (inline C) or Perl (call_sv)
550             # Builder middleware has build_before/build_after methods and generates C at compile time
551 21         175 my (@builder_before, @perl_before, @builder_after, @perl_after);
552 21         94 for my $mw (@{$self->{before_middleware}}) {
  21         98  
553 1 50 0     4 if (blessed($mw) && ($mw->can('build_before') || $mw->can('build_after'))) {
      33        
554 0         0 push @builder_before, $mw;
555             } else {
556 1         3 push @perl_before, $mw;
557             }
558             }
559 21         65 for my $mw (@{$self->{after_middleware}}) {
  21         53  
560 1 50 0     3 if (blessed($mw) && ($mw->can('build_before') || $mw->can('build_after'))) {
      33        
561 0         0 push @builder_after, $mw;
562             } else {
563 1         2 push @perl_after, $mw;
564             }
565             }
566              
567 21         59 $analysis{builder_before} = \@builder_before;
568 21         95 $analysis{builder_after} = \@builder_after;
569 21         78 $analysis{perl_before} = \@perl_before;
570 21         48 $analysis{perl_after} = \@perl_after;
571 21         194 $analysis{has_builder_before} = @builder_before > 0;
572 21         148 $analysis{has_builder_after} = @builder_after > 0;
573             # Update flags: has_global_* now refers to Perl middleware only (for call_sv)
574 21         152 $analysis{has_global_before} = @perl_before > 0;
575 21         226 $analysis{has_global_after} = @perl_after > 0;
576              
577             # Allocate slots for builder middleware
578             # Request slots 0-15 are reserved for core fields (see Hypersonic::Request)
579             # Middleware slots start at 16
580 21         42 my $next_slot = 16;
581 21         48 my %middleware_slots;
582 21         78 for my $mw (@builder_before, @builder_after) {
583 0 0       0 next unless $mw->can('slot_requirements');
584 0         0 my $reqs = $mw->slot_requirements;
585 0         0 for my $name (keys %$reqs) {
586 0 0       0 next if exists $middleware_slots{$name}; # Deduplicate
587 0         0 $middleware_slots{$name} = $next_slot;
588 0         0 $next_slot += $reqs->{$name};
589             }
590             }
591 21         61 $analysis{middleware_slots} = \%middleware_slots;
592              
593             # Determine if any middleware is present (global or per-route)
594             $analysis{has_any_middleware} = $analysis{has_global_before} ||
595             $analysis{has_global_after} ||
596             $analysis{has_builder_before} ||
597             $analysis{has_builder_after} ||
598 21   33     362 $analysis{has_route_middleware};
599              
600             # Check if only one method is used
601 21         50 my @methods = keys %{$analysis{methods_used}};
  21         224  
602 21 100       70 if (@methods == 1) {
603 16         30 $analysis{single_method} = $methods[0];
604             }
605              
606             # Check for common prefix (e.g., all routes start with /api)
607 21         45 my @paths = map { $_->{path} } @{$self->{routes}};
  47         176  
  21         88  
608 21 100       84 if (@paths > 1) {
609 11         164 my $prefix = _find_common_prefix(@paths);
610 11 50       49 if (length($prefix) > 1) { # More than just "/"
611 0         0 $analysis{all_same_prefix} = $prefix;
612             }
613             }
614              
615             # Store analysis for use in code generation
616 21         47 $self->{route_analysis} = \%analysis;
617              
618             # Compile JIT request accessors (after analysis so we know what features are needed)
619 21         22551 require Hypersonic::Request;
620             Hypersonic::Request->compile_accessors(
621             cache_dir => $self->{cache_dir},
622             response_helpers => $analysis{needs_response_helpers},
623 21         233 );
624              
625             # Pre-evaluate all static handlers and build FULL HTTP responses
626 21         159 my @full_responses;
627             my @dynamic_handlers; # Store CV refs for dynamic routes
628 21         0 my @route_param_info; # Store param info for dynamic routes
629              
630 21         167 for my $i (0 .. $#{$self->{routes}}) {
  21         163  
631 47         158 my $route = $self->{routes}[$i];
632              
633 47 100       202 if (!$route->{dynamic}) {
634             # Check if this is a pre-built static file response
635 36 50       130 if ($route->{_static_file}) {
636 0         0 push @full_responses, $route->{_static_response};
637 0         0 $route->{response_idx} = $#full_responses;
638 0         0 next;
639             }
640            
641             # need_xs_builder routes are handled later in code generation
642 36 50       106 if ($route->{need_xs_builder}) {
643 0         0 $route->{dynamic} = 1; # Treat as dynamic for dispatch
644 0         0 push @dynamic_handlers, $route->{handler};
645 0         0 push @route_param_info, $route->{params};
646 0         0 $route->{handler_idx} = $#dynamic_handlers;
647 0         0 next;
648             }
649            
650             # RUN THE HANDLER ONCE - this is the magic
651 36         176 my $result = $route->{handler}->();
652            
653             # Support both string and [status, headers, body] format
654 36         390 my ($status, $headers, $body);
655 36 100       142 if (ref($result) eq 'ARRAY') {
    100          
656 1         3 ($status, $headers, $body) = @$result;
657 1   50     4 $status //= 200;
658 1   50     5 $headers //= {};
659             } elsif (ref($result) eq 'HASH') {
660 8   100     23 $status = $result->{status} // 200;
661 8   100     38 $headers = $result->{headers} // {};
662 8   50     16 $body = $result->{body} // '';
663             } else {
664 27         70 $status = 200;
665 27         52 $headers = {};
666 27         47 $body = $result;
667             }
668            
669 36 100 66     348 die "Handler for $route->{method} $route->{path} must return a string or response structure"
670             unless defined $body && !ref($body);
671              
672             # Build COMPLETE HTTP response via Protocol module (JIT at compile time)
673             my $security_hdrs = $self->{enable_security_headers}
674 35 50       387 ? $self->_get_security_headers_string()
675             : '';
676            
677 35         573 my $full_response = $PROTOCOL->build_response(
678             status => $status,
679             headers => $headers,
680             body => $body,
681             keep_alive => 1,
682             security_headers => $security_hdrs,
683             );
684              
685 35         75 push @full_responses, $full_response;
686 35         200 $route->{response_idx} = $#full_responses;
687             } else {
688             # Dynamic route - store handler index and param info
689 11         27 push @dynamic_handlers, $route->{handler};
690 11         18 push @route_param_info, $route->{params};
691 11         28 $route->{handler_idx} = $#dynamic_handlers;
692             }
693             }
694              
695             # Store dynamic handlers and param info for runtime access
696 20         69 $self->{dynamic_handlers} = \@dynamic_handlers;
697 20         62 $self->{route_param_info} = \@route_param_info;
698              
699             # JIT: Build streaming handlers lookup table (only if streaming is enabled)
700 20 100       101 if ($self->{route_analysis}{needs_streaming}) {
701 2         3 my @streaming_flags;
702 2         3 for my $route (@{$self->{routes}}) {
  2         6  
703 4 100       12 next unless $route->{dynamic};
704 2 50       6 push @streaming_flags, $route->{streaming} ? 1 : 0;
705             }
706 2         7 $self->{_streaming_flags} = \@streaming_flags;
707             }
708              
709             # JIT: Build WebSocket handlers lookup table
710 20 50       276 if ($self->_has_websocket_routes()) {
711 0         0 my @ws_handlers;
712             my @ws_paths;
713 0         0 for my $route (@{$self->{websocket_routes}}) {
  0         0  
714 0         0 push @ws_handlers, $route->{handler};
715 0         0 push @ws_paths, $route->{path};
716             # Check for Room usage in route options
717 0 0       0 if ($route->{opts}{rooms}) {
718 0         0 $self->{route_analysis}{needs_websocket_rooms} = 1;
719             }
720             }
721 0         0 $self->{_websocket_handlers} = \@ws_handlers;
722 0         0 $self->{_websocket_paths} = \@ws_paths;
723 0         0 $self->{route_analysis}{needs_websocket} = 1;
724             # Handler is needed if we have websocket routes
725 0         0 $self->{route_analysis}{needs_websocket_handler} = 1;
726             # WebSocket uses Stream for connection handling
727 0         0 $self->{route_analysis}{needs_streaming} = 1;
728             }
729              
730             # JIT: Explicit opt-in for Room support (can also be set in new())
731 20 50       97 if ($self->{websocket_rooms}) {
732 0         0 $self->{route_analysis}{needs_websocket_rooms} = 1;
733             }
734              
735             # JIT: Build per-route middleware arrays (only if route middleware is present)
736 20         44 my $analysis = $self->{route_analysis};
737 20 50       69 if ($analysis->{has_route_middleware}) {
738 0         0 my @route_before_mw;
739             my @route_after_mw;
740 0         0 for my $route (@{$self->{routes}}) {
  0         0  
741 0 0       0 next unless $route->{dynamic};
742             # Store arrays of middleware handlers per route (by handler_idx)
743 0         0 push @route_before_mw, $route->{before};
744 0         0 push @route_after_mw, $route->{after};
745             }
746 0         0 $self->{_route_before_mw} = \@route_before_mw;
747 0         0 $self->{_route_after_mw} = \@route_after_mw;
748             }
749              
750             # JIT: Store Perl-only middleware for runtime call_sv dispatch
751             # Builder middleware is handled at compile time (inline C), not runtime
752 20 100 66     248 if ($analysis->{has_global_before} || $analysis->{has_global_after}) {
753 1         7 $self->{_perl_before_mw} = $analysis->{perl_before};
754 1         4 $self->{_perl_after_mw} = $analysis->{perl_after};
755             }
756              
757             # Generate C code with pure C event loop
758 20         206 my $c_code = $self->_generate_server_code(\@full_responses);
759              
760             # Compile via XS::JIT
761 20         150 my $module_name = 'Hypersonic::_Server_' . $self->{id};
762            
763             # Build compile options - add TLS flags if enabled
764 20         318 my %functions = (
765             "${module_name}::run_event_loop" => {
766             source => 'hypersonic_run_event_loop',
767             is_xs_native => 1,
768             },
769             "${module_name}::dispatch" => {
770             source => 'hypersonic_dispatch',
771             is_xs_native => 1,
772             },
773             );
774              
775             # Add Stream and SSE XS functions if streaming is enabled
776 20 100       95 if ($self->{route_analysis}{needs_streaming}) {
777 2         5 %functions = (%functions, %{Hypersonic::Stream->get_xs_functions()});
  2         20  
778 2         15 %functions = (%functions, %{Hypersonic::SSE->get_xs_functions()});
  2         9  
779             }
780              
781             # Add WebSocket XS functions if WebSocket routes are registered
782 20 50       114 if ($self->{route_analysis}{needs_websocket}) {
783 0         0 require Hypersonic::WebSocket;
784 0         0 %functions = (%functions, %{Hypersonic::WebSocket->get_xs_functions()});
  0         0  
785             }
786              
787             # Add WebSocket Handler XS functions
788 20 50       71 if ($self->{route_analysis}{needs_websocket_handler}) {
789 0         0 require Hypersonic::WebSocket::Handler;
790 0         0 %functions = (%functions, %{Hypersonic::WebSocket::Handler->get_xs_functions()});
  0         0  
791             }
792              
793             # Add WebSocket Room XS functions
794 20 50       221 if ($self->{route_analysis}{needs_websocket_rooms}) {
795 0         0 require Hypersonic::WebSocket::Room;
796 0         0 %functions = (%functions, %{Hypersonic::WebSocket::Room->get_xs_functions()});
  0         0  
797             }
798              
799             # Add Future/Pool XS functions if async pool is enabled
800 20 50       71 if ($self->{route_analysis}{needs_async_pool}) {
801 0         0 require Hypersonic::Future;
802 0         0 require Hypersonic::Future::Pool;
803 0         0 %functions = (%functions, %{Hypersonic::Future->get_xs_functions()});
  0         0  
804 0         0 %functions = (%functions, %{Hypersonic::Future::Pool->get_xs_functions()});
  0         0  
805             }
806              
807             # Add need_xs_builder route additional XS functions (if any)
808             # Note: The main handler functions are C functions called by call_xs_builder_handler,
809             # NOT XS functions callable from Perl, so we don't register them.
810 20 50       115 if (my $xsr = $self->{_xs_builder_routes}) {
811 0         0 for my $entry (@$xsr) {
812 0         0 my $result = $entry->{result};
813            
814             # Add any additional XS functions the handler defined
815 0 0       0 if ($result->{xs_functions}) {
816 0         0 %functions = (%functions, %{$result->{xs_functions}});
  0         0  
817             }
818             }
819             }
820              
821             my %compile_opts = (
822             code => $c_code,
823             name => $module_name,
824             cache_dir => $self->{cache_dir},
825 20         522 functions => \%functions,
826             );
827            
828             # Add OpenSSL flags for TLS support
829 20 50       96 if ($self->{tls}) {
830 0         0 $compile_opts{extra_cflags} = Hypersonic::TLS::get_extra_cflags();
831 0         0 $compile_opts{extra_ldflags} = Hypersonic::TLS::get_extra_ldflags();
832             }
833            
834             # Add nghttp2 flags for HTTP/2 support
835 20 50       81 if ($self->{http2}) {
836 0         0 require Hypersonic::Protocol::HTTP2;
837 0         0 my $h2_cflags = Hypersonic::Protocol::HTTP2::get_extra_cflags();
838 0         0 my $h2_ldflags = Hypersonic::Protocol::HTTP2::get_extra_ldflags();
839 0   0     0 $compile_opts{extra_cflags} = ($compile_opts{extra_cflags} // '') . " $h2_cflags";
840 0   0     0 $compile_opts{extra_ldflags} = ($compile_opts{extra_ldflags} // '') . " $h2_ldflags";
841             }
842            
843             # Add zlib flags for compression support
844 20 50       72 if ($self->{_compression_enabled}) {
845 0         0 require Hypersonic::Compress;
846 0         0 my ($cflags, $ldflags) = Hypersonic::Compress::get_zlib_flags();
847 0   0     0 $compile_opts{extra_cflags} = ($compile_opts{extra_cflags} // '') . " $cflags";
848 0   0     0 $compile_opts{extra_ldflags} = ($compile_opts{extra_ldflags} // '') . " $ldflags";
849             }
850              
851             # Add pthread flags for async pool (thread pool)
852 20 50       124 if ($self->{route_analysis}{needs_async_pool}) {
853 0   0     0 $compile_opts{extra_cflags} = ($compile_opts{extra_cflags} // '') . " -pthread";
854 0   0     0 $compile_opts{extra_ldflags} = ($compile_opts{extra_ldflags} // '') . " -lpthread";
855             }
856              
857             # Add event backend flags (e.g., io_uring needs -luring)
858 20 50       65 if ($self->{_event_backend}) {
859 20         147 my $backend = $self->{_event_backend};
860 20 50       532 if ($backend->can('extra_cflags')) {
861 20   50     82 my $ev_cflags = $backend->extra_cflags // '';
862 20 50 0     78 $compile_opts{extra_cflags} = ($compile_opts{extra_cflags} // '') . " $ev_cflags"
863             if $ev_cflags;
864             }
865 20 50       98 if ($backend->can('extra_ldflags')) {
866 20   50     63 my $ev_ldflags = $backend->extra_ldflags // '';
867 20 50 0     58 $compile_opts{extra_ldflags} = ($compile_opts{extra_ldflags} // '') . " $ev_ldflags"
868             if $ev_ldflags;
869             }
870             }
871              
872             # Windows: link Winsock so socket()/recv()/send()/etc. resolve in
873             # the JIT-compiled .so. The select backend already adds this, but
874             # belt-and-braces - the main compile uses these symbols too.
875 20 50       184 if ($^O eq 'MSWin32') {
876 0   0     0 my $ld = $compile_opts{extra_ldflags} // '';
877 0 0       0 $compile_opts{extra_ldflags} = $ld . ' -lws2_32'
878             unless $ld =~ /-lws2_32\b/;
879             }
880              
881 20         12896796 my $ok = XS::JIT->compile(%compile_opts);
882             die "XS::JIT->compile failed for $module_name (check liburing/zlib/openssl "
883             . "are installed and linkable; extra_ldflags='"
884 20 50 0     356 . ($compile_opts{extra_ldflags} // '') . "')"
885             unless $ok;
886              
887             # Store function references - confirm the boot xsub actually installed
888             # them. If we somehow got past compile() with the .so loaded but the
889             # symbols missing, fail loudly here rather than crash later in run().
890             {
891 35     35   318 no strict 'refs';
  35         60  
  35         364356  
  20         48  
892 20         42 $self->{run_loop_fn} = \&{"${module_name}::run_event_loop"};
  20         259  
893 20         53 $self->{dispatch_fn} = \&{"${module_name}::dispatch"};
  20         133  
894             die "XS::JIT loaded $module_name but ::dispatch is not defined "
895             . "(stale cache_dir? link line was: ldflags='"
896             . ($compile_opts{extra_ldflags} // '') . "')"
897 20 50 0     46 unless defined &{"${module_name}::dispatch"};
  20         119  
898             }
899              
900             # Mark Future/Pool as compiled if async pool is enabled
901             # (prevents them from trying to compile separately)
902 20 50       118 if ($self->{route_analysis}{needs_async_pool}) {
903 0         0 $Hypersonic::Future::COMPILED = 1;
904 0         0 $Hypersonic::Future::Pool::COMPILED = 1;
905             # Register custom ops for Future after compilation
906 0         0 Hypersonic::Future->_register_ops();
907             }
908              
909 20         60 $self->{compiled} = 1;
910 20         728 return $self;
911             }
912              
913             sub _generate_server_code {
914 20     20   92 my ($self, $full_responses) = @_;
915              
916             # Load event backend module
917 20         6818 require Hypersonic::Event;
918 20   33     667 my $backend_name = $self->{event_backend} // Hypersonic::Event->best_backend;
919 20         196 my $backend = Hypersonic::Event->backend($backend_name);
920              
921             # Store backend for use in event loop generation
922 20         340 $self->{_event_backend} = $backend;
923 20         160 $self->{_event_backend_name} = $backend_name;
924              
925 20         426 my $builder = XS::JIT::Builder->new;
926              
927             # C99 detection for inline keyword
928 20         461 my $inline = Hypersonic::JIT::Util->inline_keyword;
929              
930             # Check if we have any dynamic routes
931 20         8107 my $has_dynamic = grep { $_->{dynamic} } @{$self->{routes}};
  46         251  
  20         546  
932              
933             # Common includes - portable across POSIX and Windows. On Windows
934             # we substitute Winsock for netinet/socket/unistd, and define a
935             # couple of compatibility macros so the rest of the codegen can
936             # stay POSIX-shaped.
937 20         681 $builder->line('#include ')
938             ->line('#include ')
939             ->raw(<<'C');
940             #ifdef _WIN32
941             # define WIN32_LEAN_AND_MEAN
942             # include
943             # include
944             # include
945             # define close(fd) closesocket(fd)
946             /* Windows has no O_NONBLOCK / fcntl(F_SETFL, O_NONBLOCK); use
947             * ioctlsocket(FIONBIO) instead. Wrap both fcntl invocations the
948             * codegen emits so they vanish on Windows. */
949             # define hs_set_nonblocking(fd) do { u_long _m = 1; ioctlsocket((fd), FIONBIO, &_m); } while(0)
950             /* sockets don't raise SIGPIPE on Windows. */
951             # ifndef MSG_NOSIGNAL
952             # define MSG_NOSIGNAL 0
953             # endif
954             #else
955             # include
956             # include
957             # include
958             # include
959             # include
960             # include
961             # define hs_set_nonblocking(fd) do { int _f = fcntl((fd), F_GETFL, 0); fcntl((fd), F_SETFL, _f | O_NONBLOCK); } while(0)
962             #endif
963             C
964              
965             # Backend-specific includes
966 20         556 $builder->line($backend->includes);
967            
968             # Add signal and time headers for graceful shutdown
969 20         316 $builder->line('#include ')
970             ->line('#include ');
971            
972             # Compression support - include zlib if compression is enabled
973 20 50       207 if ($self->{_compression_enabled}) {
974 0         0 $builder->line('#include ')
975             ->line('#define HYPERSONIC_COMPRESSION 1');
976             }
977            
978             # TLS support - include OpenSSL headers if TLS is enabled
979 20 50       203 if ($self->{tls}) {
980 0         0 $builder->raw(Hypersonic::TLS::gen_includes())
981             ->line('#define HYPERSONIC_TLS 1');
982             }
983            
984             # HTTP/2 support - include nghttp2 if enabled
985 20 50       168 if ($self->{http2}) {
986 0         0 require Hypersonic::Protocol::HTTP2;
987 0         0 Hypersonic::Protocol::HTTP2->gen_includes($builder);
988             }
989              
990             # Security hardening configuration
991 20         137 my $max_connections = $self->{max_connections};
992 20         92 my $max_request_size = $self->{max_request_size};
993 20         94 my $keepalive_timeout = $self->{keepalive_timeout};
994 20         77 my $recv_timeout = $self->{recv_timeout};
995 20         95 my $drain_timeout = $self->{drain_timeout};
996              
997             # Backend-specific defines
998 20         265 $builder->blank
999             ->line($backend->defines)
1000             ->line("#define RECV_BUF_SIZE $max_request_size")
1001             ->line("#define MAX_CONNECTIONS $max_connections");
1002            
1003             # Enable security headers macro if configured
1004 20 100 66     407 if ($self->{enable_security_headers} && $has_dynamic) {
1005 4         36 $builder->line('#define HYPERSONIC_SECURITY_HEADERS 1');
1006             }
1007            
1008             $builder
1009 20         546 ->line("#define KEEPALIVE_TIMEOUT $keepalive_timeout")
1010             ->line("#define RECV_TIMEOUT $recv_timeout")
1011             ->line("#define DRAIN_TIMEOUT $drain_timeout")
1012             ->blank;
1013            
1014             # TLS-aware I/O macros - compile-time decision, zero runtime overhead
1015 20         627 $builder->comment('TLS-aware I/O wrappers - compile-time branching')
1016             ->line('#ifdef HYPERSONIC_TLS')
1017             ->line('#define HYPERSONIC_SEND(fd, buf, len) do { \\')
1018             ->line(' TLSConnection* _tc = get_tls_connection(fd); \\')
1019             ->line(' if (_tc) tls_send(_tc, buf, len); \\')
1020             ->line(' else send(fd, buf, len, 0); \\')
1021             ->line('} while(0)')
1022             ->line('#define HYPERSONIC_CLOSE(fd) tls_close(fd)')
1023             ->line('#else')
1024             ->line('#define HYPERSONIC_SEND(fd, buf, len) send(fd, buf, len, 0)')
1025             ->line('#define HYPERSONIC_CLOSE(fd) close(fd)')
1026             ->line('#endif')
1027             ->blank;
1028              
1029             # User-defined C helpers (early, so they're available to all routes)
1030 20 50       156 if (my $helpers = $self->{c_helpers}) {
1031 0         0 $builder->comment('User-defined C helpers');
1032 0 0       0 if (ref $helpers eq 'CODE') {
1033 0         0 my $helper_builder = XS::JIT::Builder->new;
1034 0         0 $helpers->($helper_builder);
1035 0         0 $builder->raw($helper_builder->code);
1036             } else {
1037             # Raw C string
1038 0         0 $builder->raw($helpers);
1039             }
1040 0         0 $builder->blank;
1041             }
1042              
1043             # Graceful shutdown support
1044 20         482 $builder->comment('Graceful shutdown support')
1045             ->line('static volatile sig_atomic_t g_shutdown = 0;')
1046             ->line('static volatile int g_active_connections = 0;')
1047             ->blank
1048             ->line('static void handle_shutdown_signal(int sig) {')
1049             ->line(' (void)sig;')
1050             ->line(' g_shutdown = 1;')
1051             ->line('}')
1052             ->blank;
1053            
1054             # Compression support - JIT compiled zlib functions
1055 20 50       149 if ($self->{_compression_enabled}) {
1056 0         0 my $config = $self->{_compression_config};
1057 0   0     0 my $min_size = $config->{min_size} // 1024;
1058 0   0     0 my $level = $config->{level} // 6;
1059            
1060 0         0 $builder->comment('Gzip compression support - JIT compiled')
1061             ->line('static __thread unsigned char gzip_out_buf[131072];')
1062             ->blank
1063             ->line('static int accepts_gzip(const char* accept_encoding, size_t len) {')
1064             ->line(' if (!accept_encoding || len == 0) return 0;')
1065             ->line(' const char* p = accept_encoding;')
1066             ->line(' const char* end = accept_encoding + len;')
1067             ->line(' while (p < end - 3) {')
1068             ->line(' if (p[0] == \'g\' && p[1] == \'z\' && p[2] == \'i\' && p[3] == \'p\') return 1;')
1069             ->line(' p++;')
1070             ->line(' }')
1071             ->line(' return 0;')
1072             ->line('}')
1073             ->blank
1074             ->line('static size_t gzip_compress(const char* input, size_t input_len, unsigned char** output) {')
1075             ->line(' size_t max_out;')
1076             ->line(' z_stream strm;')
1077             ->line(' int ret;')
1078             ->line(' size_t compressed_len;')
1079             ->line(" if (input_len < $min_size) return 0;")
1080             ->line(' max_out = compressBound(input_len) + 18;')
1081             ->line(' if (max_out > sizeof(gzip_out_buf)) return 0;')
1082             ->line(' memset(&strm, 0, sizeof(strm));')
1083             ->line(" if (deflateInit2(&strm, $level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) return 0;")
1084             ->line(' strm.next_in = (Bytef*)input;')
1085             ->line(' strm.avail_in = input_len;')
1086             ->line(' strm.next_out = gzip_out_buf;')
1087             ->line(' strm.avail_out = sizeof(gzip_out_buf);')
1088             ->line(' ret = deflate(&strm, Z_FINISH);')
1089             ->line(' compressed_len = strm.total_out;')
1090             ->line(' deflateEnd(&strm);')
1091             ->line(' if (ret != Z_STREAM_END || compressed_len >= input_len) return 0;')
1092             ->line(' *output = gzip_out_buf;')
1093             ->line(' return compressed_len;')
1094             ->line('}')
1095             ->blank;
1096             }
1097            
1098             # Connection tracking for keep-alive timeout - O(1) using fd as index
1099 20         1153 $builder->comment('Connection tracking - O(1) using fd as direct index')
1100             ->line('#define MAX_FD 65536')
1101             ->line('static time_t g_conn_time[MAX_FD];')
1102             ->line('static time_t g_current_time = 0;')
1103             ->blank
1104             ->line("static $inline void track_connection(int fd, time_t now) {")
1105             ->line(' if (fd >= 0 && fd < MAX_FD) {')
1106             ->line(' g_conn_time[fd] = now;')
1107             ->line(' g_active_connections++;')
1108             ->line(' }')
1109             ->line('}')
1110             ->blank
1111             ->line("static $inline void update_connection(int fd, time_t now) {")
1112             ->line(' if (fd >= 0 && fd < MAX_FD) {')
1113             ->line(' g_conn_time[fd] = now;')
1114             ->line(' }')
1115             ->line('}')
1116             ->blank
1117             ->line("static $inline void remove_connection(int fd) {")
1118             ->line(' if (fd >= 0 && fd < MAX_FD && g_conn_time[fd] > 0) {')
1119             ->line(' g_conn_time[fd] = 0;')
1120             ->line(' g_active_connections--;')
1121             ->line(' }')
1122             ->line('}')
1123             ->blank;
1124              
1125             # TLS code generation - SSL context, accept, read/write wrappers
1126 20 50       108 if ($self->{tls}) {
1127             $builder->comment('TLS/HTTPS support via OpenSSL')
1128 0         0 ->raw(Hypersonic::TLS::gen_ssl_ctx_init(http2 => $self->{http2}))
1129             ->blank
1130             ->raw(Hypersonic::TLS::gen_ssl_accept())
1131             ->blank
1132             ->raw(Hypersonic::TLS::gen_ssl_io())
1133             ->blank
1134             ->raw(Hypersonic::TLS::gen_ssl_close())
1135             ->blank;
1136             }
1137            
1138             # HTTP/2 code generation - nghttp2 callbacks, session init, dispatchers
1139 20 50       88 if ($self->{http2}) {
1140 0         0 require Hypersonic::Protocol::HTTP2;
1141 0         0 $builder->comment('HTTP/2 support via nghttp2');
1142 0         0 Hypersonic::Protocol::HTTP2->gen_connection_struct($builder);
1143 0         0 Hypersonic::Protocol::HTTP2->gen_connection_preface_check($builder);
1144 0         0 Hypersonic::Protocol::HTTP2->gen_response_sender($builder);
1145 0         0 Hypersonic::Protocol::HTTP2->gen_404_response($builder);
1146 0         0 Hypersonic::Protocol::HTTP2->gen_callbacks($builder);
1147 0         0 Hypersonic::Protocol::HTTP2->gen_session_init($builder);
1148 0         0 Hypersonic::Protocol::HTTP2->gen_dispatcher($builder);
1149 0         0 Hypersonic::Protocol::HTTP2->gen_input_processor($builder);
1150 0         0 $builder->blank;
1151             }
1152              
1153             # Streaming support - JIT: only generate when streaming handlers detected
1154 20         145 my $analysis = $self->{route_analysis};
1155 20 100       99 if ($analysis->{needs_streaming}) {
1156 2         47 require Hypersonic::Stream;
1157             Hypersonic::Stream->generate_c_code($builder, {
1158             max_streams => $self->{max_connections},
1159 2         55 });
1160              
1161             # SSE support - compile SSE methods when streaming is enabled
1162 2         2144 require Hypersonic::SSE;
1163             Hypersonic::SSE->generate_c_code($builder, {
1164             max_sse_instances => $self->{max_connections},
1165 2         21 });
1166             }
1167              
1168             # WebSocket support - JIT: only generate when WebSocket routes exist
1169 20 50       118 if ($analysis->{needs_websocket}) {
1170 0         0 require Hypersonic::WebSocket;
1171 0         0 require Hypersonic::Protocol::WebSocket;
1172 0         0 require Hypersonic::Protocol::WebSocket::Frame;
1173              
1174             # Generate WebSocket frame encoding functions
1175             Hypersonic::Protocol::WebSocket::Frame->generate_c_code($builder, {
1176             max_connections => $self->{max_connections},
1177 0         0 });
1178              
1179             # Generate WebSocket connection management
1180             Hypersonic::WebSocket->generate_c_code($builder, {
1181             max_websockets => $self->{max_connections},
1182 0         0 });
1183             }
1184              
1185             # WebSocket Handler - JIT: connection registry, only when websocket routes exist
1186 20 50       132 if ($analysis->{needs_websocket_handler}) {
1187 0         0 require Hypersonic::WebSocket::Handler;
1188             Hypersonic::WebSocket::Handler->generate_c_code($builder, {
1189             max_connections => $self->{max_connections},
1190 0         0 });
1191             }
1192              
1193             # WebSocket Rooms - JIT: broadcast groups, only when explicitly enabled
1194 20 50       90 if ($analysis->{needs_websocket_rooms}) {
1195 0         0 require Hypersonic::WebSocket::Room;
1196             Hypersonic::WebSocket::Room->generate_c_code($builder, {
1197             max_rooms => $self->{max_rooms},
1198             max_clients_per_room => $self->{max_clients_per_room},
1199 0         0 });
1200             }
1201              
1202             # Future/Pool - JIT: async thread pool for blocking operations
1203 20 50       100 if ($analysis->{needs_async_pool}) {
1204 0         0 require Hypersonic::Future;
1205 0         0 require Hypersonic::Future::Pool;
1206 0   0     0 my $async_config = $self->{_async_config} // {};
1207             Hypersonic::Future->generate_c_code($builder, {
1208 0   0     0 max_futures => $async_config->{max_futures} // 65536,
1209             });
1210             Hypersonic::Future::Pool->generate_c_code($builder, {
1211             workers => $async_config->{workers} // 8,
1212 0   0     0 queue_size => $async_config->{queue_size} // 4096,
      0        
1213             });
1214             }
1215              
1216             # need_xs_builder routes - call handlers with fresh builder
1217 20 50       120 if ($analysis->{needs_xs_builder}) {
1218 0         0 my @xs_builder_routes;
1219 0         0 for my $i (0 .. $#{$self->{routes}}) {
  0         0  
1220 0         0 my $route = $self->{routes}[$i];
1221 0 0       0 next unless $route->{need_xs_builder};
1222            
1223             # Create fresh builder for this handler
1224 0         0 my $ext_builder = XS::JIT::Builder->new;
1225            
1226             # Call handler with clean builder
1227 0         0 my $result = $route->{handler}->($ext_builder);
1228            
1229             die "need_xs_builder handler for $route->{method} $route->{path} must return hashref with xs_function"
1230 0 0 0     0 unless ref($result) eq 'HASH' && $result->{xs_function};
1231            
1232             # Store result
1233 0         0 $route->{_xs_result} = $result;
1234 0         0 push @xs_builder_routes, {
1235             route_idx => $i,
1236             route => $route,
1237             result => $result,
1238             code => $ext_builder->code,
1239             };
1240             }
1241            
1242             # Emit generated code
1243 0 0       0 if (@xs_builder_routes) {
1244 0         0 $builder->comment('User XS builder routes');
1245 0         0 for my $xsr (@xs_builder_routes) {
1246             $builder->comment("Route: $xsr->{route}{method} $xsr->{route}{path}")
1247             ->raw($xsr->{code})
1248 0         0 ->blank;
1249             }
1250             }
1251            
1252             # Store for function merging later
1253 0         0 $self->{_xs_builder_routes} = \@xs_builder_routes;
1254             }
1255              
1256             # JIT: WebSocket handler storage (independent of dynamic routes)
1257 20 50       100 if ($analysis->{needs_websocket}) {
1258 0         0 $builder->comment('WebSocket handler storage')
1259             ->line('static SV* g_websocket_handlers = NULL;')
1260             ->blank;
1261             }
1262              
1263             # Global storage for dynamic handler dispatch (only if needed)
1264 20 100       73 if ($has_dynamic) {
1265 4         36 $builder->comment('Storage for dynamic handler callbacks')
1266             ->line('static SV* g_handler_array = NULL;')
1267             ->line('static SV* g_server_obj = NULL;');
1268              
1269             # JIT: Only generate middleware storage if middleware is present
1270 4 100       14 if ($analysis->{has_any_middleware}) {
1271 1         10 $builder->line('static SV* g_before_middleware = NULL;')
1272             ->line('static SV* g_after_middleware = NULL;')
1273             ->line('static SV* g_route_before_middleware = NULL;')
1274             ->line('static SV* g_route_after_middleware = NULL;');
1275             }
1276 4         11 $builder->blank;
1277              
1278             # Generate param info table for named path parameters
1279             # Structure: { param_name, segment_position } per handler
1280 4         38 $builder->comment('Path parameter info per dynamic handler')
1281             ->line('typedef struct { const char* name; int position; } ParamInfo;')
1282             ->line('typedef struct { int count; ParamInfo params[8]; } RouteParamInfo;')
1283             ->blank;
1284              
1285 4   50     6 my @route_params = @{$self->{route_param_info} // []};
  4         56  
1286 4         10 my $handler_count = scalar @route_params;
1287              
1288 4         83 $builder->line("static RouteParamInfo g_route_params[$handler_count] = {");
1289 4         36 for my $i (0 .. $#route_params) {
1290 11   50     31 my $params = $route_params[$i] // [];
1291 11         17 my $count = scalar @$params;
1292 11         15 my @param_strs;
1293 11         19 for my $p (@$params) {
1294 0         0 push @param_strs, qq({ "$p->{name}", $p->{position} });
1295             }
1296             # Pad to 8 elements with {NULL, 0}
1297 11         30 my $padding = 8 - scalar(@param_strs);
1298 11         27 for (1 .. $padding) {
1299 88         162 push @param_strs, '{NULL, 0}';
1300             }
1301 11         51 my $params_str = join(', ', @param_strs);
1302 11         60 $builder->line(" { $count, { $params_str } },");
1303             }
1304 4         20 $builder->line('};')
1305             ->blank;
1306              
1307             # JIT: Streaming handler flags array (only if streaming is enabled)
1308 4 50 66     37 if ($analysis->{needs_streaming} && $self->{_streaming_flags}) {
1309 2         4 my @flags = @{$self->{_streaming_flags}};
  2         9  
1310 2         6 my $flags_str = join(', ', @flags);
1311 2         14 $builder->comment('Streaming handler flags - 1 = streaming, 0 = normal')
1312             ->line("static int g_streaming_handlers[$handler_count] = { $flags_str };")
1313             ->blank;
1314             }
1315             }
1316              
1317             # JIT: WebSocket route paths array (only if WebSocket routes exist)
1318 20 0 33     149 if ($analysis->{needs_websocket} && $self->{_websocket_paths}) {
1319 0         0 my @paths = @{$self->{_websocket_paths}};
  0         0  
1320 0         0 my $ws_count = scalar @paths;
1321 0         0 $builder->comment('WebSocket route paths');
1322 0         0 for my $i (0 .. $#paths) {
1323 0         0 my $escaped = _escape_c_string($paths[$i]);
1324 0         0 $builder->line(qq{static const char WS_PATH_$i\[] = "$escaped";});
1325             }
1326 0         0 $builder->line("static const char* g_ws_paths[$ws_count] = {");
1327 0         0 for my $i (0 .. $#paths) {
1328 0 0       0 my $comma = ($i < $#paths) ? ',' : '';
1329 0         0 $builder->line(" WS_PATH_$i$comma");
1330             }
1331 0         0 $builder->line('};')
1332             ->line("static const int g_ws_path_count = $ws_count;")
1333             ->blank;
1334             }
1335              
1336             # Emit FULL pre-computed HTTP responses (headers + body)
1337 20         417 for my $i (0 .. $#$full_responses) {
1338 35         164 my $resp = $full_responses->[$i];
1339 35         286 my $escaped = _escape_c_string($resp);
1340 35         91 my $len = length($resp);
1341 35         438 $builder->line("static const char RESP_$i\[] = \"$escaped\";")
1342             ->line("static const int RESP_${i}_LEN = $len;");
1343             }
1344 20         76 $builder->blank;
1345              
1346             # 404 response via Protocol module
1347             my $security_hdrs_404 = $self->{enable_security_headers}
1348 20 50       294 ? $self->_get_security_headers_string()
1349             : '';
1350 20         490 my $resp_404 = $PROTOCOL->build_404_response(
1351             security_headers => $security_hdrs_404,
1352             );
1353            
1354 20         125 my $escaped_404 = _escape_c_string($resp_404);
1355 20         378 $builder->line("static const char RESP_404[] = \"$escaped_404\";")
1356             ->line("static const int RESP_404_LEN = " . length($resp_404) . ";")
1357             ->blank;
1358            
1359             # Security headers constant for dynamic responses
1360 20 100 66     323 if ($self->{enable_security_headers} && $has_dynamic) {
1361 4         31 $builder->raw($self->_gen_security_headers_c_constant())
1362             ->blank;
1363             }
1364              
1365             # Generate dynamic handler caller if needed
1366 20 100       68 if ($has_dynamic) {
1367 4         63 $builder->raw($self->_gen_dynamic_handler_caller())
1368             ->blank;
1369             }
1370              
1371             # Generate XS builder route dispatcher if needed
1372 20 0 33     95 if ($analysis->{needs_xs_builder} && $self->{_xs_builder_routes} && @{$self->{_xs_builder_routes}}) {
  0   0     0  
1373 0         0 $builder->raw($self->_gen_xs_builder_dispatcher())
1374             ->blank;
1375             }
1376              
1377             # Generate WebSocket handler caller if needed
1378 20 50       107 if ($analysis->{needs_websocket}) {
1379 0         0 $builder->raw($self->_gen_websocket_handler_caller())
1380             ->blank;
1381 0         0 $builder->raw($self->_gen_websocket_data_processor())
1382             ->blank;
1383             }
1384              
1385             # Group routes by method for dispatch generation
1386 20         54 my %methods;
1387 20         41 for my $route (@{$self->{routes}}) {
  20         111  
1388 46         62 push @{$methods{$route->{method}}}, $route;
  46         218  
1389             }
1390              
1391             # Generate inline C dispatch function
1392 20         417 $builder->comment('Inline dispatch - returns response pointer and length')
1393             ->comment('Returns: 0=static response, 1=dynamic route, 2=XS builder route, -1=404')
1394             ->comment('For dynamic/XS routes: returns handler_idx in *handler_idx_out')
1395             ->line("static $inline int dispatch_request(const char* method, int method_len, const char* path, int path_len, const char** resp_out, int* resp_len_out, int* handler_idx_out) {")
1396             ->line(' *handler_idx_out = -1;');
1397              
1398 20         88 for my $method (sort keys %methods) {
1399 27         170 my $mlen = length($method);
1400 27         317 $builder->if("method_len == $mlen && memcmp(method, \"$method\", $mlen) == 0");
1401              
1402 27         47 for my $r (@{$methods{$method}}) {
  27         70  
1403 46         216 my $path = $r->{path};
1404 46         91 my $plen = length($path);
1405              
1406 46 100       138 if (!$r->{dynamic}) {
1407             # Static route - return pre-computed response
1408 35         92 my $escaped_path = _escape_c_string($path);
1409 35         105 my $idx = $r->{response_idx};
1410              
1411 35         481 $builder->if("path_len == $plen && memcmp(path, \"$escaped_path\", $plen) == 0")
1412             ->line("*resp_out = RESP_$idx;")
1413             ->line("*resp_len_out = RESP_${idx}_LEN;")
1414             ->line("return 0;")
1415             ->endif;
1416             } else {
1417             # Dynamic route - check for path params or exact match
1418 11         28 my $handler_idx = $r->{handler_idx};
1419              
1420             # Check if this is a need_xs_builder route - dispatch to XS directly
1421 11 50 33     52 if ($r->{need_xs_builder} && $r->{_xs_result}) {
    50          
1422 0         0 my $xs_func = $r->{_xs_result}{xs_function};
1423 0         0 my $escaped_path = _escape_c_string($path);
1424            
1425 0 0       0 if ($path =~ /:(\w+)/) {
1426             # Path has parameters
1427 0         0 my ($prefix) = $path =~ m{^([^:]+)};
1428 0         0 my $prefix_len = length($prefix);
1429 0         0 my $escaped_prefix = _escape_c_string($prefix);
1430            
1431 0         0 $builder->if("path_len >= $prefix_len && memcmp(path, \"$escaped_prefix\", $prefix_len) == 0")
1432             ->comment("XS builder route - call $xs_func directly")
1433             ->line("*handler_idx_out = $handler_idx;")
1434             ->line("return 2;") # Special return code for XS builder routes
1435             ->endif;
1436             } else {
1437             # Exact match
1438 0         0 $builder->if("path_len == $plen && memcmp(path, \"$escaped_path\", $plen) == 0")
1439             ->comment("XS builder route - call $xs_func directly")
1440             ->line("*handler_idx_out = $handler_idx;")
1441             ->line("return 2;") # Special return code for XS builder routes
1442             ->endif;
1443             }
1444             } elsif ($path =~ /:(\w+)/) {
1445             # Path has parameters - generate pattern matching
1446 0         0 my ($prefix) = $path =~ m{^([^:]+)};
1447 0         0 my $prefix_len = length($prefix);
1448 0         0 my $escaped_prefix = _escape_c_string($prefix);
1449              
1450 0         0 $builder->if("path_len >= $prefix_len && memcmp(path, \"$escaped_prefix\", $prefix_len) == 0")
1451             ->line("*resp_out = NULL;")
1452             ->line("*handler_idx_out = $handler_idx;")
1453             ->line("return 1;")
1454             ->endif;
1455             } else {
1456             # Exact match dynamic route
1457 11         22 my $escaped_path = _escape_c_string($path);
1458 11         109 $builder->if("path_len == $plen && memcmp(path, \"$escaped_path\", $plen) == 0")
1459             ->line("*resp_out = NULL;")
1460             ->line("*handler_idx_out = $handler_idx;")
1461             ->line("return 1;")
1462             ->endif;
1463             }
1464             }
1465             }
1466              
1467 27         94 $builder->endif;
1468             }
1469              
1470 20         186 $builder->line('*resp_out = RESP_404;')
1471             ->line('*resp_len_out = RESP_404_LEN;')
1472             ->line('return -1;')
1473             ->line('}')
1474             ->blank;
1475              
1476             # Generate the pure C event loop using backend module
1477 20         226 $self->_gen_event_loop($builder, $backend);
1478 20         52 $builder->blank;
1479              
1480             # Keep the Perl-callable dispatch for testing/compatibility
1481 20         1151 $builder->comment('Perl-callable dispatch wrapper')
1482             ->xs_function('hypersonic_dispatch')
1483             ->xs_preamble
1484             ->line('if (items != 1) croak_xs_usage(cv, "req_ref");')
1485             ->declare_sv('req_ref', 'ST(0)')
1486             ->if('!SvROK(req_ref) || SvTYPE(SvRV(req_ref)) != SVt_PVAV')
1487             ->line('ST(0) = &PL_sv_undef;')
1488             ->line('XSRETURN(1);')
1489             ->endif
1490             ->declare_av('req', '(AV*)SvRV(req_ref)')
1491             ->line('SV** ary = AvARRAY(req);')
1492             ->line('STRLEN method_len, path_len;')
1493             ->line('const char* method = SvPV(ary[0], method_len);')
1494             ->line('const char* path = SvPV(ary[1], path_len);')
1495             ->line('const char* resp;')
1496             ->line('int resp_len;')
1497             ->line('int handler_idx;')
1498             ->line('int rc = dispatch_request(method, (int)method_len, path, (int)path_len, &resp, &resp_len, &handler_idx);')
1499             ->if('rc == 0')
1500             ->comment('Static route')
1501             ->line('ST(0) = sv_2mortal(newSVpvn(resp, resp_len));')
1502             ->elsif('rc == 1')
1503             ->comment('Dynamic route - for testing, just return undef')
1504             ->line('ST(0) = &PL_sv_undef;')
1505             ->else
1506             ->line('ST(0) = &PL_sv_undef;')
1507             ->endif
1508             ->xs_return('1')
1509             ->xs_end;
1510              
1511 20         900 return $builder->code;
1512             }
1513              
1514             # Generate optimized method parser - delegates to Protocol module
1515             sub _gen_method_parser {
1516 20     20   55 my ($self, $builder) = @_;
1517 20         59 my $analysis = $self->{route_analysis};
1518              
1519             # Delegate to Protocol module for HTTP/1.1 parsing
1520             # This separation allows future HTTP/2 support without changing core logic
1521 20         134 return $PROTOCOL->gen_method_parser($builder, $analysis);
1522             }
1523              
1524             # Generate WebSocket frame handling for established connections
1525             sub _gen_websocket_frame_handler {
1526 0     0   0 my ($self, $builder) = @_;
1527              
1528 0         0 $builder->comment('Check if this is an established WebSocket connection')
1529             ->if('fd >= 0 && fd < WS_MAX && ws_registry[fd].state == WS_STATE_OPEN')
1530             ->comment('This is a WebSocket connection - process frames')
1531             ->line('process_websocket_data(aTHX_ fd, recv_buf, len);')
1532             ->line('continue;')
1533             ->endif
1534             ->blank;
1535             }
1536              
1537             # Generate WebSocket upgrade detection and dispatch
1538             sub _gen_websocket_dispatch {
1539 0     0   0 my ($self, $builder) = @_;
1540              
1541 0         0 $builder->comment('WebSocket upgrade detection')
1542             ->comment('Check for Upgrade: websocket header')
1543             ->line('int is_websocket = 0;')
1544             ->line('int ws_handler_idx = -1;')
1545             ->line('const char* ws_key = NULL;')
1546             ->line('int ws_key_len = 0;')
1547             ->blank
1548             ->comment('Look for Upgrade header in raw request')
1549             ->line('const char* upgrade_pos = strstr(recv_buf, "Upgrade:");')
1550             ->if('!upgrade_pos')
1551             ->line('upgrade_pos = strstr(recv_buf, "upgrade:");')
1552             ->endif
1553             ->if('upgrade_pos')
1554             ->line('const char* val_start = upgrade_pos + 8;')
1555             ->line('while (*val_start == \' \') val_start++;')
1556             ->comment('Check for websocket (case-insensitive)')
1557             ->if('strncasecmp(val_start, "websocket", 9) == 0')
1558             ->line('is_websocket = 1;')
1559             ->endif
1560             ->endif
1561             ->blank
1562             ->if('is_websocket')
1563             ->comment('Extract Sec-WebSocket-Key')
1564             ->line('const char* key_pos = strstr(recv_buf, "Sec-WebSocket-Key:");')
1565             ->if('!key_pos')
1566             ->line('key_pos = strstr(recv_buf, "sec-websocket-key:");')
1567             ->endif
1568             ->if('key_pos')
1569             ->line('const char* key_start = key_pos + 18;')
1570             ->line('while (*key_start == \' \') key_start++;')
1571             ->line('const char* key_end = key_start;')
1572             ->line('while (*key_end && *key_end != \'\\r\' && *key_end != \'\\n\') key_end++;')
1573             ->line('ws_key = key_start;')
1574             ->line('ws_key_len = key_end - key_start;')
1575             ->endif
1576             ->blank
1577             ->comment('Match path to WebSocket routes')
1578             ->line('int clean_path_len = path_len;')
1579             ->line('const char* qmark = memchr(path, \'?\', path_len);')
1580             ->if('qmark')
1581             ->line('clean_path_len = qmark - path;')
1582             ->endif
1583             ->for('int i = 0', 'i < g_ws_path_count', 'i++')
1584             ->line('int ws_path_len = strlen(g_ws_paths[i]);')
1585             ->if('clean_path_len == ws_path_len && memcmp(path, g_ws_paths[i], ws_path_len) == 0')
1586             ->line('ws_handler_idx = i;')
1587             ->line('break;')
1588             ->endif
1589             ->endfor
1590             ->blank
1591             ->if('ws_handler_idx >= 0 && ws_key')
1592             ->comment('WebSocket upgrade - call Perl handler')
1593             ->line('call_websocket_handler(aTHX_ ws_handler_idx, fd, path, path_len, ws_key, ws_key_len, recv_buf, len);')
1594             ->line('continue;') # Skip normal dispatch, connection stays open
1595             ->endif
1596             ->endif
1597             ->blank;
1598             }
1599              
1600             # Unified event loop generator - uses backend module for platform-specific code
1601             sub _gen_event_loop {
1602 20     20   90 my ($self, $builder, $backend) = @_;
1603 20         46 my $has_dynamic = grep { $_->{dynamic} } @{$self->{routes}};
  46         122  
  20         73  
1604 20         47 my $analysis = $self->{route_analysis};
1605 20   50     89 my $has_body_access = $analysis->{has_body_access} // 0;
1606 20         129 my $backend_name = $backend->name;
1607 20         102 my $event_struct = $backend->event_struct;
1608              
1609 20         545 $builder->comment("Pure C event loop using $backend_name backend - WITH SECURITY HARDENING")
1610             ->xs_function('hypersonic_run_event_loop')
1611             ->xs_preamble
1612             ->check_items(2, 2, 'listen_fd, server_obj')
1613             ->blank
1614             ->declare('int', 'listen_fd', '(int)SvIV(ST(0))')
1615             ->declare_sv('server_obj', 'ST(1)')
1616             ->blank;
1617              
1618             # Handler storage (only if dynamic routes)
1619 20 100       81 if ($has_dynamic) {
1620 4         78 $builder->comment('Store server object for dynamic handler access')
1621             ->if('SvROK(server_obj)')
1622             ->declare_hv('self', '(HV*)SvRV(server_obj)')
1623             ->line('SV** handlers_ref = hv_fetch(self, "dynamic_handlers", 16, 0);')
1624             ->if('handlers_ref && SvROK(*handlers_ref)')
1625             ->line('g_handler_array = *handlers_ref;')
1626             ->line('SvREFCNT_inc(g_handler_array);')
1627             ->endif
1628             ->line('g_server_obj = server_obj;')
1629             ->line('SvREFCNT_inc(g_server_obj);');
1630              
1631             # JIT: Only fetch middleware if any middleware is present
1632 4 100       14 if ($analysis->{has_any_middleware}) {
1633 1         3 $builder->blank
1634             ->comment('JIT: Middleware storage (Perl coderefs only - builders are inline C)');
1635              
1636             # Only fetch Perl middleware if we have any
1637 1 50       2 if ($analysis->{has_global_before}) {
1638 1         7 $builder->line('SV** before_ref = hv_fetch(self, "_perl_before_mw", 15, 0);')
1639             ->if('before_ref && SvROK(*before_ref)')
1640             ->line('g_before_middleware = *before_ref;')
1641             ->line('SvREFCNT_inc(g_before_middleware);')
1642             ->endif;
1643             }
1644 1 50       2 if ($analysis->{has_global_after}) {
1645 1         7 $builder->line('SV** after_ref = hv_fetch(self, "_perl_after_mw", 14, 0);')
1646             ->if('after_ref && SvROK(*after_ref)')
1647             ->line('g_after_middleware = *after_ref;')
1648             ->line('SvREFCNT_inc(g_after_middleware);')
1649             ->endif;
1650             }
1651              
1652             # Per-route middleware arrays
1653 1 50       3 if ($analysis->{has_route_middleware}) {
1654 0         0 $builder->line('SV** route_before_ref = hv_fetch(self, "_route_before_mw", 16, 0);')
1655             ->if('route_before_ref && SvROK(*route_before_ref)')
1656             ->line('g_route_before_middleware = *route_before_ref;')
1657             ->line('SvREFCNT_inc(g_route_before_middleware);')
1658             ->endif
1659             ->line('SV** route_after_ref = hv_fetch(self, "_route_after_mw", 15, 0);')
1660             ->if('route_after_ref && SvROK(*route_after_ref)')
1661             ->line('g_route_after_middleware = *route_after_ref;')
1662             ->line('SvREFCNT_inc(g_route_after_middleware);')
1663             ->endif;
1664             }
1665             }
1666              
1667 4         12 $builder->endif;
1668             } else {
1669 16         188 $builder->line('(void)server_obj; /* Not needed for static-only routes */');
1670             }
1671 20         85 $builder->blank;
1672              
1673             # WebSocket handler storage (only if WebSocket routes)
1674 20 50       67 if ($analysis->{needs_websocket}) {
1675 0         0 $builder->comment('Store WebSocket handlers for dispatch')
1676             ->if('SvROK(server_obj)')
1677             ->declare_hv('ws_self', '(HV*)SvRV(server_obj)')
1678             ->line('SV** ws_handlers_ref = hv_fetch(ws_self, "_websocket_handlers", 19, 0);')
1679             ->if('ws_handlers_ref && SvROK(*ws_handlers_ref)')
1680             ->line('g_websocket_handlers = *ws_handlers_ref;')
1681             ->line('SvREFCNT_inc(g_websocket_handlers);')
1682             ->endif
1683             ->endif
1684             ->blank;
1685             }
1686              
1687             # Signal handlers and initialization
1688 20         298 $builder->comment('Setup graceful shutdown signal handlers')
1689             ->line('signal(SIGTERM, handle_shutdown_signal);')
1690             ->line('signal(SIGINT, handle_shutdown_signal);')
1691             ->blank
1692             ->comment('Initialize connection tracking')
1693             ->line('memset(g_conn_time, 0, sizeof(g_conn_time));')
1694             ->line('g_active_connections = 0;')
1695             ->blank;
1696              
1697             # TLS initialization
1698 20 50       80 if ($self->{tls}) {
1699 0         0 my $cert_file = _escape_c_string($self->{cert_file});
1700 0         0 my $key_file = _escape_c_string($self->{key_file});
1701 0         0 $builder->comment('Initialize TLS/HTTPS')
1702             ->line('#ifdef HYPERSONIC_TLS')
1703             ->line("if (init_ssl_ctx(\"$cert_file\", \"$key_file\") != 0) {")
1704             ->line(' croak("Failed to initialize TLS context - check cert/key files");')
1705             ->line('}')
1706             ->line('memset(g_tls_connections, 0, sizeof(g_tls_connections));')
1707             ->line('#endif')
1708             ->blank;
1709             }
1710              
1711 20         210 $builder->comment('Thread-local receive buffer - each worker gets its own')
1712             ->line('static __thread char recv_buf[RECV_BUF_SIZE];')
1713             ->blank;
1714              
1715             # Backend-specific: Create event loop and add listen socket
1716 20         204 $backend->gen_create($builder, 'listen_fd');
1717              
1718             # Async Pool: Initialize thread pool and add notify fd to event loop
1719 20 50       105 if ($analysis->{needs_async_pool}) {
1720 0         0 $builder->blank
1721             ->comment('Initialize async thread pool')
1722             ->line('pool_init();')
1723             ->line('int pool_notify_fd = pool_get_notify_fd();');
1724 0         0 $backend->gen_add_pool_notify($builder, 'ev_fd', 'pool_notify_fd');
1725             }
1726              
1727             # Declare event structure variable based on backend
1728 20 50       108 if ($backend_name eq 'kqueue') {
    50          
    0          
1729             # kqueue's gen_create already declares 'ev', but we need events array
1730 0         0 $builder->line('struct kevent events[MAX_EVENTS];');
1731             } elsif ($backend_name eq 'epoll') {
1732             # epoll's gen_create already declares 'ev'
1733 20         92 $builder->line('struct epoll_event events[MAX_EVENTS];');
1734             } elsif ($backend_name eq 'io_uring') {
1735             # io_uring uses completion queue entries
1736 0         0 $builder->line('struct io_uring_cqe** events = NULL;');
1737             } else {
1738             # poll/select manage their own fd arrays internally
1739             # Declare a dummy events pointer to satisfy gen_wait signature
1740 0         0 $builder->line('void* events = NULL;');
1741             }
1742              
1743 20         117 $builder->line('time_t last_cleanup = time(NULL);')
1744             ->line('int accepting = 1; /* Flag to control accepting new connections */')
1745             ->blank;
1746              
1747             # Main event loop
1748 20         187 $builder->while('!g_shutdown || g_active_connections > 0')
1749             ->comment('Use timeout for keep-alive cleanup and shutdown check');
1750              
1751             # Backend-specific: Wait for events
1752 20         189 $backend->gen_wait($builder, 'ev_fd', 'events', 'n', '1000');
1753              
1754 20         143 $builder->blank
1755             ->comment('Check for graceful shutdown - stop accepting new connections')
1756             ->if('g_shutdown && accepting');
1757              
1758             # Backend-specific: Remove listen socket from event loop
1759 20         107 $backend->gen_del($builder, 'ev_fd', 'listen_fd');
1760 20         273 $builder->line('accepting = 0;')
1761             ->endif
1762             ->blank
1763             ->comment('Get time once per event batch')
1764             ->line('time_t now = time(NULL);')
1765             ->line('g_current_time = now;')
1766             ->blank;
1767              
1768             # Keep-alive cleanup
1769 20         374 $builder->comment('Periodic keep-alive timeout cleanup')
1770             ->if('now - last_cleanup >= 5')
1771             ->declare('int', 'cleanup_i', '0')
1772             ->for('cleanup_i = 0', 'cleanup_i < MAX_FD', 'cleanup_i++')
1773             ->if('g_conn_time[cleanup_i] > 0')
1774             ->if('now - g_conn_time[cleanup_i] > KEEPALIVE_TIMEOUT')
1775             ->comment('Close idle connection')
1776             ->line('int idle_fd = cleanup_i;');
1777              
1778             # Backend-specific: Remove idle connection
1779 20         84 $backend->gen_del($builder, 'ev_fd', 'idle_fd');
1780 20         283 $builder->line('HYPERSONIC_CLOSE(idle_fd);')
1781             ->line('remove_connection(idle_fd);')
1782             ->endif
1783             ->endif
1784             ->endfor
1785             ->line('last_cleanup = now;')
1786             ->endif
1787             ->blank;
1788              
1789             # Event processing loop
1790 20         181 $builder->declare('int', 'i', '0')
1791             ->for('i = 0', 'i < n', 'i++');
1792              
1793             # Backend-specific: Get fd from event
1794 20         152 $backend->gen_get_fd($builder, 'events', 'i', 'fd');
1795              
1796 20         235 $builder->blank
1797             ->if('fd == listen_fd && accepting');
1798              
1799             # Accept loop
1800 20         1669 $builder->comment('Accept new connections with limit check')
1801             ->while('1')
1802             ->comment('Check connection limit before accepting')
1803             ->if('g_active_connections >= MAX_CONNECTIONS')
1804             ->line('break; /* At capacity, stop accepting */')
1805             ->endif
1806             ->blank
1807             ->line('struct sockaddr_in client_addr;')
1808             ->line('socklen_t client_len = sizeof(client_addr);')
1809             ->line('int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);')
1810             ->if('client_fd < 0')
1811             ->line('break;')
1812             ->endif
1813             ->blank
1814             ->comment('Set non-blocking (hs_set_nonblocking handles Win/POSIX divergence)')
1815             ->line('hs_set_nonblocking(client_fd);')
1816             ->blank
1817             ->comment('Disable Nagle')
1818             ->line('int one = 1;')
1819             ->line('setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, sizeof(one));')
1820             ->blank
1821             ->comment('Set receive timeout for security')
1822             ->line('struct timeval tv;')
1823             ->line('tv.tv_sec = RECV_TIMEOUT;')
1824             ->line('tv.tv_usec = 0;')
1825             ->line('setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));')
1826             ->blank
1827             ->comment('Track connection for keep-alive timeout')
1828             ->line('track_connection(client_fd, now);')
1829             ->blank
1830             ->comment('TLS handshake if enabled')
1831             ->line('#ifdef HYPERSONIC_TLS')
1832             ->line('if (tls_accept(client_fd) < 0) {')
1833             ->line(' close(client_fd);')
1834             ->line(' remove_connection(client_fd);')
1835             ->line(' continue;')
1836             ->line('}')
1837             ->line('#endif')
1838             ->blank
1839             ->comment('Add to event loop');
1840              
1841             # Backend-specific: Add client to event loop
1842 20         193 $backend->gen_add($builder, 'ev_fd', 'client_fd');
1843 20         88 $builder->endwhile;
1844              
1845             # Async Pool: Handle pool notify fd - process completed futures
1846 20 50       83 if ($analysis->{needs_async_pool}) {
1847 0         0 $builder->elsif('fd == pool_notify_fd')
1848             ->comment('Thread pool notification - process completed async operations')
1849             ->line('pool_process_ready();');
1850             }
1851              
1852             # Handle client request
1853 20         429 $builder->elsif('fd != listen_fd')
1854             ->comment('Handle client request')
1855             ->line('#ifdef HYPERSONIC_TLS')
1856             ->line('TLSConnection* tls_conn = get_tls_connection(fd);')
1857             ->line('ssize_t len = tls_conn ? tls_recv(tls_conn, recv_buf, RECV_BUF_SIZE - 1) : -1;')
1858             ->line('#else')
1859             ->line('ssize_t len = recv(fd, recv_buf, RECV_BUF_SIZE - 1, 0);')
1860             ->line('#endif')
1861             ->blank
1862             ->if('len <= 0')
1863             ->comment('Connection closed or error');
1864              
1865             # Backend-specific: Remove from event loop
1866 20         136 $backend->gen_del($builder, 'ev_fd', 'fd');
1867 20         189 $builder->line('#ifdef HYPERSONIC_TLS')
1868             ->line('tls_close(fd);')
1869             ->line('#else')
1870             ->line('close(fd);')
1871             ->line('#endif');
1872              
1873             # Reset WebSocket state if WebSocket routes exist
1874 20 50       85 if ($analysis->{needs_websocket}) {
1875 0         0 $builder->line('ws_reset(fd);');
1876             }
1877              
1878 20         590 $builder->line('remove_connection(fd);')
1879             ->line('continue;')
1880             ->endif
1881             ->blank
1882             ->comment('Update connection activity for keep-alive timeout')
1883             ->line('update_connection(fd, now);')
1884             ->blank
1885             ->line('recv_buf[len] = \'\\0\';')
1886             ->blank;
1887              
1888             # WebSocket frame handling - JIT: only generate if WebSocket routes exist
1889 20 50       175 if ($analysis->{needs_websocket}) {
1890 0         0 $self->_gen_websocket_frame_handler($builder);
1891             }
1892              
1893             # Method parser - delegates to Protocol module
1894 20         275 $self->_gen_method_parser($builder);
1895 20         155 $builder->blank;
1896              
1897             # Path parsing - delegates to Protocol module
1898 20         279 $PROTOCOL->gen_path_parser($builder);
1899 20         75 $builder->blank;
1900              
1901             # WebSocket upgrade detection - JIT: only generate if WebSocket routes exist
1902 20 50       71 if ($analysis->{needs_websocket}) {
1903 0         0 $self->_gen_websocket_dispatch($builder);
1904             }
1905              
1906             # Dispatch
1907 20         197 $builder->comment('Dispatch request')
1908             ->line('const char* resp;')
1909             ->line('int resp_len;')
1910             ->line('int handler_idx;')
1911             ->line('int dispatch_result = dispatch_request(method, method_len, path, path_len, &resp, &resp_len, &handler_idx);')
1912             ->blank;
1913              
1914             # Dynamic dispatch
1915 20 100       171 if ($has_dynamic) {
1916             # Check for XS builder routes first (dispatch_result == 2)
1917 4 50 33     21 if ($analysis->{needs_xs_builder} && $self->{_xs_builder_routes} && @{$self->{_xs_builder_routes}}) {
  0   0     0  
1918 0         0 $builder->if('dispatch_result == 2')
1919             ->comment('XS builder route - call generated XS function directly');
1920            
1921             # Body parsing for XS builder routes (they may need body access)
1922 0         0 $PROTOCOL->gen_body_parser($builder, has_body_access => $has_body_access);
1923            
1924 0         0 $builder->blank
1925             ->line('char* xs_resp;')
1926             ->line('int xs_resp_len;')
1927             ->line('call_xs_builder_handler(aTHX_ handler_idx, fd, method, method_len, path, path_len, body, body_len, &xs_resp, &xs_resp_len);')
1928             ->line('HYPERSONIC_SEND(fd, xs_resp, xs_resp_len);')
1929             ->elsif('dispatch_result == 1');
1930             } else {
1931 4         23 $builder->if('dispatch_result == 1');
1932             }
1933            
1934 4         24 $builder->comment('Dynamic route - call Perl handler');
1935              
1936             # Body parsing - delegates to Protocol module
1937 4         26 $PROTOCOL->gen_body_parser($builder, has_body_access => $has_body_access);
1938              
1939 4         130 $builder->blank
1940             ->line('char* dyn_resp;')
1941             ->line('int dyn_resp_len;')
1942             ->line('call_dynamic_handler(aTHX_ handler_idx, fd, method, method_len, path, full_path_len, body, body_len, recv_buf, len, &dyn_resp, &dyn_resp_len);')
1943             ->comment('dyn_resp_len == -1 means streaming handler (response already sent)')
1944             ->line('if (dyn_resp_len >= 0) {')
1945             ->line(' HYPERSONIC_SEND(fd, dyn_resp, dyn_resp_len);')
1946             ->line('}')
1947             ->else
1948             ->line('HYPERSONIC_SEND(fd, resp, resp_len);')
1949             ->endif;
1950             } else {
1951 16         140 $builder->line('HYPERSONIC_SEND(fd, resp, resp_len);');
1952             }
1953 20         64 $builder->blank;
1954              
1955             # Keep-alive check - delegates to Protocol module
1956 20         153 $PROTOCOL->gen_keepalive_check($builder);
1957 20         113 $builder->blank
1958             ->if('!keep_alive');
1959              
1960             # Backend-specific: Remove from event loop on close
1961 20         186 $backend->gen_del($builder, 'ev_fd', 'fd');
1962 20         100 $builder->line('HYPERSONIC_CLOSE(fd);');
1963              
1964             # Reset WebSocket state if WebSocket routes exist
1965 20 50       64 if ($analysis->{needs_websocket}) {
1966 0         0 $builder->line('ws_reset(fd);');
1967             }
1968              
1969 20         158 $builder->line('remove_connection(fd);')
1970             ->endif;
1971              
1972             # Close event processing
1973 20         171 $builder->endif # fd == listen_fd
1974             ->endfor # for i
1975             ->endwhile # main loop
1976             ->blank;
1977              
1978             # Async Pool: Shutdown thread pool on server exit
1979 20 50       71 if ($analysis->{needs_async_pool}) {
1980 0         0 $builder->comment('Shutdown async thread pool')
1981             ->line('pool_shutdown();');
1982             }
1983              
1984 20         243 $builder->line('close(ev_fd);')
1985             ->xs_return('0')
1986             ->xs_end;
1987              
1988 20         44 return $builder;
1989             }
1990              
1991             sub _gen_xs_builder_dispatcher {
1992 0     0   0 my ($self) = @_;
1993            
1994 0         0 my $builder = XS::JIT::Builder->new;
1995 0   0     0 my $xs_routes = $self->{_xs_builder_routes} || [];
1996            
1997 0 0       0 return '' unless @$xs_routes;
1998            
1999 0         0 $builder->comment('XS Builder route dispatcher')
2000             ->comment('Dispatches to user-defined XS functions based on handler_idx')
2001             ->line('static void call_xs_builder_handler(pTHX_ int handler_idx, int fd,')
2002             ->line(' const char* method, int method_len,')
2003             ->line(' const char* path, int path_len,')
2004             ->line(' const char* body, int body_len,')
2005             ->line(' char** resp_out, int* resp_len_out) {')
2006             ->line(' switch (handler_idx) {');
2007            
2008 0         0 for my $entry (@$xs_routes) {
2009 0         0 my $handler_idx = $entry->{route}{handler_idx};
2010 0         0 my $xs_func = $entry->{result}{xs_function};
2011            
2012 0         0 $builder->line(" case $handler_idx:")
2013             ->line(" $xs_func(aTHX_ fd, method, method_len, path, path_len, body, body_len, resp_out, resp_len_out);")
2014             ->line(" break;");
2015             }
2016            
2017 0         0 $builder->line(' default:')
2018             ->line(' *resp_out = (char*)RESP_404;')
2019             ->line(' *resp_len_out = RESP_404_LEN;')
2020             ->line(' break;')
2021             ->line(' }')
2022             ->line('}')
2023             ->blank;
2024            
2025 0         0 return $builder->code;
2026             }
2027              
2028             sub _gen_dynamic_handler_caller {
2029 4     4   38 my ($self) = @_;
2030 4         12 my $analysis = $self->{route_analysis};
2031 4         88 my $builder = XS::JIT::Builder->new;
2032            
2033             # ============================================================
2034             # JIT PHILOSOPHY: Only generate code for features actually used
2035             # ============================================================
2036            
2037             # Status code helper - always needed for dynamic routes
2038 4         190 $builder->comment('Status code to text mapping')
2039             ->line('static const char* get_status_text(int code) {')
2040             ->line(' switch(code) {')
2041             ->line(' case 200: return "OK";')
2042             ->line(' case 201: return "Created";')
2043             ->line(' case 202: return "Accepted";')
2044             ->line(' case 204: return "No Content";')
2045             ->line(' case 301: return "Moved Permanently";')
2046             ->line(' case 302: return "Found";')
2047             ->line(' case 303: return "See Other";')
2048             ->line(' case 304: return "Not Modified";')
2049             ->line(' case 400: return "Bad Request";')
2050             ->line(' case 401: return "Unauthorized";')
2051             ->line(' case 403: return "Forbidden";')
2052             ->line(' case 404: return "Not Found";')
2053             ->line(' case 405: return "Method Not Allowed";')
2054             ->line(' case 408: return "Request Timeout";')
2055             ->line(' case 409: return "Conflict";')
2056             ->line(' case 413: return "Payload Too Large";')
2057             ->line(' case 422: return "Unprocessable Entity";')
2058             ->line(' case 429: return "Too Many Requests";')
2059             ->line(' case 500: return "Internal Server Error";')
2060             ->line(' case 502: return "Bad Gateway";')
2061             ->line(' case 503: return "Service Unavailable";')
2062             ->line(' default: return "Unknown";')
2063             ->line(' }')
2064             ->line('}')
2065             ->blank;
2066            
2067             # Path segment parser - always needed for path params
2068 4         144 $builder->comment('Parse path segments into array - stops at ? for query string')
2069             ->line('static int parse_path_segments(const char* path, int path_len,')
2070             ->line(' const char** segments, int* seg_lens, int max_segs) {')
2071             ->line(' int count = 0;')
2072             ->line(' const char* start;')
2073             ->line(' const char* end;')
2074             ->line(' const char* query;')
2075             ->line(' const char* seg_end;')
2076             ->line(' start = path;')
2077             ->line(' end = path + path_len;')
2078             ->line(' query = memchr(path, \'?\', path_len);')
2079             ->line(' if (query) end = query;')
2080             ->line(' while (start < end && count < max_segs) {')
2081             ->line(' if (*start == \'/\') start++;')
2082             ->line(' if (start >= end) break;')
2083             ->line(' seg_end = start;')
2084             ->line(' while (seg_end < end && *seg_end != \'/\') seg_end++;')
2085             ->line(' segments[count] = start;')
2086             ->line(' seg_lens[count] = seg_end - start;')
2087             ->line(' count++;')
2088             ->line(' start = seg_end;')
2089             ->line(' }')
2090             ->line(' return count;')
2091             ->line('}')
2092             ->blank;
2093            
2094             # URL decoder - only if query/form parsing needed
2095 4 50 33     65 if ($analysis->{needs_query} || $analysis->{needs_form}) {
2096 0         0 $builder->comment('JIT: URL decode helper (query/form parsing enabled)')
2097             ->line('static int url_decode(char* str, int len) {')
2098             ->line(' char* dst = str;')
2099             ->line(' const char* src = str;')
2100             ->line(' const char* end = str + len;')
2101             ->line(' while (src < end) {')
2102             ->line(' if (*src == \'%\' && src + 2 < end) {')
2103             ->line(' int hi = src[1];')
2104             ->line(' int lo = src[2];')
2105             ->line(' hi = (hi >= \'0\' && hi <= \'9\') ? hi - \'0\' :')
2106             ->line(' (hi >= \'A\' && hi <= \'F\') ? hi - \'A\' + 10 :')
2107             ->line(' (hi >= \'a\' && hi <= \'f\') ? hi - \'a\' + 10 : -1;')
2108             ->line(' lo = (lo >= \'0\' && lo <= \'9\') ? lo - \'0\' :')
2109             ->line(' (lo >= \'A\' && lo <= \'F\') ? lo - \'A\' + 10 :')
2110             ->line(' (lo >= \'a\' && lo <= \'f\') ? lo - \'a\' + 10 : -1;')
2111             ->line(' if (hi >= 0 && lo >= 0) {')
2112             ->line(' *dst++ = (char)((hi << 4) | lo);')
2113             ->line(' src += 3;')
2114             ->line(' continue;')
2115             ->line(' }')
2116             ->line(' } else if (*src == \'+\') {')
2117             ->line(' *dst++ = \' \';')
2118             ->line(' src++;')
2119             ->line(' continue;')
2120             ->line(' }')
2121             ->line(' *dst++ = *src++;')
2122             ->line(' }')
2123             ->line(' return dst - str;')
2124             ->line('}')
2125             ->blank;
2126            
2127 0         0 $builder->comment('JIT: Query string parser (query/form parsing enabled)')
2128             ->line('static void parse_query_string(pTHX_ const char* query, int query_len, HV* hv) {')
2129             ->line(' const char* start = query;')
2130             ->line(' const char* end = query + query_len;')
2131             ->line(' while (start < end) {')
2132             ->line(' const char* eq = memchr(start, \'=\', end - start);')
2133             ->line(' const char* amp = memchr(start, \'&\', end - start);')
2134             ->line(' if (!amp) amp = end;')
2135             ->line(' if (eq && eq < amp) {')
2136             ->line(' int key_len = eq - start;')
2137             ->line(' const char* val = eq + 1;')
2138             ->line(' int val_len = amp - val;')
2139             ->line(' char val_buf[1024];')
2140             ->line(' if (val_len < (int)sizeof(val_buf)) {')
2141             ->line(' memcpy(val_buf, val, val_len);')
2142             ->line(' val_len = url_decode(val_buf, val_len);')
2143             ->line(' hv_store(hv, start, key_len, newSVpvn(val_buf, val_len), 0);')
2144             ->line(' }')
2145             ->line(' } else if (amp > start) {')
2146             ->line(' hv_store(hv, start, amp - start, newSVpvn("", 0), 0);')
2147             ->line(' }')
2148             ->line(' start = amp + 1;')
2149             ->line(' }')
2150             ->line('}')
2151             ->blank;
2152             }
2153            
2154             # Header parser - only if headers/cookies/json/form needed
2155 4 50 66     67 if ($analysis->{needs_headers} || $analysis->{needs_cookies} ||
      66        
      33        
2156             $analysis->{needs_json} || $analysis->{needs_form}) {
2157 1         35 $builder->comment('JIT: Header parser (headers/cookies/json/form enabled)')
2158             ->line('static void parse_headers(pTHX_ const char* raw, int raw_len, HV* hv) {')
2159             ->line(' const char* line = memchr(raw, \'\\n\', raw_len);')
2160             ->line(' if (!line) return;')
2161             ->line(' line++;')
2162             ->line(' const char* end = raw + raw_len;')
2163             ->line(' while (line < end) {')
2164             ->line(' const char* line_end = memchr(line, \'\\r\', end - line);')
2165             ->line(' if (!line_end) line_end = memchr(line, \'\\n\', end - line);')
2166             ->line(' if (!line_end || line_end == line) break;')
2167             ->line(' const char* colon = memchr(line, \':\', line_end - line);')
2168             ->line(' if (colon) {')
2169             ->line(' int name_len = colon - line;')
2170             ->line(' const char* value = colon + 1;')
2171             ->line(' while (value < line_end && (*value == \' \' || *value == \'\\t\')) value++;')
2172             ->line(' int value_len = line_end - value;')
2173             ->line(' char name_buf[128];')
2174             ->line(' if (name_len < (int)sizeof(name_buf)) {')
2175             ->line(' int i;')
2176             ->line(' for (i = 0; i < name_len; i++) {')
2177             ->line(' char c = line[i];')
2178             ->line(' name_buf[i] = (c >= \'A\' && c <= \'Z\') ? c + 32 : (c == \'-\') ? \'_\' : c;')
2179             ->line(' }')
2180             ->line(' hv_store(hv, name_buf, name_len, newSVpvn(value, value_len), 0);')
2181             ->line(' }')
2182             ->line(' }')
2183             ->line(' line = line_end;')
2184             ->line(' if (*line == \'\\r\') line++;')
2185             ->line(' if (line < end && *line == \'\\n\') line++;')
2186             ->line(' }')
2187             ->line('}')
2188             ->blank;
2189             }
2190            
2191             # JIT: Generate C helpers from builder middleware (static functions at file scope)
2192 4 50 33     49 if ($analysis->{has_builder_before} || $analysis->{has_builder_after}) {
2193 0         0 my %seen_classes;
2194 0         0 $builder->comment('JIT: Builder middleware C helpers (zero Perl in hot path)');
2195 0         0 for my $mw (@{$analysis->{builder_before}}, @{$analysis->{builder_after}}) {
  0         0  
  0         0  
2196 0         0 my $class = ref($mw);
2197 0 0       0 next if $seen_classes{$class}++; # Deduplicate by class
2198 0 0       0 if ($mw->can('build_helpers')) {
2199 0         0 $mw->build_helpers($builder);
2200 0         0 $builder->blank;
2201             }
2202             }
2203             }
2204              
2205             # JIT: Generate middleware helper function BEFORE call_dynamic_handler
2206 4 100       14 if ($analysis->{has_any_middleware}) {
2207 1         42 $builder->comment('JIT: Middleware helper - call array of handlers, short-circuit on defined return')
2208             ->line('static SV* call_middleware_chain(pTHX_ AV* handlers, SV* req_ref) {')
2209             ->line(' dSP;')
2210             ->line(' SSize_t len = av_len(handlers) + 1;')
2211             ->line(' SSize_t i;')
2212             ->line(' for (i = 0; i < len; i++) {')
2213             ->line(' SV** handler_sv = av_fetch(handlers, i, 0);')
2214             ->line(' if (!handler_sv || !SvROK(*handler_sv)) continue;')
2215             ->line(' PUSHMARK(SP);')
2216             ->line(' XPUSHs(req_ref);')
2217             ->line(' PUTBACK;')
2218             ->line(' int count = call_sv(*handler_sv, G_SCALAR | G_EVAL);')
2219             ->line(' SPAGAIN;')
2220             ->line(' if (SvTRUE(ERRSV)) {')
2221             ->line(' POPs;')
2222             ->line(' continue;')
2223             ->line(' }')
2224             ->line(' if (count == 1) {')
2225             ->line(' SV* result = POPs;')
2226             ->line(' PUTBACK;')
2227             ->line(' if (SvOK(result)) {')
2228             ->line(' return SvREFCNT_inc(result);')
2229             ->line(' }')
2230             ->line(' }')
2231             ->line(' PUTBACK;')
2232             ->line(' }')
2233             ->line(' return NULL;')
2234             ->line('}')
2235             ->blank;
2236             }
2237            
2238             # Main handler function
2239 4         315 $builder->comment('Call dynamic Perl handler and format HTTP response')
2240             ->line('static void call_dynamic_handler(pTHX_ int handler_idx, int client_fd,')
2241             ->line(' const char* method, int method_len,')
2242             ->line(' const char* path, int path_len,')
2243             ->line(' const char* body, int body_len,')
2244             ->line(' const char* raw_request, int raw_request_len,')
2245             ->line(' char** resp_out, int* resp_len_out) {')
2246             ->line(' dSP;')
2247             ->line(' int count;')
2248             ->line(' SV* result;')
2249             ->line(' STRLEN len;')
2250             ->line(' const char* body_str;')
2251             ->line(' int status = 200;')
2252             ->line(' const char* content_type = "text/plain";')
2253             ->line(' AV* handlers;')
2254             ->line(' SV** handler_sv;')
2255             ->line(' const char* query_start;')
2256             ->line(' int clean_path_len;')
2257             ->line(' AV* req;')
2258             ->line(' const char* segments[16];')
2259             ->line(' int seg_lens[16];')
2260             ->line(' int seg_count;')
2261             ->line(' AV* seg_av;')
2262             ->line(' int i;')
2263             ->line(' HV* params_hv;')
2264             ->line(' RouteParamInfo* param_info;')
2265             ->line(' SV* req_ref;')
2266             ->line(' SV* mw_result = NULL;')
2267             ->line(' int short_circuit = 0;')
2268             ->line(' HV* headers_hv = NULL;')
2269             ->line(' int is_streaming = 0;')
2270             ->line(' SV* stream_sv = NULL;')
2271             ->blank
2272             ->line(' if (!g_handler_array) {')
2273             ->line(' *resp_out = (char*)RESP_404;')
2274             ->line(' *resp_len_out = RESP_404_LEN;')
2275             ->line(' return;')
2276             ->line(' }')
2277             ->blank
2278             ->line(' handlers = (AV*)SvRV(g_handler_array);')
2279             ->line(' handler_sv = av_fetch(handlers, handler_idx, 0);')
2280             ->line(' if (!handler_sv || !SvROK(*handler_sv)) {')
2281             ->line(' *resp_out = (char*)RESP_404;')
2282             ->line(' *resp_len_out = RESP_404_LEN;')
2283             ->line(' return;')
2284             ->line(' }')
2285             ->blank;
2286            
2287             # Query string separation
2288 4         93 $builder->comment('Separate path from query string')
2289             ->line(' query_start = memchr(path, \'?\', path_len);')
2290             ->line(' clean_path_len = query_start ? (query_start - path) : path_len;')
2291             ->blank
2292             ->comment('Build array-based request object (JIT slots)')
2293             ->comment('Slot layout: METHOD=0, PATH=1, BODY=2, PARAMS=3, QUERY=4, QUERY_STRING=5,')
2294             ->comment(' HEADERS=6, COOKIES=7, JSON=8, FORM=9, SEGMENTS=10, ID=11')
2295             ->line(' req = newAV();')
2296             ->line(' av_extend(req, 11);') # Pre-allocate 12 slots (0-11)
2297             ->line(' av_store(req, 0, newSVpvn(method, method_len));') # SLOT_METHOD
2298             ->line(' av_store(req, 1, newSVpvn(path, clean_path_len));') # SLOT_PATH
2299             ->line(' av_store(req, 2, newSVpvn(body, body_len));') # SLOT_BODY
2300             ->blank;
2301              
2302             # Path segments and params - always needed
2303 4         162 $builder->comment('Parse path segments and named params')
2304             ->line(' seg_count = parse_path_segments(path, path_len, segments, seg_lens, 16);')
2305             ->line(' seg_av = newAV();')
2306             ->line(' for (i = 0; i < seg_count; i++) {')
2307             ->line(' av_push(seg_av, newSVpvn(segments[i], seg_lens[i]));')
2308             ->line(' }')
2309             ->line(' av_store(req, 10, newRV_noinc((SV*)seg_av));') # SLOT_SEGMENTS
2310             ->blank
2311             ->comment('Build named params from route_param_info table')
2312             ->line(' params_hv = newHV();')
2313             ->line(' param_info = &g_route_params[handler_idx];')
2314             ->line(' for (i = 0; i < param_info->count && i < seg_count; i++) {')
2315             ->line(' int pos = param_info->params[i].position;')
2316             ->line(' if (pos < seg_count) {')
2317             ->line(' hv_store(params_hv, param_info->params[i].name,')
2318             ->line(' strlen(param_info->params[i].name),')
2319             ->line(' newSVpvn(segments[pos], seg_lens[pos]), 0);')
2320             ->line(' }')
2321             ->line(' }')
2322             ->line(' av_store(req, 3, newRV_noinc((SV*)params_hv));') # SLOT_PARAMS
2323             ->line(' if (seg_count > 0) {')
2324             ->line(' av_store(req, 11, newSVpvn(segments[seg_count-1], seg_lens[seg_count-1]));') # SLOT_ID
2325             ->line(' } else {')
2326             ->line(' av_store(req, 11, newSVpvn("", 0));') # SLOT_ID (empty)
2327             ->line(' }')
2328             ->blank;
2329            
2330             # Query string parsing - JIT conditional
2331 4 50       31 if ($analysis->{needs_query}) {
2332 0         0 $builder->comment('JIT: Parse query string (parse_query enabled)')
2333             ->line(' if (query_start) {')
2334             ->line(' HV* query_hv = newHV();')
2335             ->line(' int query_len = path_len - (query_start - path) - 1;')
2336             ->line(' parse_query_string(aTHX_ query_start + 1, query_len, query_hv);')
2337             ->line(' av_store(req, 4, newRV_noinc((SV*)query_hv));') # SLOT_QUERY
2338             ->line(' av_store(req, 5, newSVpvn(query_start + 1, query_len));') # SLOT_QUERY_STRING
2339             ->line(' } else {')
2340             ->line(' av_store(req, 4, newRV_noinc((SV*)newHV()));') # SLOT_QUERY
2341             ->line(' av_store(req, 5, newSVpvn("", 0));') # SLOT_QUERY_STRING
2342             ->line(' }')
2343             ->blank;
2344             } else {
2345 4         26 $builder->comment('JIT: Query parsing SKIPPED (no routes use parse_query)')
2346             ->line(' av_store(req, 4, newRV_noinc((SV*)newHV()));') # SLOT_QUERY
2347             ->line(' av_store(req, 5, newSVpvn("", 0));') # SLOT_QUERY_STRING
2348             ->blank;
2349             }
2350            
2351             # Header parsing - JIT conditional
2352             my $needs_header_parse = $analysis->{needs_headers} || $analysis->{needs_cookies} ||
2353 4   33     143 $analysis->{needs_json} || $analysis->{needs_form};
2354            
2355 4 100       16 if ($needs_header_parse) {
2356 1         7 $builder->comment('JIT: Parse headers (headers/cookies/json/form enabled)')
2357             ->line(' headers_hv = newHV();')
2358             ->line(' parse_headers(aTHX_ raw_request, raw_request_len, headers_hv);')
2359             ->line(' av_store(req, 6, newRV_noinc((SV*)headers_hv));') # SLOT_HEADERS
2360             ->blank;
2361            
2362             # Cookie parsing
2363 1 50       2 if ($analysis->{needs_cookies}) {
2364 1         34 $builder->comment('JIT: Parse cookies (parse_cookies enabled)')
2365             ->line(' SV** cookie_sv = hv_fetch(headers_hv, "cookie", 6, 0);')
2366             ->line(' if (cookie_sv && SvOK(*cookie_sv)) {')
2367             ->line(' HV* cookies_hv = newHV();')
2368             ->line(' STRLEN cookie_len;')
2369             ->line(' const char* cookie_str = SvPV(*cookie_sv, cookie_len);')
2370             ->line(' const char* start = cookie_str;')
2371             ->line(' const char* end = cookie_str + cookie_len;')
2372             ->line(' while (start < end) {')
2373             ->line(' while (start < end && (*start == \' \' || *start == \';\')) start++;')
2374             ->line(' if (start >= end) break;')
2375             ->line(' const char* eq = memchr(start, \'=\', end - start);')
2376             ->line(' const char* semi = memchr(start, \';\', end - start);')
2377             ->line(' if (!semi) semi = end;')
2378             ->line(' if (eq && eq < semi) {')
2379             ->line(' int name_len = eq - start;')
2380             ->line(' const char* val = eq + 1;')
2381             ->line(' int val_len = semi - val;')
2382             ->line(' hv_store(cookies_hv, start, name_len, newSVpvn(val, val_len), 0);')
2383             ->line(' }')
2384             ->line(' start = semi + 1;')
2385             ->line(' }')
2386             ->line(' av_store(req, 7, newRV_noinc((SV*)cookies_hv));') # SLOT_COOKIES
2387             ->line(' } else {')
2388             ->line(' av_store(req, 7, newRV_noinc((SV*)newHV()));') # SLOT_COOKIES
2389             ->line(' }')
2390             ->blank;
2391             } else {
2392 0         0 $builder->comment('JIT: Cookie parsing SKIPPED')
2393             ->line(' av_store(req, 7, newRV_noinc((SV*)newHV()));') # SLOT_COOKIES
2394             ->blank;
2395             }
2396            
2397             # Form/JSON parsing
2398 1 50 33     8 if ($analysis->{needs_form} || $analysis->{needs_json}) {
2399 1         9 $builder->comment('JIT: Form/JSON body parsing')
2400             ->line(' SV** ct_sv = hv_fetch(headers_hv, "content_type", 12, 0);')
2401             ->line(' if (ct_sv && SvOK(*ct_sv) && body_len > 0) {')
2402             ->line(' STRLEN ct_len;')
2403             ->line(' const char* ct_str = SvPV(*ct_sv, ct_len);');
2404            
2405 1 50       3 if ($analysis->{needs_form}) {
2406 0         0 $builder->line(' if (ct_len >= 33 && memcmp(ct_str, "application/x-www-form-urlencoded", 33) == 0) {')
2407             ->line(' HV* form_hv = newHV();')
2408             ->line(' parse_query_string(aTHX_ body, body_len, form_hv);')
2409             ->line(' av_store(req, 9, newRV_noinc((SV*)form_hv));') # SLOT_FORM
2410             ->line(' } else {')
2411             ->line(' av_store(req, 9, newRV_noinc((SV*)newHV()));') # SLOT_FORM
2412             ->line(' }');
2413             } else {
2414 1         4 $builder->line(' av_store(req, 9, newRV_noinc((SV*)newHV()));'); # SLOT_FORM
2415             }
2416              
2417 1 50       4 if ($analysis->{needs_json}) {
2418 1         28 $builder->line(' if (ct_len >= 16 && memcmp(ct_str, "application/json", 16) == 0) {')
2419             ->line(' dSP;')
2420             ->line(' PUSHMARK(SP);')
2421             ->line(' XPUSHs(sv_2mortal(newSVpvn(body, body_len)));')
2422             ->line(' PUTBACK;')
2423             ->line(' int json_count = call_pv("Hypersonic::_decode_json", G_SCALAR | G_EVAL);')
2424             ->line(' SPAGAIN;')
2425             ->line(' if (json_count == 1 && !SvTRUE(ERRSV)) {')
2426             ->line(' SV* json_sv = POPs;')
2427             ->line(' av_store(req, 8, SvREFCNT_inc(json_sv));') # SLOT_JSON
2428             ->line(' } else {')
2429             ->line(' if (SvTRUE(ERRSV)) POPs;')
2430             ->line(' av_store(req, 8, &PL_sv_undef);') # SLOT_JSON
2431             ->line(' }')
2432             ->line(' PUTBACK;')
2433             ->line(' } else {')
2434             ->line(' av_store(req, 8, &PL_sv_undef);') # SLOT_JSON
2435             ->line(' }');
2436             } else {
2437 0         0 $builder->line(' av_store(req, 8, &PL_sv_undef);'); # SLOT_JSON
2438             }
2439              
2440 1         6 $builder->line(' } else {')
2441             ->line(' av_store(req, 9, newRV_noinc((SV*)newHV()));') # SLOT_FORM
2442             ->line(' av_store(req, 8, &PL_sv_undef);') # SLOT_JSON
2443             ->line(' }')
2444             ->blank;
2445             } else {
2446 0         0 $builder->comment('JIT: Form/JSON parsing SKIPPED')
2447             ->line(' av_store(req, 9, newRV_noinc((SV*)newHV()));') # SLOT_FORM
2448             ->line(' av_store(req, 8, &PL_sv_undef);') # SLOT_JSON
2449             ->blank;
2450             }
2451             } else {
2452 3         62 $builder->comment('JIT: All header-based parsing SKIPPED (no routes use these features)')
2453             ->line(' av_store(req, 6, newRV_noinc((SV*)newHV()));') # SLOT_HEADERS
2454             ->line(' av_store(req, 7, newRV_noinc((SV*)newHV()));') # SLOT_COOKIES
2455             ->line(' av_store(req, 8, &PL_sv_undef);') # SLOT_JSON
2456             ->line(' av_store(req, 9, newRV_noinc((SV*)newHV()));') # SLOT_FORM
2457             ->blank;
2458             }
2459              
2460             # Call handler and build response
2461 4         32 $builder->comment('Bless array into Hypersonic::Request and call Perl handler')
2462             ->line(' req_ref = newRV_noinc((SV*)req);')
2463             ->line(' sv_bless(req_ref, gv_stashpv("Hypersonic::Request", GV_ADD));')
2464             ->line(' ENTER;')
2465             ->line(' SAVETMPS;');
2466            
2467             # JIT: Add middleware short-circuit variable only if middleware present
2468             # (mw_result and short_circuit already declared at function top)
2469            
2470             # JIT: Builder-based before middleware (inline C - no Perl calls)
2471 4 50       16 if ($analysis->{has_builder_before}) {
2472             my $ctx = {
2473             req_var => 'req',
2474             req_ref_var => 'req_ref',
2475             slots => $analysis->{middleware_slots},
2476 0         0 };
2477 0         0 $builder->blank
2478             ->comment('JIT: Builder before middleware (inline C - zero Perl overhead)');
2479 0         0 for my $mw (@{$analysis->{builder_before}}) {
  0         0  
2480 0 0       0 if ($mw->can('build_before')) {
2481 0         0 $mw->build_before($builder, $ctx);
2482             }
2483             }
2484             }
2485              
2486             # JIT: Call global before middleware (Perl coderefs via call_sv)
2487 4 100       13 if ($analysis->{has_global_before}) {
2488 1         24 $builder->blank
2489             ->comment('JIT: Call global before middleware (Perl)')
2490             ->line(' if (g_before_middleware && SvROK(g_before_middleware)) {')
2491             ->line(' AV* before_arr = (AV*)SvRV(g_before_middleware);')
2492             ->line(' mw_result = call_middleware_chain(aTHX_ before_arr, req_ref);')
2493             ->line(' if (mw_result) {')
2494             ->line(' result = mw_result;')
2495             ->line(' short_circuit = 1;')
2496             ->line(' }')
2497             ->line(' }');
2498             }
2499            
2500             # JIT: Call per-route before middleware
2501 4 50       19 if ($analysis->{has_route_middleware}) {
2502 0         0 $builder->blank
2503             ->comment('JIT: Call per-route before middleware')
2504             ->line(' if (!short_circuit && g_route_before_middleware && SvROK(g_route_before_middleware)) {')
2505             ->line(' AV* route_before = (AV*)SvRV(g_route_before_middleware);')
2506             ->line(' SV** handler_arr_ref = av_fetch(route_before, handler_idx, 0);')
2507             ->line(' if (handler_arr_ref && SvROK(*handler_arr_ref)) {')
2508             ->line(' AV* handler_arr = (AV*)SvRV(*handler_arr_ref);')
2509             ->line(' if (av_len(handler_arr) >= 0) {')
2510             ->line(' mw_result = call_middleware_chain(aTHX_ handler_arr, req_ref);')
2511             ->line(' if (mw_result) {')
2512             ->line(' result = mw_result;')
2513             ->line(' short_circuit = 1;')
2514             ->line(' }')
2515             ->line(' }')
2516             ->line(' }')
2517             ->line(' }');
2518             }
2519            
2520             # JIT: Streaming handler support
2521 4 100       19 if ($analysis->{needs_streaming}) {
2522 2         47 $builder->blank
2523             ->comment('JIT: Check if this is a streaming handler')
2524             ->line(' is_streaming = g_streaming_handlers[handler_idx];')
2525             ->if('is_streaming')
2526             ->comment('Create Hypersonic::Stream object for streaming handler')
2527             ->line('dSP;')
2528             ->line('PUSHMARK(SP);')
2529             ->line('XPUSHs(sv_2mortal(newSVpv("Hypersonic::Stream", 0)));')
2530             ->line('XPUSHs(sv_2mortal(newSVpv("fd", 0)));')
2531             ->line('XPUSHs(sv_2mortal(newSViv(client_fd)));')
2532             ->line('PUTBACK;')
2533             ->line('int stream_count = call_method("new", G_SCALAR);')
2534             ->line('SPAGAIN;')
2535             ->if('stream_count > 0')
2536             ->line('stream_sv = POPs;')
2537             ->line('SvREFCNT_inc(stream_sv);')
2538             ->endif
2539             ->line('PUTBACK;')
2540             ->endif;
2541             }
2542              
2543             # Call the main handler (conditionally if middleware present)
2544 4 100       82 if ($analysis->{has_any_middleware}) {
2545 1         7 $builder->blank
2546             ->comment('Call main handler (unless middleware short-circuited)')
2547             ->line(' if (!short_circuit) {')
2548             ->line(' PUSHMARK(SP);')
2549             ->line(' XPUSHs(req_ref);');
2550              
2551             # For streaming handlers with middleware
2552 1 50       2 if ($analysis->{needs_streaming}) {
2553 0         0 $builder->line(' if (is_streaming && stream_sv) XPUSHs(stream_sv);');
2554             }
2555              
2556 1         7 $builder->line(' PUTBACK;')
2557             ->line(' count = call_sv(*handler_sv, G_SCALAR | G_EVAL);')
2558             ->line(' SPAGAIN;')
2559             ->line(' if (count == 1) result = POPs;')
2560             ->line(' PUTBACK;')
2561             ->line(' }');
2562             } else {
2563 3         20 $builder->line(' PUSHMARK(SP);')
2564             ->line(' XPUSHs(sv_2mortal(req_ref));');
2565              
2566             # For streaming handlers without middleware
2567 3 100       9 if ($analysis->{needs_streaming}) {
2568 2         5 $builder->line(' if (is_streaming && stream_sv) XPUSHs(stream_sv);');
2569             }
2570              
2571 3         78 $builder->line(' PUTBACK;')
2572             ->line(' count = call_sv(*handler_sv, G_SCALAR | G_EVAL);')
2573             ->line(' SPAGAIN;');
2574             }
2575            
2576             # JIT: Call per-route after middleware
2577 4 50       17 if ($analysis->{has_route_middleware}) {
2578 0         0 $builder->blank
2579             ->comment('JIT: Call per-route after middleware')
2580             ->line(' if (g_route_after_middleware && SvROK(g_route_after_middleware)) {')
2581             ->line(' AV* route_after = (AV*)SvRV(g_route_after_middleware);')
2582             ->line(' SV** handler_arr_ref = av_fetch(route_after, handler_idx, 0);')
2583             ->line(' if (handler_arr_ref && SvROK(*handler_arr_ref)) {')
2584             ->line(' AV* handler_arr = (AV*)SvRV(*handler_arr_ref);')
2585             ->line(' if (av_len(handler_arr) >= 0) {')
2586             ->line(' SV* after_result = call_middleware_chain(aTHX_ handler_arr, req_ref);')
2587             ->line(' if (after_result) {')
2588             ->line(' result = after_result;')
2589             ->line(' }')
2590             ->line(' }')
2591             ->line(' }')
2592             ->line(' }');
2593             }
2594            
2595             # JIT: Call global after middleware (Perl coderefs via call_sv)
2596 4 100       14 if ($analysis->{has_global_after}) {
2597 1         9 $builder->blank
2598             ->comment('JIT: Call global after middleware (Perl)')
2599             ->line(' if (g_after_middleware && SvROK(g_after_middleware)) {')
2600             ->line(' AV* after_arr = (AV*)SvRV(g_after_middleware);')
2601             ->line(' SV* after_result = call_middleware_chain(aTHX_ after_arr, req_ref);')
2602             ->line(' if (after_result) {')
2603             ->line(' result = after_result;')
2604             ->line(' }')
2605             ->line(' }');
2606             }
2607              
2608             # JIT: Builder-based after middleware (inline C - no Perl calls)
2609 4 50       43 if ($analysis->{has_builder_after}) {
2610             my $ctx = {
2611             req_var => 'req',
2612             req_ref_var => 'req_ref',
2613             res_var => 'result',
2614             slots => $analysis->{middleware_slots},
2615 0         0 };
2616 0         0 $builder->blank
2617             ->comment('JIT: Builder after middleware (inline C - zero Perl overhead)');
2618 0         0 for my $mw (@{$analysis->{builder_after}}) {
  0         0  
2619 0 0       0 if ($mw->can('build_after')) {
2620 0         0 $mw->build_after($builder, $ctx);
2621             }
2622             }
2623             }
2624              
2625             # JIT: Streaming handlers - early return (response already sent via Stream)
2626 4 100       17 if ($analysis->{needs_streaming}) {
2627 2         99 $builder->blank
2628             ->comment('JIT: Streaming handlers return early - response sent via Stream object')
2629             ->if('is_streaming')
2630             ->line('if (stream_sv) SvREFCNT_dec(stream_sv);')
2631             ->line('FREETMPS;')
2632             ->line('LEAVE;')
2633             ->line('*resp_out = NULL;')
2634             ->line('*resp_len_out = -1;')
2635             ->comment('Signal streaming response - caller should not send')
2636             ->line('return;')
2637             ->endif;
2638             }
2639              
2640             $builder->blank
2641 4         93 ->line(' if (SvTRUE(ERRSV)) {')
2642             ->line(' static char error_resp[512];')
2643             ->line(' int err_len = snprintf(error_resp, sizeof(error_resp),')
2644             ->line(' "HTTP/1.1 500 Internal Server Error\\r\\n"')
2645             ->line(' "Content-Type: text/plain\\r\\n"')
2646             ->line(' "Content-Length: 21\\r\\n"')
2647             ->line(' "Connection: close\\r\\n\\r\\n"')
2648             ->line(' "Internal Server Error");')
2649             ->line(' *resp_out = error_resp;')
2650             ->line(' *resp_len_out = err_len;');
2651            
2652             # Middleware version already has result set, no-middleware version needs POPs
2653 4 100       15 if ($analysis->{has_any_middleware}) {
2654 1         6 $builder->line(' } else if (SvOK(result)) {')
2655             ->line(' HV* custom_headers = NULL;');
2656             } else {
2657 3         35 $builder->line(' POPs;')
2658             ->line(' } else if (count == 1) {')
2659             ->line(' result = POPs;')
2660             ->line(' if (SvOK(result)) {')
2661             ->line(' HV* custom_headers = NULL;');
2662             }
2663              
2664 4         326 $builder
2665             ->comment(' Handle arrayref [status, headers, body]')
2666             ->line(' if (SvROK(result) && SvTYPE(SvRV(result)) == SVt_PVAV) {')
2667             ->line(' AV* arr = (AV*)SvRV(result);')
2668             ->line(' SV** status_sv = av_fetch(arr, 0, 0);')
2669             ->line(' SV** headers_sv = av_fetch(arr, 1, 0);')
2670             ->line(' SV** body_sv = av_fetch(arr, 2, 0);')
2671             ->line(' if (status_sv) status = (int)SvIV(*status_sv);')
2672             ->line(' if (body_sv) body_str = SvPV(*body_sv, len);')
2673             ->line(' else { body_str = ""; len = 0; }')
2674             ->line(' if (headers_sv && SvROK(*headers_sv) && SvTYPE(SvRV(*headers_sv)) == SVt_PVHV) {')
2675             ->line(' custom_headers = (HV*)SvRV(*headers_sv);')
2676             ->line(' SV** ct_sv = hv_fetch(custom_headers, "Content-Type", 12, 0);')
2677             ->line(' if (ct_sv && SvOK(*ct_sv)) {')
2678             ->line(' STRLEN ct_len;')
2679             ->line(' content_type = SvPV(*ct_sv, ct_len);')
2680             ->line(' }')
2681             ->line(' }')
2682             ->line(' }')
2683             ->comment(' Handle hashref {status, headers, body}')
2684             ->line(' else if (SvROK(result) && SvTYPE(SvRV(result)) == SVt_PVHV) {')
2685             ->line(' HV* hash = (HV*)SvRV(result);')
2686             ->line(' SV** status_sv = hv_fetch(hash, "status", 6, 0);')
2687             ->line(' SV** headers_sv = hv_fetch(hash, "headers", 7, 0);')
2688             ->line(' SV** body_sv = hv_fetch(hash, "body", 4, 0);')
2689             ->line(' if (status_sv) status = (int)SvIV(*status_sv);')
2690             ->line(' if (body_sv) body_str = SvPV(*body_sv, len);')
2691             ->line(' else { body_str = ""; len = 0; }')
2692             ->line(' if (headers_sv && SvROK(*headers_sv) && SvTYPE(SvRV(*headers_sv)) == SVt_PVHV) {')
2693             ->line(' custom_headers = (HV*)SvRV(*headers_sv);')
2694             ->line(' SV** ct_sv = hv_fetch(custom_headers, "Content-Type", 12, 0);')
2695             ->line(' if (ct_sv && SvOK(*ct_sv)) {')
2696             ->line(' STRLEN ct_len;')
2697             ->line(' content_type = SvPV(*ct_sv, ct_len);')
2698             ->line(' }')
2699             ->line(' }')
2700             ->line(' }')
2701             ->comment(' Plain string response')
2702             ->line(' else {')
2703             ->line(' body_str = SvPV(result, len);')
2704             ->line(' }')
2705             ->blank
2706             ->comment(' Auto-detect JSON content type')
2707             ->line(' if (strcmp(content_type, "text/plain") == 0 && len > 0 &&')
2708             ->line(' (body_str[0] == \'{\' || body_str[0] == \'[\')) {')
2709             ->line(' content_type = "application/json";')
2710             ->line(' }')
2711             ->blank;
2712            
2713             # JIT: Add compression logic only if compression is enabled
2714 4 50       17 if ($self->{_compression_enabled}) {
2715 0         0 my $config = $self->{_compression_config};
2716 0   0     0 my $min_size = $config->{min_size} // 1024;
2717            
2718 0         0 $builder
2719             ->comment(' Gzip compression - check Accept-Encoding')
2720             ->line('#ifdef HYPERSONIC_COMPRESSION')
2721             ->line(' int use_gzip = 0;')
2722             ->line(' unsigned char* compressed_body = NULL;')
2723             ->line(' size_t compressed_len = 0;')
2724             ->blank
2725             ->comment(' Get Accept-Encoding from request (from SLOT_HEADERS)')
2726             ->line(' SV** req_arr = AvARRAY(req);')
2727             ->line(' HV* hdrs = NULL;')
2728             ->line(' if (req_arr[6] && SvROK(req_arr[6])) {')
2729             ->line(' hdrs = (HV*)SvRV(req_arr[6]);')
2730             ->line(' }')
2731             ->line(' if (hdrs && len >= ' . $min_size . ') {')
2732             ->line(' SV** ae = hv_fetch(hdrs, "accept_encoding", 15, 0);')
2733             ->line(' if (ae && SvOK(*ae)) {')
2734             ->line(' STRLEN ae_len;')
2735             ->line(' const char* ae_str = SvPV(*ae, ae_len);')
2736             ->line(' if (accepts_gzip(ae_str, ae_len)) {')
2737             ->line(' compressed_len = gzip_compress(body_str, len, &compressed_body);')
2738             ->line(' if (compressed_len > 0) {')
2739             ->line(' use_gzip = 1;')
2740             ->line(' body_str = (const char*)compressed_body;')
2741             ->line(' len = compressed_len;')
2742             ->line(' }')
2743             ->line(' }')
2744             ->line(' }')
2745             ->line(' }')
2746             ->line('#endif')
2747             ->blank;
2748             }
2749            
2750             $builder
2751 4         456 ->comment(' Build response with custom headers support')
2752             ->line(' static __thread char resp_buf[65536];')
2753             ->line(' int hdr_len;')
2754             ->line('#ifdef HYPERSONIC_SECURITY_HEADERS')
2755             ->line(' hdr_len = snprintf(resp_buf, 2048,')
2756             ->line(' "HTTP/1.1 %d %s\\r\\n"')
2757             ->line(' "Content-Type: %s\\r\\n"')
2758             ->line(' "Content-Length: %zu\\r\\n"')
2759             ->line(' "Connection: keep-alive\\r\\n"')
2760             ->line(' "%s",')
2761             ->line(' status, get_status_text(status), content_type, len, SECURITY_HEADERS);')
2762             ->line('#else')
2763             ->line(' hdr_len = snprintf(resp_buf, 512,')
2764             ->line(' "HTTP/1.1 %d %s\\r\\n"')
2765             ->line(' "Content-Type: %s\\r\\n"')
2766             ->line(' "Content-Length: %zu\\r\\n"')
2767             ->line(' "Connection: keep-alive\\r\\n",')
2768             ->line(' status, get_status_text(status), content_type, len);')
2769             ->line('#endif')
2770             ->blank
2771             ->comment(' Add custom headers from response (Location, Set-Cookie, etc.)')
2772             ->line(' if (custom_headers) {')
2773             ->line(' HE* entry;')
2774             ->line(' hv_iterinit(custom_headers);')
2775             ->line(' while ((entry = hv_iternext(custom_headers))) {')
2776             ->line(' I32 klen;')
2777             ->line(' const char* key = hv_iterkey(entry, &klen);')
2778             ->comment(' Skip Content-Type/Content-Length (already added)')
2779             ->line(' if (klen == 12 && memcmp(key, "Content-Type", 12) == 0) continue;')
2780             ->line(' if (klen == 14 && memcmp(key, "Content-Length", 14) == 0) continue;')
2781             ->line(' SV* val = hv_iterval(custom_headers, entry);')
2782             ->comment(' Handle Set-Cookie array (multiple cookies)')
2783             ->line(' if (SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVAV) {')
2784             ->line(' AV* arr = (AV*)SvRV(val);')
2785             ->line(' SSize_t arr_len = av_len(arr) + 1;')
2786             ->line(' SSize_t j;')
2787             ->line(' for (j = 0; j < arr_len; j++) {')
2788             ->line(' SV** item = av_fetch(arr, j, 0);')
2789             ->line(' if (item && SvOK(*item)) {')
2790             ->line(' STRLEN vlen;')
2791             ->line(' const char* vstr = SvPV(*item, vlen);')
2792             ->line(' hdr_len += snprintf(resp_buf + hdr_len, sizeof(resp_buf) - hdr_len,')
2793             ->line(' "%.*s: %.*s\\r\\n", (int)klen, key, (int)vlen, vstr);')
2794             ->line(' }')
2795             ->line(' }')
2796             ->line(' } else if (SvOK(val)) {')
2797             ->line(' STRLEN vlen;')
2798             ->line(' const char* vstr = SvPV(val, vlen);')
2799             ->line(' hdr_len += snprintf(resp_buf + hdr_len, sizeof(resp_buf) - hdr_len,')
2800             ->line(' "%.*s: %.*s\\r\\n", (int)klen, key, (int)vlen, vstr);')
2801             ->line(' }')
2802             ->line(' }')
2803             ->line(' }')
2804             ->blank
2805             ->comment(' Add Content-Encoding header if gzip was used')
2806             ->line('#ifdef HYPERSONIC_COMPRESSION')
2807             ->line(' if (use_gzip) {')
2808             ->line(' hdr_len += snprintf(resp_buf + hdr_len, sizeof(resp_buf) - hdr_len,')
2809             ->line(' "Content-Encoding: gzip\\r\\n");')
2810             ->line(' }')
2811             ->line('#endif')
2812             ->blank
2813             ->comment(' End headers')
2814             ->line(' memcpy(resp_buf + hdr_len, "\\r\\n", 2);')
2815             ->line(' hdr_len += 2;')
2816             ->blank
2817             ->line(' if (hdr_len + len < sizeof(resp_buf)) {')
2818             ->line(' memcpy(resp_buf + hdr_len, body_str, len);')
2819             ->line(' *resp_out = resp_buf;')
2820             ->line(' *resp_len_out = hdr_len + (int)len;')
2821             ->line(' } else {')
2822             ->line(' *resp_out = (char*)RESP_404;')
2823             ->line(' *resp_len_out = RESP_404_LEN;')
2824             ->line(' }');
2825            
2826             # Different closing braces based on middleware presence
2827 4 100       24 if ($analysis->{has_any_middleware}) {
2828             # Middleware: } else if (SvOK(result)) { ... } else { 404 }
2829 1         4 $builder->line(' } else {')
2830             ->line(' *resp_out = (char*)RESP_404;')
2831             ->line(' *resp_len_out = RESP_404_LEN;')
2832             ->line(' }');
2833             } else {
2834             # No middleware: } else if (count == 1) { result = POPs; if (SvOK(result)) { ... } } else { 404 }
2835 3         34 $builder->line(' } else {')
2836             ->line(' *resp_out = (char*)RESP_404;')
2837             ->line(' *resp_len_out = RESP_404_LEN;')
2838             ->line(' }')
2839             ->line(' } else {')
2840             ->line(' *resp_out = (char*)RESP_404;')
2841             ->line(' *resp_len_out = RESP_404_LEN;')
2842             ->line(' }');
2843             }
2844            
2845 4         42 $builder->blank
2846             ->line(' PUTBACK;')
2847             ->line(' FREETMPS;')
2848             ->line(' LEAVE;')
2849             ->line('}');
2850            
2851 4         299 return $builder->code;
2852             }
2853              
2854             # JSON decoder helper - called from C via call_pv
2855             sub _decode_json {
2856 0     0   0 my ($json_str) = @_;
2857 0         0 require Cpanel::JSON::XS;
2858 0         0 return Cpanel::JSON::XS::decode_json($json_str);
2859             }
2860              
2861             # Generate WebSocket handler caller function
2862             sub _gen_websocket_handler_caller {
2863 0     0   0 my ($self) = @_;
2864              
2865 0         0 my $builder = XS::JIT::Builder->new;
2866              
2867 0         0 $builder->comment('WebSocket handler caller - performs handshake and calls Perl handler')
2868             ->line('static void call_websocket_handler(pTHX_ int handler_idx, int fd,')
2869             ->line(' const char* path, int path_len,')
2870             ->line(' const char* ws_key, int ws_key_len,')
2871             ->line(' const char* raw_request, int raw_request_len) {')
2872             ->line(' dSP;')
2873             ->blank
2874             ->if('!g_websocket_handlers || !SvROK(g_websocket_handlers)')
2875             ->line('return;')
2876             ->endif
2877             ->blank
2878             ->line('AV* handlers = (AV*)SvRV(g_websocket_handlers);')
2879             ->line('SV** handler_sv = av_fetch(handlers, handler_idx, 0);')
2880             ->if('!handler_sv || !SvROK(*handler_sv)')
2881             ->line('return;')
2882             ->endif
2883             ->blank
2884             ->comment('Generate WebSocket accept key')
2885             ->line('ENTER;')
2886             ->line('SAVETMPS;')
2887             ->blank
2888             ->comment('Build handshake response via Protocol::WebSocket')
2889             ->line('PUSHMARK(SP);')
2890             ->line('XPUSHs(sv_2mortal(newSVpvs("Hypersonic::Protocol::WebSocket")));')
2891             ->line('XPUSHs(sv_2mortal(newSVpvs("key")));')
2892             ->line('XPUSHs(sv_2mortal(newSVpvn(ws_key, ws_key_len)));')
2893             ->line('PUTBACK;')
2894             ->line('int count = call_method("build_response", G_SCALAR);')
2895             ->line('SPAGAIN;')
2896             ->blank
2897             ->if('count != 1')
2898             ->line('FREETMPS;')
2899             ->line('LEAVE;')
2900             ->line('return;')
2901             ->endif
2902             ->blank
2903             ->line('SV* response_sv = POPs;')
2904             ->line('STRLEN resp_len;')
2905             ->line('const char* response = SvPV(response_sv, resp_len);')
2906             ->blank
2907             ->comment('Send WebSocket handshake response')
2908             ->line('send(fd, response, resp_len, 0);')
2909             ->blank
2910             ->comment('Create Stream object for WebSocket')
2911             ->line('PUSHMARK(SP);')
2912             ->line('XPUSHs(sv_2mortal(newSVpvs("Hypersonic::Stream")));')
2913             ->line('XPUSHs(sv_2mortal(newSVpvs("fd")));')
2914             ->line('XPUSHs(sv_2mortal(newSViv(fd)));')
2915             ->line('PUTBACK;')
2916             ->line('count = call_method("new", G_SCALAR);')
2917             ->line('SPAGAIN;')
2918             ->blank
2919             ->if('count != 1')
2920             ->line('FREETMPS;')
2921             ->line('LEAVE;')
2922             ->line('return;')
2923             ->endif
2924             ->blank
2925             ->line('SV* stream_sv = POPs;')
2926             ->line('SvREFCNT_inc(stream_sv);')
2927             ->blank
2928             ->comment('Create WebSocket object')
2929             ->line('PUSHMARK(SP);')
2930             ->line('XPUSHs(sv_2mortal(newSVpvs("Hypersonic::WebSocket")));')
2931             ->line('XPUSHs(stream_sv);')
2932             ->line('XPUSHs(sv_2mortal(newSVpvs("fd")));')
2933             ->line('XPUSHs(sv_2mortal(newSViv(fd)));')
2934             ->line('PUTBACK;')
2935             ->line('count = call_method("new", G_SCALAR);')
2936             ->line('SPAGAIN;')
2937             ->blank
2938             ->if('count != 1')
2939             ->line('SvREFCNT_dec(stream_sv);')
2940             ->line('FREETMPS;')
2941             ->line('LEAVE;')
2942             ->line('return;')
2943             ->endif
2944             ->blank
2945             ->line('SV* ws_sv = POPs;')
2946             ->line('SvREFCNT_inc(ws_sv);')
2947             ->blank
2948             ->comment('Set WebSocket state to OPEN and store object in registry')
2949             ->if('fd >= 0 && fd < WS_MAX')
2950             ->line('ws_registry[fd].state = WS_STATE_OPEN;')
2951             ->line('ws_registry[fd].ws_object = ws_sv;')
2952             ->line('SvREFCNT_inc(ws_sv);')
2953             ->endif
2954             ->blank
2955             ->comment('Register with Handler for global broadcast support')
2956             ->line('PUSHMARK(SP);')
2957             ->line('XPUSHs(sv_2mortal(newSVpvs("Hypersonic::WebSocket::Handler")));')
2958             ->line('XPUSHs(sv_2mortal(newSViv(fd)));')
2959             ->line('XPUSHs(ws_sv);')
2960             ->line('PUTBACK;')
2961             ->line('call_method("new", G_DISCARD);')
2962             ->blank
2963             ->comment('Call the Perl WebSocket handler with the WebSocket object')
2964             ->line('PUSHMARK(SP);')
2965             ->line('XPUSHs(ws_sv);')
2966             ->line('PUTBACK;')
2967             ->line('call_sv(*handler_sv, G_DISCARD | G_EVAL);')
2968             ->blank
2969             ->if('SvTRUE(ERRSV)')
2970             ->line('warn("WebSocket handler error: %s", SvPV_nolen(ERRSV));')
2971             ->endif
2972             ->blank
2973             ->comment('Emit open event')
2974             ->line('PUSHMARK(SP);')
2975             ->line('XPUSHs(ws_sv);')
2976             ->line('XPUSHs(sv_2mortal(newSVpvs("open")));')
2977             ->line('PUTBACK;')
2978             ->line('call_method("emit", G_DISCARD | G_EVAL);')
2979             ->blank
2980             ->line('SvREFCNT_dec(stream_sv);')
2981             ->line('SvREFCNT_dec(ws_sv);')
2982             ->line('FREETMPS;')
2983             ->line('LEAVE;')
2984             ->line('}')
2985             ->blank;
2986              
2987 0         0 return $builder->code;
2988             }
2989              
2990             # Generate WebSocket data processor for established connections
2991             sub _gen_websocket_data_processor {
2992 0     0   0 my ($self) = @_;
2993              
2994 0         0 my $builder = XS::JIT::Builder->new;
2995              
2996 0         0 $builder->comment('Process incoming WebSocket data on established connection')
2997             ->line('static void process_websocket_data(pTHX_ int fd, const char* data, ssize_t len) {')
2998             ->line(' dSP;')
2999             ->blank
3000             ->comment('Get WebSocket object from registry')
3001             ->if('fd < 0 || fd >= WS_MAX || !ws_registry[fd].ws_object')
3002             ->line('return;')
3003             ->endif
3004             ->blank
3005             ->line('SV* ws_sv = ws_registry[fd].ws_object;')
3006             ->blank
3007             ->comment('Parse WebSocket frame')
3008             ->line('WSFrame frame;')
3009             ->line('int result = ws_decode_frame((const uint8_t*)data, len, &frame);')
3010             ->blank
3011             ->if('result < 0')
3012             ->comment('Incomplete frame or error - wait for more data')
3013             ->line('return;')
3014             ->endif
3015             ->blank
3016             ->comment('Handle different opcodes')
3017             ->if('frame.opcode == WS_OP_TEXT || frame.opcode == WS_OP_BINARY')
3018             ->comment('Data frame - emit message event')
3019             ->line('ENTER;')
3020             ->line('SAVETMPS;')
3021             ->line('PUSHMARK(SP);')
3022             ->line('XPUSHs(ws_sv);')
3023             ->line('XPUSHs(sv_2mortal(newSVpvs("message")));')
3024             ->line('XPUSHs(sv_2mortal(newSVpvn((const char*)frame.payload, frame.payload_length)));')
3025             ->line('PUTBACK;')
3026             ->line('call_method("emit", G_DISCARD | G_EVAL);')
3027             ->line('FREETMPS;')
3028             ->line('LEAVE;')
3029             ->elsif('frame.opcode == WS_OP_CLOSE')
3030             ->comment('Close frame - emit close event and respond')
3031             ->line('int close_code = 1000;')
3032             ->if('frame.payload_length >= 2')
3033             ->line('close_code = (frame.payload[0] << 8) | frame.payload[1];')
3034             ->endif
3035             ->blank
3036             ->comment('Send close response')
3037             ->line('uint8_t close_frame[4];')
3038             ->line('close_frame[0] = 0x88;') # FIN + Close opcode
3039             ->line('close_frame[1] = 2;') # Length 2 (just the code)
3040             ->line('close_frame[2] = (close_code >> 8) & 0xFF;')
3041             ->line('close_frame[3] = close_code & 0xFF;')
3042             ->line('send(fd, close_frame, 4, 0);')
3043             ->blank
3044             ->comment('Emit close event')
3045             ->line('ENTER;')
3046             ->line('SAVETMPS;')
3047             ->line('PUSHMARK(SP);')
3048             ->line('XPUSHs(ws_sv);')
3049             ->line('XPUSHs(sv_2mortal(newSVpvs("close")));')
3050             ->line('XPUSHs(sv_2mortal(newSViv(close_code)));')
3051             ->line('PUTBACK;')
3052             ->line('call_method("emit", G_DISCARD | G_EVAL);')
3053             ->line('FREETMPS;')
3054             ->line('LEAVE;')
3055             ->blank
3056             ->comment('Unregister from Handler')
3057             ->line('ENTER;')
3058             ->line('SAVETMPS;')
3059             ->line('PUSHMARK(SP);')
3060             ->line('XPUSHs(sv_2mortal(newSVpvs("Hypersonic::WebSocket::Handler")));')
3061             ->line('XPUSHs(sv_2mortal(newSViv(fd)));')
3062             ->line('PUTBACK;')
3063             ->line('call_method("close", G_DISCARD);')
3064             ->line('FREETMPS;')
3065             ->line('LEAVE;')
3066             ->blank
3067             ->comment('Mark connection as closed')
3068             ->line('ws_registry[fd].state = WS_STATE_CLOSED;')
3069             ->elsif('frame.opcode == WS_OP_PING')
3070             ->comment('Ping - auto pong')
3071             ->line('uint8_t pong_frame[256];')
3072             ->line('size_t pong_len = ws_encode_pong(pong_frame, sizeof(pong_frame), frame.payload, frame.payload_length);')
3073             ->if('pong_len > 0')
3074             ->line('send(fd, pong_frame, pong_len, 0);')
3075             ->endif
3076             ->endif
3077             ->line('}')
3078             ->blank;
3079              
3080 0         0 return $builder->code;
3081             }
3082              
3083             sub _escape_c_string {
3084 105     105   215 my ($str) = @_;
3085 105         423 $str =~ s/\\/\\\\/g;
3086 105         266 $str =~ s/"/\\"/g;
3087 105         620 $str =~ s/\n/\\n/g;
3088 105         519 $str =~ s/\r/\\r/g;
3089 105         191 $str =~ s/\t/\\t/g;
3090 105         280 return $str;
3091             }
3092              
3093             # Deparse a handler coderef to analyze what request features it uses
3094             # Returns deparsed code as string, or undef on failure
3095             sub _deparse_handler {
3096 11     11   27 my ($coderef) = @_;
3097 11 50       59 return undef unless ref($coderef) eq 'CODE';
3098            
3099 11         21 my $code = eval {
3100 11 100       92 require B::Deparse unless $DEPARSER;
3101 11   66     728 $DEPARSER //= B::Deparse->new('-q'); # -q = don't quote simple strings
3102 11         20686 $DEPARSER->coderef2text($coderef);
3103             };
3104 11 50       118 return $@ ? undef : $code;
3105             }
3106              
3107             # Find common prefix of multiple paths
3108             sub _find_common_prefix {
3109 11     11   56 my @paths = @_;
3110 11 50       40 return '' unless @paths;
3111 11 50       31 return $paths[0] if @paths == 1;
3112              
3113 11         32 my $prefix = $paths[0];
3114 11         101 for my $path (@paths[1..$#paths]) {
3115 26         86 while (index($path, $prefix) != 0) {
3116 56         120 $prefix = substr($prefix, 0, -1);
3117 56 50       134 return '' if $prefix eq '';
3118             }
3119             }
3120             # Don't include trailing non-slash char as prefix
3121             # e.g., /api/hello and /api/health -> /api/ not /api/he
3122 11 100 66     265 if ($prefix !~ m{/$} && $prefix =~ m{^(.*/)[^/]+$}) {
3123 3         95 $prefix = $1;
3124             }
3125 11         41 return $prefix;
3126             }
3127              
3128             # HTTP status code to text mapping
3129             my %STATUS_TEXT = (
3130             200 => 'OK',
3131             201 => 'Created',
3132             202 => 'Accepted',
3133             204 => 'No Content',
3134             301 => 'Moved Permanently',
3135             302 => 'Found',
3136             303 => 'See Other',
3137             304 => 'Not Modified',
3138             307 => 'Temporary Redirect',
3139             308 => 'Permanent Redirect',
3140             400 => 'Bad Request',
3141             401 => 'Unauthorized',
3142             403 => 'Forbidden',
3143             404 => 'Not Found',
3144             405 => 'Method Not Allowed',
3145             408 => 'Request Timeout',
3146             409 => 'Conflict',
3147             410 => 'Gone',
3148             413 => 'Payload Too Large',
3149             415 => 'Unsupported Media Type',
3150             422 => 'Unprocessable Entity',
3151             429 => 'Too Many Requests',
3152             500 => 'Internal Server Error',
3153             501 => 'Not Implemented',
3154             502 => 'Bad Gateway',
3155             503 => 'Service Unavailable',
3156             504 => 'Gateway Timeout',
3157             );
3158              
3159             sub _status_text {
3160 0     0   0 my ($code) = @_;
3161 0   0     0 return $STATUS_TEXT{$code} // 'Unknown';
3162             }
3163              
3164             # ============================================================
3165             # STATIC FILE SERVING - JIT compiled for maximum performance
3166             # Files are read at compile time and baked into C string constants
3167             # ============================================================
3168              
3169             sub _compile_static_files {
3170 0     0   0 my ($self, $static_dirs) = @_;
3171            
3172 0         0 require File::Find;
3173 0         0 require Digest::MD5;
3174            
3175 0         0 my @static_files;
3176            
3177 0         0 for my $config (@$static_dirs) {
3178 0         0 my $prefix = $config->{prefix};
3179 0         0 my $dir = $config->{directory};
3180 0         0 my $max_age = $config->{max_age};
3181 0         0 my $gen_etag = $config->{etag};
3182            
3183             # Recursively find all files
3184             File::Find::find({
3185             no_chdir => 1,
3186             wanted => sub {
3187 0 0   0   0 return unless -f $_;
3188 0         0 my $file_path = $_;
3189 0         0 my $rel_path = $file_path;
3190 0         0 $rel_path =~ s{^\Q$dir\E/?}{};
3191            
3192             # URL path for this file
3193 0         0 my $url_path = "$prefix/$rel_path";
3194 0         0 $url_path =~ s{//+}{/}g;
3195            
3196             # Read file content
3197 0 0       0 open my $fh, '<:raw', $file_path or return;
3198 0         0 local $/;
3199 0         0 my $content = <$fh>;
3200 0         0 close $fh;
3201            
3202             # Get MIME type
3203 0         0 my $mime = _get_mime_type($file_path);
3204            
3205             # Generate ETag (MD5 of content)
3206 0         0 my $etag = '';
3207 0 0       0 if ($gen_etag) {
3208 0         0 $etag = Digest::MD5::md5_hex($content);
3209             }
3210            
3211             # Store file info
3212 0         0 push @static_files, {
3213             url_path => $url_path,
3214             content => $content,
3215             mime => $mime,
3216             etag => $etag,
3217             max_age => $max_age,
3218             length => length($content),
3219             };
3220             },
3221 0         0 }, $dir);
3222             }
3223            
3224             # Store for code generation
3225 0         0 $self->{_static_files} = \@static_files;
3226            
3227             # Create static routes - these are essentially pre-computed responses
3228 0         0 for my $file (@static_files) {
3229 0         0 my $url_path = $file->{url_path};
3230 0         0 my $content = $file->{content};
3231 0         0 my $mime = $file->{mime};
3232 0         0 my $etag = $file->{etag};
3233 0         0 my $max_age = $file->{max_age};
3234 0         0 my $len = $file->{length};
3235            
3236             # Build complete HTTP response at compile time
3237 0         0 my $response = "HTTP/1.1 200 OK\r\n"
3238             . "Content-Type: $mime\r\n"
3239             . "Content-Length: $len\r\n"
3240             . "Connection: keep-alive\r\n";
3241 0 0       0 $response .= "Cache-Control: public, max-age=$max_age\r\n" if $max_age;
3242 0 0       0 $response .= "ETag: \"$etag\"\r\n" if $etag;
3243            
3244             # Add security headers
3245 0 0       0 if ($self->{enable_security_headers}) {
3246 0         0 $response .= $self->_get_security_headers_string();
3247             }
3248            
3249 0         0 $response .= "\r\n" . $content;
3250            
3251             # Store as static route
3252 0         0 push @{$self->{routes}}, {
3253             method => 'GET',
3254             path => $url_path,
3255 0     0   0 handler => sub { $content }, # Dummy handler for static
3256 0         0 dynamic => 0,
3257             params => [],
3258             segments => [split('/', $url_path)],
3259             features => {},
3260             before => [],
3261             after => [],
3262             # Mark as static file with pre-built response
3263             _static_response => $response,
3264             _static_file => 1,
3265             };
3266             }
3267             }
3268              
3269             # Generate security headers string for HTTP responses
3270             # Pre-computed at compile time - zero runtime overhead
3271             sub _get_security_headers_string {
3272 59     59   228 my ($self) = @_;
3273 59         324 my $headers = '';
3274            
3275 59         113 for my $name (sort keys %{$self->{security_headers}}) {
  59         954  
3276 413         933 my $value = $self->{security_headers}{$name};
3277 413 100 66     1227 next unless defined $value && length($value);
3278 236         480 $headers .= "$name: $value\r\n";
3279             }
3280            
3281 59         199 return $headers;
3282             }
3283              
3284             # Generate security headers as C string constant for dynamic routes
3285             sub _gen_security_headers_c_constant {
3286 4     4   11 my ($self) = @_;
3287 4 50       16 return '' unless $self->{enable_security_headers};
3288            
3289 4         12 my $headers = $self->_get_security_headers_string();
3290 4 50       25 return '' unless length($headers);
3291            
3292 4         10 my $escaped = _escape_c_string($headers);
3293 4         50 return "static const char SECURITY_HEADERS[] = \"$escaped\";\n"
3294             . "static const int SECURITY_HEADERS_LEN = " . length($headers) . ";\n";
3295             }
3296              
3297             sub dispatch {
3298 31     31 1 1022322 my ($self, $req) = @_;
3299 31 100       103 die "Must call compile() first" unless $self->{compiled};
3300 30         212 return $self->{dispatch_fn}->($req);
3301             }
3302              
3303             sub run {
3304 7     7 1 211 my ($self, %opts) = @_;
3305              
3306 7 100       50 die "Must call compile() before run()" unless $self->{compiled};
3307              
3308 6   33     162 my $host = $opts{host} // $self->{host};
3309 6   33     32 my $port = $opts{port} // $self->{port};
3310 6   50     22 my $workers = $opts{workers} // 1;
3311              
3312             # Windows lacks fork(); the multi-worker model assumes SO_REUSEPORT
3313             # + fork()-per-worker so the kernel can balance connections. With
3314             # neither primitive available the only safe behavior is single
3315             # process. Warn loudly so users know they're not getting the
3316             # workers they asked for.
3317 6 50 33     50 if ($^O eq 'MSWin32' && $workers > 1) {
3318 0         0 warn "Hypersonic: Windows lacks fork(); forcing workers=1.\n";
3319 0         0 $workers = 1;
3320             }
3321              
3322             # Protocol mode indication
3323 6 50       115 my $mode = $self->{http2} ? "HTTP/2" : ($self->{tls} ? "HTTPS/TLS" : "HTTP");
    50          
3324 6         300 print "Hypersonic listening on $host:$port ($mode, pure C event loop, $workers workers)\n";
3325              
3326             # Fork workers if requested
3327             # Each worker creates its OWN listening socket with SO_REUSEPORT
3328             # This avoids thundering herd and lets kernel distribute connections
3329 6 50       33 if ($workers > 1) {
3330 0         0 for my $i (1 .. $workers - 1) {
3331 0         0 my $pid = fork();
3332 0 0       0 if (!defined $pid) {
3333 0         0 die "Fork failed: $!";
3334             }
3335 0 0       0 if ($pid == 0) {
3336             # Child - create own listen socket and run event loop
3337 0         0 my $listen_fd = Hypersonic::Socket::create_listen_socket($port);
3338 0 0       0 die "Worker $i: Failed to create listen socket" if $listen_fd < 0;
3339 0         0 $self->{run_loop_fn}->($listen_fd, $self);
3340 0         0 exit(0);
3341             }
3342             }
3343             }
3344              
3345             # Parent (or single worker) - create listen socket and run event loop
3346 6         468 my $listen_fd = Hypersonic::Socket::create_listen_socket($port);
3347 6 50       36 die "Failed to create listen socket on port $port" if $listen_fd < 0;
3348 6         4109715 $self->{run_loop_fn}->($listen_fd, $self);
3349             }
3350              
3351              
3352             1;
3353              
3354             __END__