File Coverage

blib/lib/Hypersonic/UA.pm
Criterion Covered Total %
statement 150 150 100.0
branch 12 14 85.7
condition 5 6 83.3
subroutine 40 40 100.0
pod 0 25 0.0
total 207 235 88.0


line stmt bran cond sub pod time code
1             package Hypersonic::UA;
2              
3 13     13   7491284 use strict;
  13         40  
  13         668  
4 13     13   76 use warnings;
  13         39  
  13         1236  
5 13     13   401 use 5.010;
  13         57  
6 13     13   79 use Carp;
  13         38  
  13         2414  
7              
8             our $VERSION = '0.15';
9              
10 13     13   3990 use XS::JIT::Builder;
  13         9795  
  13         924  
11              
12 13     13   81 use constant MAX_CONNECTIONS => 65536;
  13         24  
  13         1625  
13 13     13   76 use constant UA_MAX_INSTANCES => 256;
  13         22  
  13         990  
14              
15             # Object slots (array-based for O(1) access)
16             use constant {
17 13         79112 SLOT_ID => 0,
18             SLOT_TIMEOUT => 1,
19             SLOT_CONNECT_TIMEOUT => 2,
20             SLOT_HEADERS => 3,
21             SLOT_BASE_URL => 4,
22             SLOT_MAX_REDIRECTS => 5,
23             SLOT_KEEP_ALIVE => 6,
24 13     13   109 };
  13         55  
