File Coverage

blib/lib/Hypersonic/Socket.pm
Criterion Covered Total %
statement 41 49 83.6
branch 5 18 27.7
condition 1 2 50.0
subroutine 10 11 90.9
pod 2 4 50.0
total 59 84 70.2


line stmt bran cond sub pod time code
1             package Hypersonic::Socket;
2              
3 41     41   594716 use strict;
  41         58  
  41         1300  
4 41     41   255 use warnings;
  41         69  
  41         1788  
5 41     41   630 use 5.010;
  41         107  
6              
7             our $VERSION = '0.15';
8              
9 41     41   2217 use XS::JIT;
  41         4894  
  41         928  
10 41     41   2964 use XS::JIT::Builder;
  41         7115  
  41         1744  
11 41     41   17646 use Hypersonic::JIT::Util;
  41         90  
  41         36068  
12              
13             # Platform detection
14             sub platform {
15 7 50   7 1 384712 return 'darwin' if $^O eq 'darwin';
16 7 50       37 return 'linux' if $^O eq 'linux';
17 0 0       0 return 'freebsd' if $^O eq 'freebsd';
18 0 0       0 return 'openbsd' if $^O eq 'openbsd';
19 0 0       0 return 'netbsd' if $^O eq 'netbsd';
20 0 0       0 return 'mswin32' if $^O eq 'MSWin32';
21 0 0       0 return 'cygwin' if $^O eq 'cygwin';
22 0         0 die "Unsupported platform: $^O";
23             }
24              
25             # Event backend detection (delegates to Hypersonic::Event)
26             sub event_backend {
27 1     1 0 694 require Hypersonic::Event;
28 1         7 return Hypersonic::Event->best_backend();
29             }
30              
31             my $COMPILED = 0;
32             my $MODULE_ID = 0;
33              
34             # Unified compile interface
35             sub compile {
36 0     0 0 0 my ($class, %opts) = @_;
37 0         0 return $class->compile_socket_ops(%opts);
38             }
39              
40             # Generate and compile JIT socket functions using Builder
41             sub compile_socket_ops {
42 42     42 1 95 my ($class, %opts) = @_;
43              
44 42 100       173 return 1 if $COMPILED;
45              
46 41   50     215 my $cache_dir = $opts{cache_dir} // '_hypersonic_cache/socket';
47 41         139 my $module_name = 'Hypersonic::Socket::Ops_' . $MODULE_ID++;
48              
49 41         357 my $builder = XS::JIT::Builder->new;
50              
51             # Common includes via centralized utility
52 41         170 Hypersonic::JIT::Util->add_standard_includes($builder,
53             qw(stdio unistd fcntl socket));
54              
55 41         188 $builder->line('#define RECV_BUF_SIZE 65536')
56             ->blank
57             ->line('static char recv_buf[RECV_BUF_SIZE];')
58             ->blank;
59              
60             # Windows: WSAStartup must run before any socket call. We don't
61             # have a BOOT hook in the JIT module, so guard with a static flag
62             # and call it from create_listen_socket (the first entrypoint
63             # users hit). socket() requires Winsock to be initialized; otherwise
64             # it returns INVALID_SOCKET with WSANOTINITIALISED.
65 41         122 $builder->raw(<<'C');
66             #ifdef _WIN32
67             static int hs_wsa_initialized = 0;
68             static void hs_wsa_init(void) {
69             if (!hs_wsa_initialized) {
70             WSADATA wsa;
71             if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
72             return; /* leave flag unset; create_listen_socket will croak */
73             }
74             hs_wsa_initialized = 1;
75             }
76             }
77             #else
78             static inline void hs_wsa_init(void) {}
79             #endif
80             C
81              
82             # Generate create_listen_socket
83 41         12871 $builder->xs_function('jit_create_listen_socket')
84             ->xs_preamble
85             ->line('IV port;')
86             ->line('int fd;')
87             ->line('int opt;')
88             ->line('struct sockaddr_in addr;')
89             ->blank
90             ->line('if (items != 1) croak("Usage: create_listen_socket(port)");')
91             ->line('port = SvIV(ST(0));')
92             ->blank
93             ->line('hs_wsa_init(); /* no-op on POSIX */')
94             ->line('fd = socket(AF_INET, SOCK_STREAM, 0);')
95             ->if('fd < 0')
96             # Surface the actual errno - returning silent -1 hides why the
97             # child server died on platforms where bind/listen/socket fail
98             # for non-obvious reasons (see OpenBSD smoke reports).
99             ->line('croak("socket() failed: %s", strerror(errno));')
100             ->endif
101             ->blank
102             ->line('opt = 1;')
103             ->line('setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));')
104             ->line('#ifdef SO_REUSEPORT')
105             ->line('setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt, sizeof(opt));')
106             ->line('#endif')
107             ->blank
108             ->line('hs_set_nonblocking(fd);')
109             ->blank
110             ->line('memset(&addr, 0, sizeof(addr));')
111             ->line('addr.sin_family = AF_INET;')
112             ->line('addr.sin_port = htons((uint16_t)port);')
113             ->line('addr.sin_addr.s_addr = INADDR_ANY;')
114             ->blank
115             ->if('bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0')
116             ->line('int saved_errno = errno;')
117             ->line('close(fd);')
118             ->line('croak("bind(port=%d) failed: %s", (int)port, strerror(saved_errno));')
119             ->endif
120             ->blank
121             ->if('listen(fd, SOMAXCONN) < 0')
122             ->line('int saved_errno = errno;')
123             ->line('close(fd);')
124             ->line('croak("listen() failed: %s", strerror(saved_errno));')
125             ->endif
126             ->blank
127             ->line('ST(0) = sv_2mortal(newSViv(fd));')
128             ->xs_return('1')
129             ->xs_end;
130              
131             # Event loop functions (create_event_loop, event_add, event_del, ev_poll)
132             # have been moved to Hypersonic::Event::* backend modules
133              
134             # Generate http_accept
135 41         1063 $builder->xs_function('jit_http_accept')
136             ->xs_preamble
137             ->line('if (items != 1) croak("Usage: http_accept(listen_fd)");')
138             ->line('IV listen_fd = SvIV(ST(0));')
139             ->blank
140             ->line('struct sockaddr_in client_addr;')
141             ->line('socklen_t client_len = sizeof(client_addr);')
142             ->blank
143             ->line('int client_fd = accept((int)listen_fd, (struct sockaddr*)&client_addr, &client_len);')
144             ->blank
145             ->if('client_fd < 0')
146             ->line('ST(0) = sv_2mortal(newSViv(-1));')
147             ->line('XSRETURN(1);')
148             ->endif
149             ->blank
150             ->line('int flags = fcntl(client_fd, F_GETFL, 0);')
151             ->line('fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);')
152             ->blank
153             ->line('ST(0) = sv_2mortal(newSViv(client_fd));')
154             ->xs_return('1')
155             ->xs_end;
156              
157             # Generate http_recv - zero-copy HTTP parsing
158 41         3826 $builder->xs_function('jit_http_recv')
159             ->xs_preamble
160             ->line('if (items != 1) croak("Usage: http_recv(fd)");')
161             ->line('IV fd = SvIV(ST(0));')
162             ->blank
163             ->line('ssize_t len = recv((int)fd, recv_buf, RECV_BUF_SIZE - 1, 0);')
164             ->blank
165             ->if('len <= 0')
166             ->line('ST(0) = &PL_sv_undef;')
167             ->line('XSRETURN(1);')
168             ->endif
169             ->blank
170             ->line('recv_buf[len] = \'\\0\';')
171             ->blank
172             ->comment('Quick parse - extract method, path, detect keep-alive')
173             ->line('const char* p = recv_buf;')
174             ->line('const char* end = recv_buf + len;')
175             ->blank
176             ->comment('Method')
177             ->line('const char* method = p;')
178             ->line('while (p < end && *p != \' \') p++;')
179             ->line('int method_len = p - method;')
180             ->if('p >= end')
181             ->line('ST(0) = &PL_sv_undef;')
182             ->line('XSRETURN(1);')
183             ->endif
184             ->line('p++;')
185             ->blank
186             ->comment('Path')
187             ->line('const char* path = p;')
188             ->line('while (p < end && *p != \' \' && *p != \'?\') p++;')
189             ->line('int path_len = p - path;')
190             ->if('p >= end')
191             ->line('ST(0) = &PL_sv_undef;')
192             ->line('XSRETURN(1);')
193             ->endif
194             ->blank
195             ->comment('Skip to end of request line')
196             ->line('while (p < end && *p != \'\\n\') p++;')
197             ->if('p >= end')
198             ->line('ST(0) = &PL_sv_undef;')
199             ->line('XSRETURN(1);')
200             ->endif
201             ->line('p++;')
202             ->blank
203             ->comment('Check for Connection: close')
204             ->line('int keep_alive = 1;')
205             ->line('while (p < end) {')
206             ->line(' if (*p == \'\\r\' || *p == \'\\n\') break;')
207             ->line(' if (end - p > 17 && strncasecmp(p, "Connection: close", 17) == 0) {')
208             ->line(' keep_alive = 0;')
209             ->line(' }')
210             ->line(' while (p < end && *p != \'\\n\') p++;')
211             ->line(' if (p < end) p++;')
212             ->line('}')
213             ->blank
214             ->comment('Skip blank line')
215             ->line('if (p < end && *p == \'\\r\') p++;')
216             ->line('if (p < end && *p == \'\\n\') p++;')
217             ->blank
218             ->comment('Body')
219             ->line('const char* body = p;')
220             ->line('int body_len = end - p;')
221             ->blank
222             ->comment('Build request array: [method, path, body, keep_alive, fd]')
223             ->line('AV* req = newAV();')
224             ->line('av_push(req, newSVpvn(method, method_len));')
225             ->line('av_push(req, newSVpvn(path, path_len));')
226             ->line('av_push(req, newSVpvn(body, body_len));')
227             ->line('av_push(req, newSViv(keep_alive));')
228             ->line('av_push(req, newSViv(fd));')
229             ->blank
230             ->line('ST(0) = sv_2mortal(newRV_noinc((SV*)req));')
231             ->xs_return('1')
232             ->xs_end;
233              
234             # Generate http_send - writev for zero-copy
235 41         951 $builder->xs_function('jit_http_send')
236             ->xs_preamble
237             ->line('if (items < 2 || items > 3) croak("Usage: http_send(fd, body, [content_type])");')
238             ->line('IV fd = SvIV(ST(0));')
239             ->blank
240             ->line('STRLEN body_len;')
241             ->line('const char* body = SvPV(ST(1), body_len);')
242             ->blank
243             ->line('const char* content_type = "text/plain";')
244             ->if('items == 3 && SvOK(ST(2))')
245             ->line('STRLEN ct_len;')
246             ->line('content_type = SvPV(ST(2), ct_len);')
247             ->endif
248             ->blank
249             ->line('static __thread char header[512];')
250             ->line('int hdr_len = snprintf(header, sizeof(header),')
251             ->line(' "HTTP/1.1 200 OK\\r\\n"')
252             ->line(' "Content-Type: %s\\r\\n"')
253             ->line(' "Content-Length: %zu\\r\\n"')
254             ->line(' "Connection: keep-alive\\r\\n\\r\\n",')
255             ->line(' content_type, body_len);')
256             ->blank
257             ->line('struct iovec iov[2];')
258             ->line('iov[0].iov_base = header;')
259             ->line('iov[0].iov_len = (size_t)hdr_len;')
260             ->line('iov[1].iov_base = (void*)body;')
261             ->line('iov[1].iov_len = body_len;')
262             ->blank
263             ->line('ssize_t sent = writev((int)fd, iov, 2);')
264             ->line('ST(0) = sv_2mortal(newSViv((IV)sent));')
265             ->xs_return('1')
266             ->xs_end;
267              
268             # Generate http_send_404
269 41         776 $builder->xs_function('jit_http_send_404')
270             ->xs_preamble
271             ->line('if (items != 1) croak("Usage: http_send_404(fd)");')
272             ->line('IV fd = SvIV(ST(0));')
273             ->blank
274             ->line('static const char resp[] =')
275             ->line(' "HTTP/1.1 404 Not Found\\r\\n"')
276             ->line(' "Content-Type: text/plain\\r\\n"')
277             ->line(' "Content-Length: 9\\r\\n"')
278             ->line(' "Connection: close\\r\\n\\r\\n"')
279             ->line(' "Not Found";')
280             ->blank
281             ->line('ssize_t sent = send((int)fd, resp, sizeof(resp) - 1, 0);')
282             ->line('ST(0) = sv_2mortal(newSViv((IV)sent));')
283             ->xs_return('1')
284             ->xs_end;
285              
286             # Generate close_fd
287 41         286 $builder->xs_function('jit_close_fd')
288             ->xs_preamble
289             ->line('if (items != 1) croak("Usage: close_fd(fd)");')
290             ->line('IV fd = SvIV(ST(0));')
291             ->line('int result = close((int)fd);')
292             ->line('ST(0) = sv_2mortal(newSViv(result));')
293             ->xs_return('1')
294             ->xs_end;
295              
296             # Compile via XS::JIT (socket-only functions - event loop is in backends)
297 41 50       1850031 XS::JIT->compile(
298             code => $builder->code,
299             name => $module_name,
300             cache_dir => $cache_dir,
301             # Windows needs to link against Winsock for the JIT-compiled .so
302             # to resolve socket()/recv()/send()/etc.
303             ($^O eq 'MSWin32' ? (extra_ldflags => '-lws2_32') : ()),
304             functions => {
305             'Hypersonic::Socket::create_listen_socket' => { source => 'jit_create_listen_socket', is_xs_native => 1 },
306             'Hypersonic::Socket::http_accept' => { source => 'jit_http_accept', is_xs_native => 1 },
307             'Hypersonic::Socket::http_recv' => { source => 'jit_http_recv', is_xs_native => 1 },
308             'Hypersonic::Socket::http_send' => { source => 'jit_http_send', is_xs_native => 1 },
309             'Hypersonic::Socket::http_send_404' => { source => 'jit_http_send_404', is_xs_native => 1 },
310             'Hypersonic::Socket::close_fd' => { source => 'jit_close_fd', is_xs_native => 1 },
311             },
312             );
313              
314 41         357 $COMPILED = 1;
315 41         399259 return 1;
316             }
317              
318             # Auto-compile on import
319             sub import {
320 42     42   1315 my $class = shift;
321 42         89 my %opts = @_;
322 42         149 $class->compile_socket_ops(%opts);
323             }
324              
325             1;
326              
327             __END__