File Coverage

blib/lib/Hypersonic.pm
Criterion Covered Total %
statement 590 949 62.1
branch 213 410 51.9
condition 95 262 36.2
subroutine 37 58 63.7
pod 18 21 85.7
total 953 1700 56.0


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