25              
26             # JIT compilation state
27             our $COMPILED = 0;
28             our %FEATURES; # Track which features were compiled
29             my $MODULE_NAME;
30              
31             #############################################################################
32             # Compilation
33             #############################################################################
34              
35             sub compile {
36             my ($class, %opts) = @_;
37              
38             return 1 if $COMPILED;
39              
40             require XS::JIT;
41              
42             my $cache_dir = $opts{cache_dir} // '_hypersonic_cache/ua';
43             $MODULE_NAME = 'Hypersonic::UA::XS_' . $$;
44              
45             # Feature analysis - default to minimal (blocking-only)
46             my %analysis = (
47             needs_async => $opts{async} || $opts{parallel} || $opts{full} || 0,
48             needs_parallel => $opts{parallel} || $opts{full} || 0,
49             needs_tls => $opts{tls} || $opts{full} || 0,
50             needs_http2 => $opts{http2} || $opts{full} || 0,
51             needs_compression => $opts{compression} || $opts{full} || 0,
52             needs_cookie_jar => $opts{cookie_jar} || $opts{full} || 0,
53             needs_redirects => $opts{redirects} || $opts{full} || 0,
54             );
55              
56             # Store for runtime checks
57             %FEATURES = %analysis;
58              
59             my $builder = XS::JIT::Builder->new;
60              
61             # Generate core UA code (always)
62             $class->generate_c_code($builder, \%opts, \%analysis);
63              
64             # Collect core functions
65             my %functions = %{ $class->get_xs_functions(\%analysis) };
66              
67             # Include Async module (conditional)
68             if ($analysis{needs_async}) {
69             require Hypersonic::UA::Async;
70             Hypersonic::UA::Async->generate_c_code($builder, \%opts);
71             %functions = (%functions, %{ Hypersonic::UA::Async->get_xs_functions });
72             }
73              
74             # Include Future module for parallel/race (conditional)
75             if ($analysis{needs_parallel}) {
76             require Hypersonic::Future;
77             Hypersonic::Future->compile(cache_dir => $cache_dir)
78             unless $Hypersonic::Future::COMPILED;
79             }
80              
81             my $code = $builder->code;
82              
83             XS::JIT->compile(
84             code => $code,
85             name => $MODULE_NAME,
86             cache_dir => $cache_dir,
87             functions => \%functions,
88             );
89              
90             $COMPILED = 1;
91             return 1;
92             }
93              
94             sub generate_c_code {
95 7     7 0 14395 my ($class, $builder, $opts, $analysis) = @_;
96              
97 7   50     53 my $max = $opts->{max_connections} // MAX_CONNECTIONS;
98              
99             # Add required includes for networking (needed by UA registry and HTTP methods)
100 7         284 $builder->line('#include ')
101             ->line('#include ')
102             ->line('#include ')
103             ->line('#include ')
104             ->line('#include ')
105             ->line('#include ')
106             ->line('#include ')
107             ->line('#include ')
108             ->line('#include ')
109             ->line('#include ')
110             ->line('#include ')
111             ->blank
112             ->comment('Portable strcasestr implementation (GNU extension not available everywhere)')
113             ->line('#ifndef HAVE_STRCASESTR')
114             ->line('static char *hs_strcasestr(const char *haystack, const char *needle) {')
115             ->line(' size_t needle_len;')
116             ->line(' if (!needle || !*needle) return (char *)haystack;')
117             ->line(' needle_len = strlen(needle);')
118             ->line(' while (*haystack) {')
119             ->line(' if (strncasecmp(haystack, needle, needle_len) == 0)')
120             ->line(' return (char *)haystack;')
121             ->line(' haystack++;')
122             ->line(' }')
123             ->line(' return NULL;')
124             ->line('}')
125             ->line('#define strcasestr hs_strcasestr')
126             ->line('#endif')
127             ->blank;
128              
129             # UA registry (always)
130 7         46 $class->gen_ua_registry($builder, $max);
131              
132             # Core URL/request building (always)
133 7         113 $class->gen_xs_parse_url($builder);
134 7         46 $class->gen_xs_build_request($builder);
135              
136             # Constructor/destructor (always)
137 7         29 $class->gen_xs_new($builder);
138 7         27 $class->gen_xs_destroy($builder);
139              
140             # Blocking/callback methods (always - core functionality)
141 7         39 $class->gen_xs_get($builder);
142 7         64 $class->gen_xs_post($builder);
143 7         39 $class->gen_xs_put($builder);
144 7         41 $class->gen_xs_patch($builder);
145 7         24 $class->gen_xs_delete($builder);
146 7         43 $class->gen_xs_head($builder);
147 7         27 $class->gen_xs_options($builder);
148 7         24 $class->gen_xs_request($builder);
149              
150             # Async/Future methods (conditional - requires async => 1)
151 7 100       81 if ($analysis->{needs_async}) {
152 4         14 $builder->comment('JIT: Async methods enabled (async => 1)');
153 4         20 $class->gen_xs_get_async($builder);
154 4         13 $class->gen_xs_post_async($builder);
155 4         38 $class->gen_xs_put_async($builder);
156 4         15 $class->gen_xs_delete_async($builder);
157 4         18 $class->gen_xs_request_async($builder);
158              
159             # Run/poll (requires async) - tick is generated in Async.pm after headers
160 4         34 $class->gen_xs_run($builder);
161 4         27 $class->gen_xs_run_one($builder);
162             # gen_xs_tick moved to Async.pm to be after kqueue headers
163 4         16 $class->gen_xs_pending($builder);
164             } else {
165 3         10 $builder->comment('JIT: Async methods SKIPPED (compile with async => 1 to enable)');
166             }
167              
168             # Parallel/race helpers (conditional - requires parallel => 1)
169 7 100       29 if ($analysis->{needs_parallel}) {
170 1         3 $builder->comment('JIT: Parallel helpers enabled (parallel => 1)');
171 1         4 $class->gen_xs_parallel($builder);
172 1         4 $class->gen_xs_race($builder);
173             } else {
174 6         40 $builder->comment('JIT: Parallel helpers SKIPPED (compile with parallel => 1 to enable)');
175             }
176             }
177              
178             sub get_xs_functions {
179 7     7 0 10251 my ($class, $analysis) = @_;
180 7   100     20 $analysis //= {};
181              
182             # Core functions (always registered)
183 7         102 my %functions = (
184             # Constructor/destructor
185             'Hypersonic::UA::new' => { source => 'xs_ua_new', is_xs_native => 1 },
186             'Hypersonic::UA::DESTROY' => { source => 'xs_ua_destroy', is_xs_native => 1 },
187              
188             # URL/request utilities
189             'Hypersonic::UA::parse_url' => { source => 'xs_ua_parse_url', is_xs_native => 1 },
190             'Hypersonic::UA::build_request' => { source => 'xs_ua_build_request', is_xs_native => 1 },
191              
192             # Blocking (sync) HTTP methods - always available
193             'Hypersonic::UA::get' => { source => 'xs_ua_get', is_xs_native => 1 },
194             'Hypersonic::UA::post' => { source => 'xs_ua_post', is_xs_native => 1 },
195             'Hypersonic::UA::put' => { source => 'xs_ua_put', is_xs_native => 1 },
196             'Hypersonic::UA::patch' => { source => 'xs_ua_patch', is_xs_native => 1 },
197             'Hypersonic::UA::delete' => { source => 'xs_ua_delete', is_xs_native => 1 },
198             'Hypersonic::UA::head' => { source => 'xs_ua_head', is_xs_native => 1 },
199             'Hypersonic::UA::options' => { source => 'xs_ua_options', is_xs_native => 1 },
200             'Hypersonic::UA::request' => { source => 'xs_ua_request', is_xs_native => 1 },
201             );
202              
203             # Async functions (conditional - requires async => 1)
204 7 100       20 if ($analysis->{needs_async}) {
205 3         64 %functions = (%functions,
206             # Future-based async methods
207             'Hypersonic::UA::get_async' => { source => 'xs_ua_get_async', is_xs_native => 1 },
208             'Hypersonic::UA::post_async' => { source => 'xs_ua_post_async', is_xs_native => 1 },
209             'Hypersonic::UA::put_async' => { source => 'xs_ua_put_async', is_xs_native => 1 },
210             'Hypersonic::UA::delete_async' => { source => 'xs_ua_delete_async', is_xs_native => 1 },
211             'Hypersonic::UA::request_async' => { source => 'xs_ua_request_async', is_xs_native => 1 },
212              
213             # Run/poll methods
214             'Hypersonic::UA::run' => { source => 'xs_ua_run', is_xs_native => 1 },
215             'Hypersonic::UA::run_one' => { source => 'xs_ua_run_one', is_xs_native => 1 },
216             'Hypersonic::UA::tick' => { source => 'xs_ua_tick', is_xs_native => 1 },
217             'Hypersonic::UA::pending' => { source => 'xs_ua_pending', is_xs_native => 1 },
218             );
219             }
220              
221             # Parallel/race helpers (conditional - requires parallel => 1)
222 7 100       20 if ($analysis->{needs_parallel}) {
223 1         20 %functions = (%functions,
224             'Hypersonic::UA::parallel' => { source => 'xs_ua_parallel', is_xs_native => 1 },
225             'Hypersonic::UA::race' => { source => 'xs_ua_race', is_xs_native => 1 },
226             );
227             }
228              
229 7         32 return \%functions;
230             }
231              
232             sub gen_ua_registry {
233 24     24 0 5827320 my ($class, $builder, $max) = @_;
234 24   100     177 $max //= MAX_CONNECTIONS;
235              
236 24         2948 $builder->line("#define UA_MAX_CONNECTIONS $max")
237             ->line("#define UA_MAX_INSTANCES 256")
238             ->line("#define DNS_CACHE_SIZE 64")
239             ->line("#define CONN_POOL_SIZE 32")
240             ->blank
241             ->comment('Connection pool entry for keep-alive')
242             ->line('typedef struct {')
243             ->line(' int fd;')
244             ->line(' char host[256];')
245             ->line(' int port;')
246             ->line(' time_t expires;')
247             ->line('} PooledConn;')
248             ->blank
249             ->line("static PooledConn conn_pool[CONN_POOL_SIZE];")
250             ->blank
251             ->comment('Get a pooled connection if available')
252             ->line('static int pool_get(const char *host, int port) {')
253             ->line(' int i;')
254             ->line(' time_t now = time(NULL);')
255             ->line(' for (i = 0; i < CONN_POOL_SIZE; i++) {')
256             ->line(' if (conn_pool[i].fd > 0 && conn_pool[i].port == port &&')
257             ->line(' strcmp(conn_pool[i].host, host) == 0 && conn_pool[i].expires > now) {')
258             ->line(' int fd = conn_pool[i].fd;')
259             ->line(' conn_pool[i].fd = 0;')
260             ->line(' return fd;')
261             ->line(' }')
262             ->line(' }')
263             ->line(' return -1;')
264             ->line('}')
265             ->blank
266             ->comment('Return connection to pool (15 second keep-alive)')
267             ->line('static void pool_put(int fd, const char *host, int port) {')
268             ->line(' int i;')
269             ->line(' time_t now = time(NULL);')
270             ->line(' /* Find empty slot or expired entry */')
271             ->line(' for (i = 0; i < CONN_POOL_SIZE; i++) {')
272             ->line(' if (conn_pool[i].fd <= 0 || conn_pool[i].expires <= now) {')
273             ->line(' if (conn_pool[i].fd > 0) close(conn_pool[i].fd);')
274             ->line(' conn_pool[i].fd = fd;')
275             ->line(' strncpy(conn_pool[i].host, host, 255);')
276             ->line(' conn_pool[i].host[255] = 0;')
277             ->line(' conn_pool[i].port = port;')
278             ->line(' conn_pool[i].expires = now + 15;')
279             ->line(' return;')
280             ->line(' }')
281             ->line(' }')
282             ->line(' /* Pool full, just close */')
283             ->line(' close(fd);')
284             ->line('}')
285             ->blank
286             ->line('typedef struct {')
287             ->line(' int fd;')
288             ->line(' int tls;')
289             ->line(' int state;')
290             ->line(' char* host;')
291             ->line(' int port;')
292             ->line(' int timeout_ms;')
293             ->line(' int connect_timeout_ms;')
294             ->line('} UAConnection;')
295             ->blank
296             ->line("static UAConnection g_ua_connections[UA_MAX_CONNECTIONS];")
297             ->blank
298             ->comment('DNS cache entry')
299             ->line('typedef struct {')
300             ->line(' char host[256];')
301             ->line(' struct in_addr addr;')
302             ->line(' time_t expires;')
303             ->line('} DNSCacheEntry;')
304             ->blank
305             ->line("static DNSCacheEntry dns_cache[DNS_CACHE_SIZE];")
306             ->line("static int dns_cache_next = 0;")
307             ->blank
308             ->comment('Lookup DNS with caching (60 second TTL)')
309             ->line('static int dns_lookup_cached(const char *host, struct in_addr *addr_out) {')
310             ->line(' int i;')
311             ->line(' int slot;')
312             ->line(' struct hostent *he;')
313             ->line(' time_t now = time(NULL);')
314             ->line(' /* Check cache */')
315             ->line(' for (i = 0; i < DNS_CACHE_SIZE; i++) {')
316             ->line(' if (dns_cache[i].host[0] && strcmp(dns_cache[i].host, host) == 0 && dns_cache[i].expires > now) {')
317             ->line(' *addr_out = dns_cache[i].addr;')
318             ->line(' return 1;')
319             ->line(' }')
320             ->line(' }')
321             ->line(' /* Cache miss - do lookup */')
322             ->line(' he = gethostbyname(host);')
323             ->line(' if (!he) return 0;')
324             ->line(' memcpy(addr_out, he->h_addr_list[0], sizeof(struct in_addr));')
325             ->line(' /* Store in cache */')
326             ->line(' slot = dns_cache_next++ % DNS_CACHE_SIZE;')
327             ->line(' strncpy(dns_cache[slot].host, host, 255);')
328             ->line(' dns_cache[slot].host[255] = 0;')
329             ->line(' dns_cache[slot].addr = *addr_out;')
330             ->line(' dns_cache[slot].expires = now + 60;')
331             ->line(' return 1;')
332             ->line('}')
333             ->blank
334             ->line('typedef struct {')
335             ->line(' int in_use;')
336             ->line(' int timeout_ms;')
337             ->line(' int connect_timeout_ms;')
338             ->line(' int max_redirects;')
339             ->line(' int keep_alive;')
340             ->line(' SV *default_headers;')
341             ->line(' char *base_url;')
342             ->line('} UAContext;')
343             ->blank
344             ->line("static UAContext ua_registry[UA_MAX_INSTANCES];")
345             ->blank
346             ->line('static int ua_alloc_slot(void) {')
347             ->line(' int i;')
348             ->line(' for (i = 0; i < UA_MAX_INSTANCES; i++) {')
349             ->line(' if (!ua_registry[i].in_use) {')
350             ->line(' memset(&ua_registry[i], 0, sizeof(UAContext));')
351             ->line(' ua_registry[i].in_use = 1;')
352             ->line(' return i;')
353             ->line(' }')
354             ->line(' }')
355             ->line(' return -1;')
356             ->line('}')
357             ->blank
358             ->line('static void ua_free_slot(int slot) {')
359             ->line(' if (slot >= 0 && slot < UA_MAX_INSTANCES) {')
360             ->line(' ua_registry[slot].in_use = 0;')
361             ->line(' }')
362             ->line('}')
363             ->blank;
364             }
365              
366             sub gen_xs_new {
367 7     7 0 16 my ($class, $builder) = @_;
368              
369 7         689 $builder->comment('Constructor: new() or new({ timeout => 30000, ... })')
370             ->xs_function('xs_ua_new')
371             ->xs_preamble
372             ->line('HV *opts;')
373             ->line('SV **val;')
374             ->line('STRLEN len;')
375             ->line('const char *url;')
376             ->line('int slot;')
377             ->line('UAContext *ctx;')
378             ->line('AV *self;')
379             ->line('SV *self_ref;')
380             ->blank
381             ->line('if (items < 1) croak("Usage: Hypersonic::UA->new([$opts])");')
382             ->blank
383             ->line('slot = ua_alloc_slot();')
384             ->line('if (slot < 0) croak("Too many UA instances");')
385             ->blank
386             ->line('ctx = &ua_registry[slot];')
387             ->blank
388             ->comment('Defaults')
389             ->line('ctx->timeout_ms = 30000;')
390             ->line('ctx->connect_timeout_ms = 5000;')
391             ->line('ctx->max_redirects = 5;')
392             ->line('ctx->keep_alive = 1;')
393             ->line('ctx->default_headers = NULL;')
394             ->line('ctx->base_url = NULL;')
395             ->blank
396             ->comment('Parse options hash if provided')
397             ->if('items >= 2 && SvROK(ST(1)) && SvTYPE(SvRV(ST(1))) == SVt_PVHV')
398             ->line('opts = (HV *)SvRV(ST(1));')
399             ->blank
400             ->line('if ((val = hv_fetchs(opts, "timeout", 0)) && SvOK(*val)) ctx->timeout_ms = SvIV(*val);')
401             ->line('if ((val = hv_fetchs(opts, "connect_timeout", 0)) && SvOK(*val)) ctx->connect_timeout_ms = SvIV(*val);')
402             ->line('if ((val = hv_fetchs(opts, "max_redirects", 0)) && SvOK(*val)) ctx->max_redirects = SvIV(*val);')
403             ->line('if ((val = hv_fetchs(opts, "keep_alive", 0)) && SvOK(*val)) ctx->keep_alive = SvTRUE(*val) ? 1 : 0;')
404             ->line('if ((val = hv_fetchs(opts, "headers", 0)) && SvROK(*val)) ctx->default_headers = SvREFCNT_inc(*val);')
405             ->if('(val = hv_fetchs(opts, "base_url", 0)) && SvOK(*val)')
406             ->line('url = SvPV(*val, len);')
407             ->line('ctx->base_url = (char *)malloc(len + 1);')
408             ->line('memcpy(ctx->base_url, url, len + 1);')
409             ->endif
410             ->endif
411             ->blank
412             ->comment('Build array-based object')
413             ->line('self = newAV();')
414             ->line('av_extend(self, 6);')
415             ->line('av_store(self, 0, newSViv(slot));')
416             ->line('av_store(self, 1, newSViv(ctx->timeout_ms));')
417             ->line('av_store(self, 2, newSViv(ctx->connect_timeout_ms));')
418             ->line('av_store(self, 3, ctx->default_headers ? SvREFCNT_inc(ctx->default_headers) : newRV_noinc((SV *)newHV()));')
419             ->line('av_store(self, 4, ctx->base_url ? newSVpv(ctx->base_url, 0) : &PL_sv_undef);')
420             ->line('av_store(self, 5, newSViv(ctx->max_redirects));')
421             ->line('av_store(self, 6, newSViv(ctx->keep_alive));')
422             ->blank
423             ->line('self_ref = newRV_noinc((SV *)self);')
424             ->line('sv_bless(self_ref, gv_stashpv("Hypersonic::UA", GV_ADD));')
425             ->blank
426             ->line('ST(0) = sv_2mortal(self_ref);')
427             ->xs_return('1')
428             ->xs_end
429             ->blank;
430             }
431              
432             sub gen_xs_destroy {
433 7     7 0 15 my ($class, $builder) = @_;
434              
435 7         204 $builder->comment('Destructor')
436             ->xs_function('xs_ua_destroy')
437             ->xs_preamble
438             ->line('if (items != 1 || !SvROK(ST(0))) XSRETURN_EMPTY;')
439             ->blank
440             ->line('AV *self = (AV *)SvRV(ST(0));')
441             ->line('SV **slot_sv = av_fetch(self, 0, 0);')
442             ->line('if (!slot_sv || !SvOK(*slot_sv)) XSRETURN_EMPTY;')
443             ->blank
444             ->line('int slot = SvIV(*slot_sv);')
445             ->line('if (slot < 0 || slot >= UA_MAX_INSTANCES) XSRETURN_EMPTY;')
446             ->blank
447             ->line('UAContext *ctx = &ua_registry[slot];')
448             ->blank
449             ->comment('Free UA resources')
450             ->if('ctx->default_headers')
451             ->line('SvREFCNT_dec(ctx->default_headers);')
452             ->line('ctx->default_headers = NULL;')
453             ->endif
454             ->if('ctx->base_url')
455             ->line('free(ctx->base_url);')
456             ->line('ctx->base_url = NULL;')
457             ->endif
458             ->blank
459             ->line('ua_free_slot(slot);')
460             ->xs_return('0')
461             ->xs_end
462             ->blank;
463             }
464              
465             sub gen_xs_parse_url {
466 7     7 0 18 my ($class, $builder) = @_;
467              
468 7         654 $builder->comment('Parse URL into components: (scheme, host, port, path, query)')
469             ->xs_function('xs_ua_parse_url')
470             ->xs_preamble
471             ->line('if (items != 1) croak("Usage: parse_url(url)");')
472             ->blank
473             ->line('STRLEN url_len;')
474             ->line('const char* url = SvPV(ST(0), url_len);')
475             ->blank
476             ->comment('Parse scheme')
477             ->line('const char* p = url;')
478             ->line('const char* scheme_end = strstr(p, "://");')
479             ->if('!scheme_end')
480             ->line('croak("Invalid URL: missing scheme");')
481             ->endif
482             ->blank
483             ->line('int is_https = (scheme_end - p == 5 && memcmp(p, "https", 5) == 0);')
484             ->line('int is_http = (scheme_end - p == 4 && memcmp(p, "http", 4) == 0);')
485             ->if('!is_https && !is_http')
486             ->line('croak("Invalid URL: unsupported scheme");')
487             ->endif
488             ->blank
489             ->line('p = scheme_end + 3;')
490             ->blank
491             ->comment('Parse host and port')
492             ->line('const char* host_start = p;')
493             ->line('const char* host_end = p;')
494             ->line('int port = is_https ? 443 : 80;')
495             ->blank
496             ->line('while (*host_end && *host_end != \':\' && *host_end != \'/\' && *host_end != \'?\') host_end++;')
497             ->blank
498             ->if('*host_end == \':\'')
499             ->line('port = 0;')
500             ->line('const char* port_start = host_end + 1;')
501             ->line('while (*port_start >= \'0\' && *port_start <= \'9\') {')
502             ->line(' port = port * 10 + (*port_start - \'0\');')
503             ->line(' port_start++;')
504             ->line('}')
505             ->line('p = port_start;')
506             ->else
507             ->line('p = host_end;')
508             ->endif
509             ->blank
510             ->comment('Parse path')
511             ->line('const char* path_start = (*p == \'/\') ? p : "/";')
512             ->line('const char* path_end = path_start;')
513             ->line('while (*path_end && *path_end != \'?\') path_end++;')
514             ->blank
515             ->comment('Parse query')
516             ->line('const char* query = (*path_end == \'?\') ? path_end + 1 : "";')
517             ->blank
518             ->comment('Build result array: [scheme, host, port, path, query]')
519             ->line('AV* result = newAV();')
520             ->line('av_push(result, newSVpv(is_https ? "https" : "http", 0));')
521             ->line('av_push(result, newSVpvn(host_start, host_end - host_start));')
522             ->line('av_push(result, newSViv(port));')
523             ->line('av_push(result, newSVpvn(path_start, path_end - path_start));')
524             ->line('av_push(result, newSVpv(query, 0));')
525             ->blank
526             ->line('ST(0) = sv_2mortal(newRV_noinc((SV*)result));')
527             ->xs_return('1')
528             ->xs_end
529             ->blank;
530             }
531              
532             sub gen_xs_build_request {
533 7     7 0 15 my ($class, $builder) = @_;
534              
535 7         700 $builder->comment('Build HTTP/1.1 request string')
536             ->xs_function('xs_ua_build_request')
537             ->xs_preamble
538             ->line('if (items < 4) croak("Usage: build_request(method, path, host, headers_hv, [body])");')
539             ->blank
540             ->line('STRLEN method_len, path_len, host_len;')
541             ->line('const char* method = SvPV(ST(0), method_len);')
542             ->line('const char* path = SvPV(ST(1), path_len);')
543             ->line('const char* host = SvPV(ST(2), host_len);')
544             ->line('HV* headers = (HV*)SvRV(ST(3));')
545             ->line('SV* body_sv = (items > 4) ? ST(4) : NULL;')
546             ->blank
547             ->comment('Calculate request size')
548             ->line('size_t request_size = method_len + 1 + path_len + 12;')
549             ->line('request_size += 6 + host_len + 2;')
550             ->blank
551             ->comment('Add header sizes')
552             ->line('hv_iterinit(headers);')
553             ->line('HE* entry;')
554             ->line('while ((entry = hv_iternext(headers)) != NULL) {')
555             ->line(' SV* key_sv = hv_iterkeysv(entry);')
556             ->line(' SV* val_sv = hv_iterval(headers, entry);')
557             ->line(' STRLEN key_len, val_len;')
558             ->line(' SvPV(key_sv, key_len);')
559             ->line(' SvPV(val_sv, val_len);')
560             ->line(' request_size += key_len + 2 + val_len + 2;')
561             ->line('}')
562             ->blank
563             ->line('STRLEN body_len = 0;')
564             ->if('body_sv && SvOK(body_sv)')
565             ->line('SvPV(body_sv, body_len);')
566             ->line('request_size += 20 + body_len;')
567             ->endif
568             ->blank
569             ->line('request_size += 2;')
570             ->blank
571             ->comment('Allocate and build request')
572             ->line('SV* request = newSV(request_size);')
573             ->line('SvPOK_on(request);')
574             ->line('char* rp = SvPVX(request);')
575             ->blank
576             ->comment('Request line')
577             ->line('memcpy(rp, method, method_len); rp += method_len;')
578             ->line('*rp++ = \' \';')
579             ->line('memcpy(rp, path, path_len); rp += path_len;')
580             ->line('memcpy(rp, " HTTP/1.1\\r\\n", 11); rp += 11;')
581             ->blank
582             ->comment('Host header')
583             ->line('memcpy(rp, "Host: ", 6); rp += 6;')
584             ->line('memcpy(rp, host, host_len); rp += host_len;')
585             ->line('*rp++ = \'\\r\'; *rp++ = \'\\n\';')
586             ->blank
587             ->comment('Other headers')
588             ->line('hv_iterinit(headers);')
589             ->line('while ((entry = hv_iternext(headers)) != NULL) {')
590             ->line(' SV* key_sv = hv_iterkeysv(entry);')
591             ->line(' SV* val_sv = hv_iterval(headers, entry);')
592             ->line(' STRLEN key_len, val_len;')
593             ->line(' const char* key = SvPV(key_sv, key_len);')
594             ->line(' const char* val = SvPV(val_sv, val_len);')
595             ->line(' memcpy(rp, key, key_len); rp += key_len;')
596             ->line(' *rp++ = \':\'; *rp++ = \' \';')
597             ->line(' memcpy(rp, val, val_len); rp += val_len;')
598             ->line(' *rp++ = \'\\r\'; *rp++ = \'\\n\';')
599             ->line('}')
600             ->blank
601             ->comment('Content-Length and body if present')
602             ->if('body_len > 0')
603             ->line('rp += sprintf(rp, "Content-Length: %zu\\r\\n", body_len);')
604             ->endif
605             ->blank
606             ->comment('End of headers')
607             ->line('*rp++ = \'\\r\'; *rp++ = \'\\n\';')
608             ->blank
609             ->comment('Body')
610             ->if('body_len > 0')
611             ->line('const char* body = SvPV_nolen(body_sv);')
612             ->line('memcpy(rp, body, body_len); rp += body_len;')
613             ->endif
614             ->blank
615             ->line('SvCUR_set(request, rp - SvPVX(request));')
616             ->line('ST(0) = sv_2mortal(request);')
617             ->xs_return('1')
618             ->xs_end
619             ->blank;
620             }
621              
622             sub gen_xs_get {
623 8     8 0 55 my ($class, $builder) = @_;
624              
625             # Inlined HTTP GET with connection pooling and keep-alive
626 8         1654 $builder->comment('GET request - with keep-alive connection pooling')
627             ->comment('Usage: $ua->get($url) or $ua->get($url, sub { ... })')
628             ->xs_function('xs_ua_get')
629             ->xs_preamble
630             ->line('if (items < 2) croak("Usage: $ua->get($url, [$cb])");')
631             ->blank
632             ->line('SV *self_sv = ST(0);')
633             ->line('SV *url_sv = ST(1);')
634             ->line('SV *cb = (items >= 3 && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVCV) ? ST(2) : NULL;')
635             ->blank
636             ->comment('Parse URL')
637             ->line('STRLEN url_len;')
638             ->line('const char *url = SvPV(url_sv, url_len);')
639             ->blank
640             ->line('const char *scheme_end = strstr(url, "://");')
641             ->line('if (!scheme_end) croak("Invalid URL");')
642             ->blank
643             ->line('int is_https = (scheme_end - url == 5 && memcmp(url, "https", 5) == 0);')
644             ->line('const char *host_start = scheme_end + 3;')
645             ->line('const char *host_end = host_start;')
646             ->line('int port = is_https ? 443 : 80;')
647             ->blank
648             ->line('while (*host_end && *host_end != \':\' && *host_end != \'/\' && *host_end != \'?\') host_end++;')
649             ->blank
650             ->line('const char *p = host_end;')
651             ->if('*host_end == \':\'')
652             ->line('port = atoi(host_end + 1);')
653             ->line('while (*p && *p != \'/\' && *p != \'?\') p++;')
654             ->endif
655             ->blank
656             ->line('const char *path = (*p == \'/\') ? p : "/";')
657             ->blank
658             ->line('char host_buf[256];')
659             ->line('int host_len = host_end - host_start;')
660             ->line('if (host_len > 255) host_len = 255;')
661             ->line('memcpy(host_buf, host_start, host_len);')
662             ->line('host_buf[host_len] = 0;')
663             ->blank
664             ->comment('Try to get pooled connection first')
665             ->line('int fd = pool_get(host_buf, port);')
666             ->line('int pooled = (fd > 0);')
667             ->blank
668             ->if('fd <= 0')
669             ->comment('No pooled connection, create new one')
670             ->line('fd = socket(AF_INET, SOCK_STREAM, 0);')
671             ->line('if (fd < 0) croak("socket() failed");')
672             ->blank
673             ->line('struct sockaddr_in addr;')
674             ->line('memset(&addr, 0, sizeof(addr));')
675             ->line('addr.sin_family = AF_INET;')
676             ->line('addr.sin_port = htons(port);')
677             ->if('!dns_lookup_cached(host_buf, &addr.sin_addr)')
678             ->line('close(fd);')
679             ->line('croak("DNS resolution failed for %s", host_buf);')
680             ->endif
681             ->blank
682             ->if('connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0')
683             ->line('close(fd);')
684             ->line('croak("connect() failed");')
685             ->endif
686             ->endif
687             ->blank
688             ->comment('Build HTTP GET request with keep-alive')
689             ->line('char req_buf[4096];')
690             ->line('int req_len = snprintf(req_buf, sizeof(req_buf),')
691             ->line(' "GET %s HTTP/1.1\\r\\n"')
692             ->line(' "Host: %s\\r\\n"')
693             ->line(' "Connection: keep-alive\\r\\n"')
694             ->line(' "User-Agent: Hypersonic/1.0\\r\\n"')
695             ->line(' "\\r\\n",')
696             ->line(' path, host_buf);')
697             ->blank
698             ->comment('Send request')
699             ->line('if (send(fd, req_buf, req_len, 0) < 0) {')
700             ->line(' close(fd);')
701             ->line(' croak("send() failed");')
702             ->line('}')
703             ->blank
704             ->comment('Receive response - need to parse Content-Length for keep-alive')
705             ->line('char resp_buf[65536];')
706             ->line('int resp_len = 0;')
707             ->line('int headers_end = 0;')
708             ->line('int content_length = -1;')
709             ->line('int n;')
710             ->blank
711             ->comment('Read until we have headers')
712             ->line('while (!headers_end && resp_len < (int)sizeof(resp_buf) - 1) {')
713             ->line(' n = recv(fd, resp_buf + resp_len, sizeof(resp_buf) - resp_len - 1, 0);')
714             ->line(' if (n <= 0) break;')
715             ->line(' resp_len += n;')
716             ->line(' resp_buf[resp_len] = 0;')
717             ->line(' char *hdr_end = strstr(resp_buf, "\\r\\n\\r\\n");')
718             ->line(' if (hdr_end) {')
719             ->line(' headers_end = hdr_end - resp_buf + 4;')
720             ->line(' /* Parse Content-Length */')
721             ->line(' char *cl = strcasestr(resp_buf, "Content-Length:");')
722             ->line(' if (cl && cl < hdr_end) content_length = atoi(cl + 15);')
723             ->line(' }')
724             ->line('}')
725             ->blank
726             ->comment('Read remaining body based on Content-Length')
727             ->if('content_length > 0')
728             ->line('int body_received = resp_len - headers_end;')
729             ->line('while (body_received < content_length && resp_len < (int)sizeof(resp_buf) - 1) {')
730             ->line(' n = recv(fd, resp_buf + resp_len, sizeof(resp_buf) - resp_len - 1, 0);')
731             ->line(' if (n <= 0) break;')
732             ->line(' resp_len += n;')
733             ->line(' body_received += n;')
734             ->line('}')
735             ->endif
736             ->line('resp_buf[resp_len] = 0;')
737             ->blank
738             ->comment('Return connection to pool if keep-alive')
739             ->line('char *conn_hdr = strcasestr(resp_buf, "Connection:");')
740             ->line('int keep_alive = 1;')
741             ->if('conn_hdr && headers_end > 0 && conn_hdr < resp_buf + headers_end')
742             ->line('if (strncasecmp(conn_hdr + 11, " close", 6) == 0) keep_alive = 0;')
743             ->endif
744             ->blank
745             ->if('keep_alive && content_length >= 0')
746             ->line('pool_put(fd, host_buf, port);')
747             ->else
748             ->line('close(fd);')
749             ->endif
750             ->blank
751             ->comment('Parse response')
752             ->line('HV *result = newHV();')
753             ->blank
754             ->comment('Extract status code')
755             ->line('int status = 0;')
756             ->line('if (resp_len > 12 && memcmp(resp_buf, "HTTP/1.", 7) == 0) {')
757             ->line(' status = atoi(resp_buf + 9);')
758             ->line('}')
759             ->line('hv_stores(result, "status", newSViv(status));')
760             ->blank
761             ->comment('Find body')
762             ->line('const char *body_start = strstr(resp_buf, "\\r\\n\\r\\n");')
763             ->if('body_start')
764             ->line('body_start += 4;')
765             ->line('hv_stores(result, "body", newSVpv(body_start, resp_len - (body_start - resp_buf)));')
766             ->else
767             ->line('hv_stores(result, "body", newSVpvs(""));')
768             ->endif
769             ->blank
770             ->comment('Store headers')
771             ->line('HV *headers = newHV();')
772             ->line('hv_stores(result, "headers", newRV_noinc((SV *)headers));')
773             ->blank
774             ->comment('If callback provided, call it')
775             ->if('cb')
776             ->line('SPAGAIN;')
777             ->line('ENTER; SAVETMPS;')
778             ->line('PUSHMARK(SP);')
779             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV *)result)));')
780             ->line('PUTBACK;')
781             ->line('call_sv(cb, G_DISCARD);')
782             ->line('FREETMPS; LEAVE;')
783             ->line('XSRETURN_EMPTY;')
784             ->endif
785             ->blank
786             ->line('ST(0) = sv_2mortal(newRV_noinc((SV *)result));')
787             ->xs_return('1')
788             ->xs_end
789             ->blank;
790             }
791              
792             sub gen_xs_post {
793 8     8 0 37 my ($class, $builder) = @_;
794              
795 8         495 $builder->comment('POST request - blocking or callback')
796             ->comment('Usage: $ua->post($url, $body) or $ua->post($url, $body, sub { ... })')
797             ->xs_function('xs_ua_post')
798             ->xs_preamble
799             ->line('if (items < 3) croak("Usage: $ua->post($url, $body, [$cb])");')
800             ->blank
801             ->line('SV *self_sv = ST(0);')
802             ->line('SV *url_sv = ST(1);')
803             ->line('SV *body_sv = ST(2);')
804             ->line('SV *cb = (items >= 4 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVCV) ? ST(3) : NULL;')
805             ->blank
806             ->comment('Build args for request')
807             ->line('SPAGAIN;')
808             ->line('ENTER; SAVETMPS;')
809             ->line('PUSHMARK(SP);')
810             ->line('XPUSHs(self_sv);')
811             ->line('XPUSHs(sv_2mortal(newSVpvs("POST")));')
812             ->line('XPUSHs(url_sv);')
813             ->line('XPUSHs(body_sv);')
814             ->if('cb')
815             ->line('XPUSHs(cb);')
816             ->endif
817             ->line('PUTBACK;')
818             ->blank
819             ->line('int count = call_method("request", G_SCALAR);')
820             ->line('SPAGAIN;')
821             ->blank
822             ->line('SV *result = &PL_sv_undef;')
823             ->if('count > 0')
824             ->line('result = POPs;')
825             ->line('SvREFCNT_inc(result);')
826             ->endif
827             ->blank
828             ->line('PUTBACK;')
829             ->line('FREETMPS; LEAVE;')
830             ->blank
831             ->line('ST(0) = sv_2mortal(result);')
832             ->xs_return('1')
833             ->xs_end
834             ->blank;
835             }
836              
837             sub gen_xs_put {
838 8     8 0 94 my ($class, $builder) = @_;
839              
840 8         419 $builder->comment('PUT request - blocking or callback')
841             ->xs_function('xs_ua_put')
842             ->xs_preamble
843             ->line('if (items < 3) croak("Usage: $ua->put($url, $body, [$cb])");')
844             ->blank
845             ->line('SV *self_sv = ST(0);')
846             ->line('SV *url_sv = ST(1);')
847             ->line('SV *body_sv = ST(2);')
848             ->line('SV *cb = (items >= 4 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVCV) ? ST(3) : NULL;')
849             ->blank
850             ->line('SPAGAIN;')
851             ->line('ENTER; SAVETMPS;')
852             ->line('PUSHMARK(SP);')
853             ->line('XPUSHs(self_sv);')
854             ->line('XPUSHs(sv_2mortal(newSVpvs("PUT")));')
855             ->line('XPUSHs(url_sv);')
856             ->line('XPUSHs(body_sv);')
857             ->if('cb')
858             ->line('XPUSHs(cb);')
859             ->endif
860             ->line('PUTBACK;')
861             ->blank
862             ->line('int count = call_method("request", G_SCALAR);')
863             ->line('SPAGAIN;')
864             ->blank
865             ->line('SV *result = &PL_sv_undef;')
866             ->if('count > 0')
867             ->line('result = POPs;')
868             ->line('SvREFCNT_inc(result);')
869             ->endif
870             ->blank
871             ->line('PUTBACK;')
872             ->line('FREETMPS; LEAVE;')
873             ->blank
874             ->line('ST(0) = sv_2mortal(result);')
875             ->xs_return('1')
876             ->xs_end
877             ->blank;
878             }
879              
880             sub gen_xs_patch {
881 8     8 0 38 my ($class, $builder) = @_;
882              
883 8         534 $builder->comment('PATCH request - blocking or callback')
884             ->xs_function('xs_ua_patch')
885             ->xs_preamble
886             ->line('if (items < 3) croak("Usage: $ua->patch($url, $body, [$cb])");')
887             ->blank
888             ->line('SV *self_sv = ST(0);')
889             ->line('SV *url_sv = ST(1);')
890             ->line('SV *body_sv = ST(2);')
891             ->line('SV *cb = (items >= 4 && SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVCV) ? ST(3) : NULL;')
892             ->blank
893             ->line('SPAGAIN;')
894             ->line('ENTER; SAVETMPS;')
895             ->line('PUSHMARK(SP);')
896             ->line('XPUSHs(self_sv);')
897             ->line('XPUSHs(sv_2mortal(newSVpvs("PATCH")));')
898             ->line('XPUSHs(url_sv);')
899             ->line('XPUSHs(body_sv);')
900             ->if('cb')
901             ->line('XPUSHs(cb);')
902             ->endif
903             ->line('PUTBACK;')
904             ->blank
905             ->line('int count = call_method("request", G_SCALAR);')
906             ->line('SPAGAIN;')
907             ->blank
908             ->line('SV *result = &PL_sv_undef;')
909             ->if('count > 0')
910             ->line('result = POPs;')
911             ->line('SvREFCNT_inc(result);')
912             ->endif
913             ->blank
914             ->line('PUTBACK;')
915             ->line('FREETMPS; LEAVE;')
916             ->blank
917             ->line('ST(0) = sv_2mortal(result);')
918             ->xs_return('1')
919             ->xs_end
920             ->blank;
921             }
922              
923             sub gen_xs_delete {
924 8     8 0 19 my ($class, $builder) = @_;
925              
926 8         537 $builder->comment('DELETE request - blocking or callback')
927             ->xs_function('xs_ua_delete')
928             ->xs_preamble
929             ->line('if (items < 2) croak("Usage: $ua->delete($url, [$cb])");')
930             ->blank
931             ->line('SV *self_sv = ST(0);')
932             ->line('SV *url_sv = ST(1);')
933             ->line('SV *cb = (items >= 3 && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVCV) ? ST(2) : NULL;')
934             ->blank
935             ->line('SPAGAIN;')
936             ->line('ENTER; SAVETMPS;')
937             ->line('PUSHMARK(SP);')
938             ->line('XPUSHs(self_sv);')
939             ->line('XPUSHs(sv_2mortal(newSVpvs("DELETE")));')
940             ->line('XPUSHs(url_sv);')
941             ->if('cb')
942             ->line('XPUSHs(cb);')
943             ->endif
944             ->line('PUTBACK;')
945             ->blank
946             ->line('int count = call_method("request", G_SCALAR);')
947             ->line('SPAGAIN;')
948             ->blank
949             ->line('SV *result = &PL_sv_undef;')
950             ->if('count > 0')
951             ->line('result = POPs;')
952             ->line('SvREFCNT_inc(result);')
953             ->endif
954             ->blank
955             ->line('PUTBACK;')
956             ->line('FREETMPS; LEAVE;')
957             ->blank
958             ->line('ST(0) = sv_2mortal(result);')
959             ->xs_return('1')
960             ->xs_end
961             ->blank;
962             }
963              
964             sub gen_xs_head {
965 8     8 0 49 my ($class, $builder) = @_;
966              
967 8         333 $builder->comment('HEAD request - blocking or callback')
968             ->xs_function('xs_ua_head')
969             ->xs_preamble
970             ->line('if (items < 2) croak("Usage: $ua->head($url, [$cb])");')
971             ->blank
972             ->line('SV *self_sv = ST(0);')
973             ->line('SV *url_sv = ST(1);')
974             ->line('SV *cb = (items >= 3 && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVCV) ? ST(2) : NULL;')
975             ->blank
976             ->line('SPAGAIN;')
977             ->line('ENTER; SAVETMPS;')
978             ->line('PUSHMARK(SP);')
979             ->line('XPUSHs(self_sv);')
980             ->line('XPUSHs(sv_2mortal(newSVpvs("HEAD")));')
981             ->line('XPUSHs(url_sv);')
982             ->if('cb')
983             ->line('XPUSHs(cb);')
984             ->endif
985             ->line('PUTBACK;')
986             ->blank
987             ->line('int count = call_method("request", G_SCALAR);')
988             ->line('SPAGAIN;')
989             ->blank
990             ->line('SV *result = &PL_sv_undef;')
991             ->if('count > 0')
992             ->line('result = POPs;')
993             ->line('SvREFCNT_inc(result);')
994             ->endif
995             ->blank
996             ->line('PUTBACK;')
997             ->line('FREETMPS; LEAVE;')
998             ->blank
999             ->line('ST(0) = sv_2mortal(result);')
1000             ->xs_return('1')
1001             ->xs_end
1002             ->blank;
1003             }
1004              
1005             sub gen_xs_options {
1006 8     8 0 36 my ($class, $builder) = @_;
1007              
1008 8         317 $builder->comment('OPTIONS request - blocking or callback')
1009             ->xs_function('xs_ua_options')
1010             ->xs_preamble
1011             ->line('if (items < 2) croak("Usage: $ua->options($url, [$cb])");')
1012             ->blank
1013             ->line('SV *self_sv = ST(0);')
1014             ->line('SV *url_sv = ST(1);')
1015             ->line('SV *cb = (items >= 3 && SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVCV) ? ST(2) : NULL;')
1016             ->blank
1017             ->line('SPAGAIN;')
1018             ->line('ENTER; SAVETMPS;')
1019             ->line('PUSHMARK(SP);')
1020             ->line('XPUSHs(self_sv);')
1021             ->line('XPUSHs(sv_2mortal(newSVpvs("OPTIONS")));')
1022             ->line('XPUSHs(url_sv);')
1023             ->if('cb')
1024             ->line('XPUSHs(cb);')
1025             ->endif
1026             ->line('PUTBACK;')
1027             ->blank
1028             ->line('int count = call_method("request", G_SCALAR);')
1029             ->line('SPAGAIN;')
1030             ->blank
1031             ->line('SV *result = &PL_sv_undef;')
1032             ->if('count > 0')
1033             ->line('result = POPs;')
1034             ->line('SvREFCNT_inc(result);')
1035             ->endif
1036             ->blank
1037             ->line('PUTBACK;')
1038             ->line('FREETMPS; LEAVE;')
1039             ->blank
1040             ->line('ST(0) = sv_2mortal(result);')
1041             ->xs_return('1')
1042             ->xs_end
1043             ->blank;
1044             }
1045              
1046             sub gen_xs_request {
1047 9     9 0 78 my ($class, $builder) = @_;
1048              
1049 9         1907 $builder->comment('General request - blocking or callback')
1050             ->comment('Usage: $ua->request($method, $url, [$body], [$cb])')
1051             ->xs_function('xs_ua_request')
1052             ->xs_preamble
1053             ->line('if (items < 3) croak("Usage: $ua->request($method, $url, [$body], [$cb])");')
1054             ->blank
1055             ->line('SV *self_sv = ST(0);')
1056             ->line('SV *method_sv = ST(1);')
1057             ->line('SV *url_sv = ST(2);')
1058             ->line('SV *body_sv = NULL;')
1059             ->line('SV *cb = NULL;')
1060             ->blank
1061             ->comment('Determine if 4th arg is body or callback')
1062             ->if('items >= 4')
1063             ->if('SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVCV')
1064             ->line('cb = ST(3);')
1065             ->else
1066             ->line('body_sv = ST(3);')
1067             ->if('items >= 5 && SvROK(ST(4)) && SvTYPE(SvRV(ST(4))) == SVt_PVCV')
1068             ->line('cb = ST(4);')
1069             ->endif
1070             ->endif
1071             ->endif
1072             ->blank
1073             ->comment('Get UA slot and context')
1074             ->line('AV *self = (AV *)SvRV(self_sv);')
1075             ->line('SV **slot_sv = av_fetch(self, 0, 0);')
1076             ->line('if (!slot_sv || !SvOK(*slot_sv)) croak("Invalid UA object");')
1077             ->line('int slot = SvIV(*slot_sv);')
1078             ->line('UAContext *ctx = &ua_registry[slot];')
1079             ->blank
1080             ->comment('Parse URL')
1081             ->line('STRLEN url_len;')
1082             ->line('const char *url = SvPV(url_sv, url_len);')
1083             ->blank
1084             ->line('const char *scheme_end = strstr(url, "://");')
1085             ->line('if (!scheme_end) croak("Invalid URL");')
1086             ->blank
1087             ->line('int is_https = (scheme_end - url == 5 && memcmp(url, "https", 5) == 0);')
1088             ->line('const char *host_start = scheme_end + 3;')
1089             ->line('const char *host_end = host_start;')
1090             ->line('int port = is_https ? 443 : 80;')
1091             ->blank
1092             ->line('while (*host_end && *host_end != \':\' && *host_end != \'/\' && *host_end != \'?\') host_end++;')
1093             ->blank
1094             ->line('const char *p = host_end;')
1095             ->if('*host_end == \':\'')
1096             ->line('port = atoi(host_end + 1);')
1097             ->line('while (*p && *p != \'/\' && *p != \'?\') p++;')
1098             ->endif
1099             ->blank
1100             ->line('const char *path = (*p == \'/\') ? p : "/";')
1101             ->blank
1102             ->comment('Build request')
1103             ->line('STRLEN method_len;')
1104             ->line('const char *method = SvPV(method_sv, method_len);')
1105             ->blank
1106             ->line('char host_buf[256];')
1107             ->line('int host_len = host_end - host_start;')
1108             ->line('if (host_len > 255) host_len = 255;')
1109             ->line('memcpy(host_buf, host_start, host_len);')
1110             ->line('host_buf[host_len] = 0;')
1111             ->blank
1112             ->comment('Create socket and connect with cached DNS')
1113             ->line('int fd = socket(AF_INET, SOCK_STREAM, 0);')
1114             ->line('if (fd < 0) croak("socket() failed");')
1115             ->blank
1116             ->line('struct sockaddr_in addr;')
1117             ->line('memset(&addr, 0, sizeof(addr));')
1118             ->line('addr.sin_family = AF_INET;')
1119             ->line('addr.sin_port = htons(port);')
1120             ->if('!dns_lookup_cached(host_buf, &addr.sin_addr)')
1121             ->line('close(fd);')
1122             ->line('croak("DNS resolution failed for %s", host_buf);')
1123             ->endif
1124             ->blank
1125             ->if('connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0')
1126             ->line('close(fd);')
1127             ->line('croak("connect() failed");')
1128             ->endif
1129             ->blank
1130             ->comment('Build HTTP request string')
1131             ->line('char req_buf[8192];')
1132             ->line('int req_len = snprintf(req_buf, sizeof(req_buf),')
1133             ->line(' "%s %s HTTP/1.1\\r\\n"')
1134             ->line(' "Host: %s\\r\\n"')
1135             ->line(' "Connection: close\\r\\n"')
1136             ->line(' "User-Agent: Hypersonic/1.0\\r\\n",')
1137             ->line(' method, path, host_buf);')
1138             ->blank
1139             ->if('body_sv && SvOK(body_sv)')
1140             ->line('STRLEN body_len;')
1141             ->line('const char *body = SvPV(body_sv, body_len);')
1142             ->line('req_len += snprintf(req_buf + req_len, sizeof(req_buf) - req_len,')
1143             ->line(' "Content-Length: %zu\\r\\n\\r\\n", body_len);')
1144             ->line('if (req_len + body_len < sizeof(req_buf)) {')
1145             ->line(' memcpy(req_buf + req_len, body, body_len);')
1146             ->line(' req_len += body_len;')
1147             ->line('}')
1148             ->else
1149             ->line('req_len += snprintf(req_buf + req_len, sizeof(req_buf) - req_len, "\\r\\n");')
1150             ->endif
1151             ->blank
1152             ->comment('Send request')
1153             ->line('if (send(fd, req_buf, req_len, 0) < 0) {')
1154             ->line(' close(fd);')
1155             ->line(' croak("send() failed");')
1156             ->line('}')
1157             ->blank
1158             ->comment('Receive response')
1159             ->line('char resp_buf[65536];')
1160             ->line('int resp_len = 0;')
1161             ->line('int n;')
1162             ->line('while ((n = recv(fd, resp_buf + resp_len, sizeof(resp_buf) - resp_len - 1, 0)) > 0) {')
1163             ->line(' resp_len += n;')
1164             ->line('}')
1165             ->line('resp_buf[resp_len] = 0;')
1166             ->line('close(fd);')
1167             ->blank
1168             ->comment('Parse response')
1169             ->line('HV *result = newHV();')
1170             ->blank
1171             ->comment('Extract status code')
1172             ->line('int status = 0;')
1173             ->line('if (resp_len > 12 && memcmp(resp_buf, "HTTP/1.", 7) == 0) {')
1174             ->line(' status = atoi(resp_buf + 9);')
1175             ->line('}')
1176             ->line('hv_stores(result, "status", newSViv(status));')
1177             ->blank
1178             ->comment('Find body')
1179             ->line('const char *body_start = strstr(resp_buf, "\\r\\n\\r\\n");')
1180             ->if('body_start')
1181             ->line('body_start += 4;')
1182             ->line('hv_stores(result, "body", newSVpv(body_start, resp_len - (body_start - resp_buf)));')
1183             ->else
1184             ->line('hv_stores(result, "body", newSVpvs(""));')
1185             ->endif
1186             ->blank
1187             ->comment('Store headers')
1188             ->line('HV *headers = newHV();')
1189             ->line('hv_stores(result, "headers", newRV_noinc((SV *)headers));')
1190             ->blank
1191             ->comment('If callback provided, call it')
1192             ->if('cb')
1193             ->line('SPAGAIN;')
1194             ->line('ENTER; SAVETMPS;')
1195             ->line('PUSHMARK(SP);')
1196             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV *)result)));')
1197             ->line('PUTBACK;')
1198             ->line('call_sv(cb, G_DISCARD);')
1199             ->line('FREETMPS; LEAVE;')
1200             ->line('XSRETURN_EMPTY;')
1201             ->endif
1202             ->blank
1203             ->line('ST(0) = sv_2mortal(newRV_noinc((SV *)result));')
1204             ->xs_return('1')
1205             ->xs_end
1206             ->blank;
1207             }
1208              
1209             # gen_xs_tick moved to Hypersonic::UA::Async for proper header ordering
1210              
1211             sub gen_xs_pending {
1212 6     6 0 52 my ($class, $builder) = @_;
1213              
1214 6         170 $builder->comment('Get count of pending async requests')
1215             ->xs_function('xs_ua_pending')
1216             ->xs_preamble
1217             ->line('if (items < 1) croak("Usage: $ua->pending()");')
1218             ->blank
1219             ->line('SV *self_sv = ST(0);')
1220             ->line('HV *ua_hv = (HV *)SvRV(self_sv);')
1221             ->blank
1222             ->comment('Get the _async_pending array')
1223             ->line('SV **pending_svp = hv_fetch(ua_hv, "_async_pending", 14, 0);')
1224             ->if('!pending_svp || !SvROK(*pending_svp)')
1225             ->line('ST(0) = sv_2mortal(newSViv(0));')
1226             ->line('XSRETURN(1);')
1227             ->endif
1228             ->blank
1229             ->line('AV *pending_av = (AV *)SvRV(*pending_svp);')
1230             ->line('I32 pending = av_len(pending_av) + 1;')
1231             ->blank
1232             ->line('ST(0) = sv_2mortal(newSViv(pending));')
1233             ->xs_return('1')
1234             ->xs_end
1235             ->blank;
1236             }
1237              
1238             sub gen_xs_get_async {
1239 6     6 0 105 my ($class, $builder) = @_;
1240              
1241 6         272 $builder->comment('Async GET - returns a Future')
1242             ->xs_function('xs_ua_get_async')
1243             ->xs_preamble
1244             ->line('if (items < 2) croak("Usage: $ua->get_async($url)");')
1245             ->blank
1246             ->line('SV *self_sv = ST(0);')
1247             ->line('SV *url_sv = ST(1);')
1248             ->blank
1249             ->line('SPAGAIN;')
1250             ->line('ENTER; SAVETMPS;')
1251             ->line('PUSHMARK(SP);')
1252             ->line('XPUSHs(self_sv);')
1253             ->line('XPUSHs(sv_2mortal(newSVpvs("GET")));')
1254             ->line('XPUSHs(url_sv);')
1255             ->line('PUTBACK;')
1256             ->blank
1257             ->line('int count = call_method("request_async", G_SCALAR);')
1258             ->line('SPAGAIN;')
1259             ->blank
1260             ->line('SV *result = &PL_sv_undef;')
1261             ->if('count > 0')
1262             ->line('result = POPs;')
1263             ->line('SvREFCNT_inc(result);')
1264             ->endif
1265             ->blank
1266             ->line('PUTBACK;')
1267             ->line('FREETMPS; LEAVE;')
1268             ->blank
1269             ->line('ST(0) = sv_2mortal(result);')
1270             ->xs_return('1')
1271             ->xs_end
1272             ->blank;
1273             }
1274              
1275             sub gen_xs_post_async {
1276 5     5 0 36 my ($class, $builder) = @_;
1277              
1278 5         213 $builder->comment('Async POST - returns a Future')
1279             ->xs_function('xs_ua_post_async')
1280             ->xs_preamble
1281             ->line('if (items < 3) croak("Usage: $ua->post_async($url, $body)");')
1282             ->blank
1283             ->line('SV *self_sv = ST(0);')
1284             ->line('SV *url_sv = ST(1);')
1285             ->line('SV *body_sv = ST(2);')
1286             ->blank
1287             ->line('SPAGAIN;')
1288             ->line('ENTER; SAVETMPS;')
1289             ->line('PUSHMARK(SP);')
1290             ->line('XPUSHs(self_sv);')
1291             ->line('XPUSHs(sv_2mortal(newSVpvs("POST")));')
1292             ->line('XPUSHs(url_sv);')
1293             ->line('XPUSHs(body_sv);')
1294             ->line('PUTBACK;')
1295             ->blank
1296             ->line('int count = call_method("request_async", G_SCALAR);')
1297             ->line('SPAGAIN;')
1298             ->blank
1299             ->line('SV *result = &PL_sv_undef;')
1300             ->if('count > 0')
1301             ->line('result = POPs;')
1302             ->line('SvREFCNT_inc(result);')
1303             ->endif
1304             ->blank
1305             ->line('PUTBACK;')
1306             ->line('FREETMPS; LEAVE;')
1307             ->blank
1308             ->line('ST(0) = sv_2mortal(result);')
1309             ->xs_return('1')
1310             ->xs_end
1311             ->blank;
1312             }
1313              
1314             sub gen_xs_put_async {
1315 5     5 0 19 my ($class, $builder) = @_;
1316              
1317 5         320 $builder->comment('Async PUT - returns a Future')
1318             ->xs_function('xs_ua_put_async')
1319             ->xs_preamble
1320             ->line('if (items < 3) croak("Usage: $ua->put_async($url, $body)");')
1321             ->blank
1322             ->line('SV *self_sv = ST(0);')
1323             ->line('SV *url_sv = ST(1);')
1324             ->line('SV *body_sv = ST(2);')
1325             ->blank
1326             ->line('SPAGAIN;')
1327             ->line('ENTER; SAVETMPS;')
1328             ->line('PUSHMARK(SP);')
1329             ->line('XPUSHs(self_sv);')
1330             ->line('XPUSHs(sv_2mortal(newSVpvs("PUT")));')
1331             ->line('XPUSHs(url_sv);')
1332             ->line('XPUSHs(body_sv);')
1333             ->line('PUTBACK;')
1334             ->blank
1335             ->line('int count = call_method("request_async", G_SCALAR);')
1336             ->line('SPAGAIN;')
1337             ->blank
1338             ->line('SV *result = &PL_sv_undef;')
1339             ->if('count > 0')
1340             ->line('result = POPs;')
1341             ->line('SvREFCNT_inc(result);')
1342             ->endif
1343             ->blank
1344             ->line('PUTBACK;')
1345             ->line('FREETMPS; LEAVE;')
1346             ->blank
1347             ->line('ST(0) = sv_2mortal(result);')
1348             ->xs_return('1')
1349             ->xs_end
1350             ->blank;
1351             }
1352              
1353             sub gen_xs_delete_async {
1354 5     5 0 59 my ($class, $builder) = @_;
1355              
1356 5         200 $builder->comment('Async DELETE - returns a Future')
1357             ->xs_function('xs_ua_delete_async')
1358             ->xs_preamble
1359             ->line('if (items < 2) croak("Usage: $ua->delete_async($url)");')
1360             ->blank
1361             ->line('SV *self_sv = ST(0);')
1362             ->line('SV *url_sv = ST(1);')
1363             ->blank
1364             ->line('SPAGAIN;')
1365             ->line('ENTER; SAVETMPS;')
1366             ->line('PUSHMARK(SP);')
1367             ->line('XPUSHs(self_sv);')
1368             ->line('XPUSHs(sv_2mortal(newSVpvs("DELETE")));')
1369             ->line('XPUSHs(url_sv);')
1370             ->line('PUTBACK;')
1371             ->blank
1372             ->line('int count = call_method("request_async", G_SCALAR);')
1373             ->line('SPAGAIN;')
1374             ->blank
1375             ->line('SV *result = &PL_sv_undef;')
1376             ->if('count > 0')
1377             ->line('result = POPs;')
1378             ->line('SvREFCNT_inc(result);')
1379             ->endif
1380             ->blank
1381             ->line('PUTBACK;')
1382             ->line('FREETMPS; LEAVE;')
1383             ->blank
1384             ->line('ST(0) = sv_2mortal(result);')
1385             ->xs_return('1')
1386             ->xs_end
1387             ->blank;
1388             }
1389              
1390             sub gen_xs_request_async {
1391 5     5 0 53 my ($class, $builder) = @_;
1392              
1393 5         622 $builder->comment('Async general request - returns a Future')
1394             ->xs_function('xs_ua_request_async')
1395             ->xs_preamble
1396             ->line('if (items < 3) croak("Usage: $ua->request_async($method, $url, [$body])");')
1397             ->blank
1398             ->line('SV *self_sv = ST(0);')
1399             ->line('SV *method_sv = ST(1);')
1400             ->line('SV *url_sv = ST(2);')
1401             ->line('SV *body_sv = (items >= 4) ? ST(3) : &PL_sv_undef;')
1402             ->blank
1403             ->comment('Create a Future')
1404             ->line('SPAGAIN;')
1405             ->line('ENTER; SAVETMPS;')
1406             ->line('PUSHMARK(SP);')
1407             ->line('PUTBACK;')
1408             ->blank
1409             ->line('int count = call_pv("Hypersonic::Future::new", G_SCALAR);')
1410             ->line('SPAGAIN;')
1411             ->blank
1412             ->line('SV *future = &PL_sv_undef;')
1413             ->if('count > 0')
1414             ->line('future = POPs;')
1415             ->line('SvREFCNT_inc(future);')
1416             ->endif
1417             ->blank
1418             ->line('PUTBACK;')
1419             ->line('FREETMPS; LEAVE;')
1420             ->blank
1421             ->comment('Start async request via Hypersonic::UA::Async')
1422             ->comment('Pass self_sv so start_request can auto-tick')
1423             ->line('ENTER; SAVETMPS;')
1424             ->line('PUSHMARK(SP);')
1425             ->line('XPUSHs(method_sv);')
1426             ->line('XPUSHs(url_sv);')
1427             ->line('XPUSHs(body_sv);')
1428             ->line('XPUSHs(future);')
1429             ->line('XPUSHs(self_sv);')
1430             ->line('PUTBACK;')
1431             ->blank
1432             ->line('count = call_pv("Hypersonic::UA::Async::start_request", G_SCALAR);')
1433             ->line('SPAGAIN;')
1434             ->blank
1435             ->line('int async_slot = -1;')
1436             ->if('count > 0')
1437             ->line('async_slot = POPi;')
1438             ->endif
1439             ->blank
1440             ->line('PUTBACK;')
1441             ->line('FREETMPS; LEAVE;')
1442             ->blank
1443             ->comment('Store async slot in UA for polling')
1444             ->if('async_slot >= 0')
1445             ->comment('Associate slot with self for run() to find')
1446             ->line('HV *ua_hv = (HV *)SvRV(self_sv);')
1447             ->line('AV *pending_av;')
1448             ->line('SV **pending_svp = hv_fetch(ua_hv, "_async_pending", 14, 0);')
1449             ->if('pending_svp && SvROK(*pending_svp)')
1450             ->line('pending_av = (AV *)SvRV(*pending_svp);')
1451             ->else
1452             ->line('pending_av = newAV();')
1453             ->line('hv_store(ua_hv, "_async_pending", 14, newRV_noinc((SV *)pending_av), 0);')
1454             ->endif
1455             ->line('av_push(pending_av, newSViv(async_slot));')
1456             ->endif
1457             ->blank
1458             ->line('ST(0) = sv_2mortal(future);')
1459             ->xs_return('1')
1460             ->xs_end
1461             ->blank;
1462             }
1463              
1464             sub gen_xs_run {
1465 6     6 0 1791611 my ($class, $builder) = @_;
1466              
1467 6         363 $builder->comment('Run all pending async requests to completion')
1468             ->xs_function('xs_ua_run')
1469             ->xs_preamble
1470             ->line('if (items < 1) croak("Usage: $ua->run()");')
1471             ->blank
1472             ->line('SV *self_sv = ST(0);')
1473             ->blank
1474             ->comment('Poll until all pending requests complete')
1475             ->line('int iterations = 0;')
1476             ->line('int max_iterations = 10000;')
1477             ->blank
1478             ->line('SPAGAIN;')
1479             ->line('while (iterations++ < max_iterations) {')
1480             ->line(' ENTER; SAVETMPS;')
1481             ->line(' PUSHMARK(SP);')
1482             ->line(' XPUSHs(self_sv);')
1483             ->line(' PUTBACK;')
1484             ->blank
1485             ->line(' call_method("pending", G_SCALAR);')
1486             ->line(' SPAGAIN;')
1487             ->blank
1488             ->line(' int pending = POPi;')
1489             ->line(' PUTBACK;')
1490             ->line(' FREETMPS; LEAVE;')
1491             ->blank
1492             ->line(' if (pending == 0) break;')
1493             ->blank
1494             ->comment(' Tick once')
1495             ->line(' ENTER; SAVETMPS;')
1496             ->line(' PUSHMARK(SP);')
1497             ->line(' XPUSHs(self_sv);')
1498             ->line(' PUTBACK;')
1499             ->line(' call_method("tick", G_DISCARD);')
1500             ->line(' FREETMPS; LEAVE;')
1501             ->line('}')
1502             ->blank
1503             ->xs_return('0')
1504             ->xs_end
1505             ->blank;
1506             }
1507              
1508             sub gen_xs_run_one {
1509 6     6 0 9918 my ($class, $builder) = @_;
1510              
1511 6         390 $builder->comment('Run one async request to completion')
1512             ->xs_function('xs_ua_run_one')
1513             ->xs_preamble
1514             ->line('if (items < 2) croak("Usage: $ua->run_one($future)");')
1515             ->blank
1516             ->line('SV *self_sv = ST(0);')
1517             ->line('SV *future_sv = ST(1);')
1518             ->blank
1519             ->comment('Poll until this specific future resolves')
1520             ->line('int iterations = 0;')
1521             ->line('int max_iterations = 10000;')
1522             ->blank
1523             ->line('SPAGAIN;')
1524             ->line('while (iterations++ < max_iterations) {')
1525             ->comment(' Check if future is done')
1526             ->line(' ENTER; SAVETMPS;')
1527             ->line(' PUSHMARK(SP);')
1528             ->line(' XPUSHs(future_sv);')
1529             ->line(' PUTBACK;')
1530             ->blank
1531             ->line(' call_method("is_ready", G_SCALAR);')
1532             ->line(' SPAGAIN;')
1533             ->blank
1534             ->line(' int ready = POPi;')
1535             ->line(' PUTBACK;')
1536             ->line(' FREETMPS; LEAVE;')
1537             ->blank
1538             ->line(' if (ready) break;')
1539             ->blank
1540             ->comment(' Tick once')
1541             ->line(' ENTER; SAVETMPS;')
1542             ->line(' PUSHMARK(SP);')
1543             ->line(' XPUSHs(self_sv);')
1544             ->line(' PUTBACK;')
1545             ->line(' call_method("tick", G_DISCARD);')
1546             ->line(' FREETMPS; LEAVE;')
1547             ->line('}')
1548             ->blank
1549             ->comment('Return the future result')
1550             ->line('ENTER; SAVETMPS;')
1551             ->line('PUSHMARK(SP);')
1552             ->line('XPUSHs(future_sv);')
1553             ->line('PUTBACK;')
1554             ->blank
1555             ->line('call_method("get", G_SCALAR);')
1556             ->line('SPAGAIN;')
1557             ->blank
1558             ->line('SV *result = POPs;')
1559             ->line('SvREFCNT_inc(result);')
1560             ->line('PUTBACK;')
1561             ->line('FREETMPS; LEAVE;')
1562             ->blank
1563             ->line('ST(0) = sv_2mortal(result);')
1564             ->xs_return('1')
1565             ->xs_end
1566             ->blank;
1567             }
1568              
1569             sub gen_xs_parallel {
1570 3     3 0 5612 my ($class, $builder) = @_;
1571              
1572 3         205 $builder->comment('Run multiple requests in parallel, wait for all')
1573             ->xs_function('xs_ua_parallel')
1574             ->xs_preamble
1575             ->line('int i;')
1576             ->line('if (items < 2) croak("Usage: $ua->parallel(@futures)");')
1577             ->blank
1578             ->line('SV *self_sv = ST(0);')
1579             ->blank
1580             ->comment('Collect futures')
1581             ->line('AV *futures = newAV();')
1582             ->line('for (i = 1; i < items; i++) {')
1583             ->line(' av_push(futures, SvREFCNT_inc(ST(i)));')
1584             ->line('}')
1585             ->blank
1586             ->comment('Create needs_all future')
1587             ->line('SPAGAIN;')
1588             ->line('ENTER; SAVETMPS;')
1589             ->line('PUSHMARK(SP);')
1590             ->line('for (i = 0; i <= av_len(futures); i++) {')
1591             ->line(' SV **f = av_fetch(futures, i, 0);')
1592             ->line(' if (f && *f) XPUSHs(*f);')
1593             ->line('}')
1594             ->line('PUTBACK;')
1595             ->blank
1596             ->line('int count = call_pv("Hypersonic::Future::needs_all", G_SCALAR);')
1597             ->line('SPAGAIN;')
1598             ->blank
1599             ->line('SV *combined = &PL_sv_undef;')
1600             ->if('count > 0')
1601             ->line('combined = POPs;')
1602             ->line('SvREFCNT_inc(combined);')
1603             ->endif
1604             ->blank
1605             ->line('PUTBACK;')
1606             ->line('FREETMPS; LEAVE;')
1607             ->blank
1608             ->comment('Run until combined future resolves')
1609             ->line('ENTER; SAVETMPS;')
1610             ->line('PUSHMARK(SP);')
1611             ->line('XPUSHs(self_sv);')
1612             ->line('XPUSHs(combined);')
1613             ->line('PUTBACK;')
1614             ->blank
1615             ->line('call_method("run_one", G_SCALAR);')
1616             ->line('SPAGAIN;')
1617             ->blank
1618             ->line('SV *result = POPs;')
1619             ->line('SvREFCNT_inc(result);')
1620             ->blank
1621             ->line('PUTBACK;')
1622             ->line('FREETMPS; LEAVE;')
1623             ->blank
1624             ->line('SvREFCNT_dec((SV *)futures);')
1625             ->line('SvREFCNT_dec(combined);')
1626             ->blank
1627             ->line('ST(0) = sv_2mortal(result);')
1628             ->xs_return('1')
1629             ->xs_end
1630             ->blank;
1631             }
1632              
1633             sub gen_xs_race {
1634 3     3 0 7519 my ($class, $builder) = @_;
1635              
1636 3         178 $builder->comment('Run multiple requests in parallel, return first to complete')
1637             ->xs_function('xs_ua_race')
1638             ->xs_preamble
1639             ->line('int i;')
1640             ->line('if (items < 2) croak("Usage: $ua->race(@futures)");')
1641             ->blank
1642             ->line('SV *self_sv = ST(0);')
1643             ->blank
1644             ->comment('Collect futures')
1645             ->line('AV *futures = newAV();')
1646             ->line('for (i = 1; i < items; i++) {')
1647             ->line(' av_push(futures, SvREFCNT_inc(ST(i)));')
1648             ->line('}')
1649             ->blank
1650             ->comment('Create needs_any future')
1651             ->line('SPAGAIN;')
1652             ->line('ENTER; SAVETMPS;')
1653             ->line('PUSHMARK(SP);')
1654             ->line('for (i = 0; i <= av_len(futures); i++) {')
1655             ->line(' SV **f = av_fetch(futures, i, 0);')
1656             ->line(' if (f && *f) XPUSHs(*f);')
1657             ->line('}')
1658             ->line('PUTBACK;')
1659             ->blank
1660             ->line('int count = call_pv("Hypersonic::Future::needs_any", G_SCALAR);')
1661             ->line('SPAGAIN;')
1662             ->blank
1663             ->line('SV *combined = &PL_sv_undef;')
1664             ->if('count > 0')
1665             ->line('combined = POPs;')
1666             ->line('SvREFCNT_inc(combined);')
1667             ->endif
1668             ->blank
1669             ->line('PUTBACK;')
1670             ->line('FREETMPS; LEAVE;')
1671             ->blank
1672             ->comment('Run until combined future resolves')
1673             ->line('ENTER; SAVETMPS;')
1674             ->line('PUSHMARK(SP);')
1675             ->line('XPUSHs(self_sv);')
1676             ->line('XPUSHs(combined);')
1677             ->line('PUTBACK;')
1678             ->blank
1679             ->line('call_method("run_one", G_SCALAR);')
1680             ->line('SPAGAIN;')
1681             ->blank
1682             ->line('SV *result = POPs;')
1683             ->line('SvREFCNT_inc(result);')
1684             ->blank
1685             ->line('PUTBACK;')
1686             ->line('FREETMPS; LEAVE;')
1687             ->blank
1688             ->line('SvREFCNT_dec((SV *)futures);')
1689             ->line('SvREFCNT_dec(combined);')
1690             ->blank
1691             ->line('ST(0) = sv_2mortal(result);')
1692             ->xs_return('1')
1693             ->xs_end
1694             ->blank;
1695             }
1696              
1697             #############################################################################
1698             # Stub methods for disabled features (provide helpful error messages)
1699             #############################################################################
1700              
1701             # These are installed as Perl methods when the feature is not compiled
1702             # They will be overwritten by XS if the feature IS compiled
1703              
1704             sub _feature_not_enabled {
1705 26     26   52 my ($method, $feature) = @_;
1706             return sub {
1707 6     6   31546 Carp::croak("$method() requires: Hypersonic::UA->compile($feature => 1)");
1708 26         85 };
1709             }
1710              
1711             # Install stubs for async methods (only if not compiled)
1712             sub _install_stubs {
1713 4     4   17 my ($class) = @_;
1714              
1715             # Async methods require async => 1
1716 4 100       18 unless ($FEATURES{needs_async}) {
1717 13     13   139 no strict 'refs';
  13         26  
  13         2431  
1718 2         8 *{"${class}::get_async"} = _feature_not_enabled('get_async', 'async');
  2         22  
1719 2         8 *{"${class}::post_async"} = _feature_not_enabled('post_async', 'async');
  2         9  
1720 2         8 *{"${class}::put_async"} = _feature_not_enabled('put_async', 'async');
  2         7  
1721 2         8 *{"${class}::delete_async"} = _feature_not_enabled('delete_async', 'async');
  2         8  
1722 2         7 *{"${class}::request_async"} = _feature_not_enabled('request_async', 'async');
  2         26  
1723 2         6 *{"${class}::tick"} = _feature_not_enabled('tick', 'async');
  2         8  
1724 2         8 *{"${class}::run"} = _feature_not_enabled('run', 'async');
  2         21  
1725 2         29 *{"${class}::run_one"} = _feature_not_enabled('run_one', 'async');
  2         21  
1726 2         6 *{"${class}::pending"} = _feature_not_enabled('pending', 'async');
  2         36  
1727             }
1728              
1729             # Parallel methods require parallel => 1
1730 4 50       16 unless ($FEATURES{needs_parallel}) {
1731 13     13   64 no strict 'refs';
  13         16  
  13         1511  
1732 4         20 *{"${class}::parallel"} = _feature_not_enabled('parallel', 'parallel');
  4         22  
1733 4         11 *{"${class}::race"} = _feature_not_enabled('race', 'parallel');
  4         25  
1734             }
1735             }
1736              
1737             # Call _install_stubs after compile() to set up error handlers
1738             {
1739             my $orig_compile = \&compile;
1740 13     13   83 no warnings 'redefine';
  13         48  
  13         1953  
1741             *compile = sub {
1742 4     4   5434 my $result = $orig_compile->(@_);
1743 4 50       45 __PACKAGE__->_install_stubs() if $result;
1744 4         36 return $result;
1745             };
1746             }
1747              
1748             1;
1749              
1750             __END__