File Coverage

Feersum.xs
Criterion Covered Total %
statement 337 378 89.1
branch 181 248 72.9
condition n/a
subroutine n/a
pod n/a
total 518 626 82.7


line stmt bran cond sub pod time code
1             #include "feersum_core.h"
2             #include "picohttpparser-git/picohttpparser.c"
3              
4             #include "rinq.c"
5              
6             #include "feersum_core.c.inc"
7             #include "feersum_utils.c.inc"
8             #include "feersum_h1.c.inc"
9             #include "feersum_psgi.c.inc"
10              
11             #ifdef FEERSUM_HAS_TLS
12             #include "feersum_tls.c.inc"
13             #endif
14             #ifdef FEERSUM_HAS_H2
15             #include "feersum_h2.c.inc"
16             #endif
17              
18             MODULE = Feersum PACKAGE = Feersum
19              
20             PROTOTYPES: ENABLE
21              
22             SV *
23             _xs_new_server(SV *classname)
24             CODE:
25             {
26             PERL_UNUSED_VAR(classname);
27 79           struct feer_server *s = new_feer_server(aTHX);
28 79           RETVAL = feer_server_2sv(s);
29             }
30             OUTPUT:
31             RETVAL
32              
33             SV *
34             _xs_default_server(SV *classname)
35             CODE:
36             {
37             PERL_UNUSED_VAR(classname);
38 128           RETVAL = feer_server_2sv(default_server);
39             }
40             OUTPUT:
41             RETVAL
42              
43             void
44             set_server_name_and_port(struct feer_server *server, SV *name, SV *port)
45             PPCODE:
46             {
47 225           struct feer_listen *lsnr = &server->listeners[server->n_listeners > 0 ? server->n_listeners - 1 : 0];
48 225           SvREFCNT_dec(lsnr->server_name);
49 225           lsnr->server_name = newSVsv(name);
50 225           SvREADONLY_on(lsnr->server_name);
51              
52 225           SvREFCNT_dec(lsnr->server_port);
53 225           lsnr->server_port = newSVsv(port);
54 225           SvREADONLY_on(lsnr->server_port);
55             }
56              
57             void
58             accept_on_fd(struct feer_server *server, int fd)
59             PPCODE:
60             {
61             struct sockaddr_storage addr;
62 224           socklen_t addr_len = sizeof(addr);
63             struct feer_listen *lsnr;
64              
65 224 100         if (server->n_listeners == 0) {
66 173           lsnr = &server->listeners[0];
67 173           server->n_listeners = 1;
68             } else {
69 51           lsnr = NULL;
70 192 100         for (int j = 0; j < server->n_listeners; j++) {
71 141 50         if (server->listeners[j].fd == -1) {
72 0           lsnr = &server->listeners[j];
73             #ifdef FEERSUM_HAS_TLS
74 0           feer_tls_cleanup_listener(lsnr);
75             #endif
76 0           break;
77             }
78             }
79 51 50         if (!lsnr) {
80 51 50         if (server->n_listeners < FEER_MAX_LISTENERS) {
81 51           lsnr = &server->listeners[server->n_listeners];
82 51           Zero(lsnr, 1, struct feer_listen);
83 51           lsnr->server = server;
84 51           lsnr->fd = -1;
85 51           lsnr->is_tcp = 1;
86             #ifdef __linux__
87 51           lsnr->epoll_fd = -1;
88             #endif
89 51           server->n_listeners++;
90             } else {
91 0           croak("Too many listeners (max %d)", FEER_MAX_LISTENERS);
92             }
93             }
94             }
95              
96             // Zero addr to ensure safe defaults if getsockname fails
97 224           Zero(&addr, 1, struct sockaddr_storage);
98 224 50         if (getsockname(fd, (struct sockaddr*)&addr, &addr_len) == -1) {
99             // Log error but continue with safe default (AF_INET assumed)
100             // This allows the server to function even if getsockname fails
101 0           warn("getsockname failed: %s (assuming TCP socket)", strerror(errno));
102 0           addr.ss_family = AF_INET;
103             }
104 224           switch (addr.ss_family) {
105 224           case AF_INET:
106             case AF_INET6:
107 224           lsnr->is_tcp = 1;
108             #ifdef TCP_DEFER_ACCEPT
109             trace("going to defer accept on %d\n",fd);
110 224 50         if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &(int){1}, sizeof(int)) < 0)
111 0           trouble("setsockopt TCP_DEFER_ACCEPT fd=%d: %s\n", fd, strerror(errno));
112             #endif
113 224           break;
114             #ifdef AF_UNIX
115 0           case AF_UNIX:
116 0           lsnr->is_tcp = 0;
117 0           break;
118             #endif
119             }
120              
121             trace("going to accept on %d\n",fd);
122 224           feersum_ev_loop = EV_DEFAULT;
123 224           lsnr->fd = fd;
124              
125             // Only init per-server watchers once (on first listener)
126 224 100         if (!server->watchers_initialized) {
127 173           server->watchers_initialized = true;
128              
129 173           ev_prepare_init(&server->ep, prepare_cb);
130 173           server->ep.data = (void *)server;
131 173           ev_prepare_start(feersum_ev_loop, &server->ep);
132              
133 173           ev_check_init(&server->ec, check_cb);
134 173           server->ec.data = (void *)server;
135 173           ev_check_start(feersum_ev_loop, &server->ec);
136              
137 173           ev_idle_init(&server->ei, idle_cb);
138 173           server->ei.data = (void *)server;
139              
140 173           date_timer_refs++;
141 51 100         } else if (!ev_is_active(&server->ep)) {
142             // Re-arm prepare watcher for runtime listener addition
143 29           ev_prepare_start(feersum_ev_loop, &server->ep);
144             }
145              
146             // Initialize date header and start periodic timer (1 second interval)
147             // Shared across all servers - only start once
148 224 100         if (!ev_is_active(&date_timer)) {
149 128           date_timer_cb(feersum_ev_loop, &date_timer, 0); // initial update
150 128           ev_timer_init(&date_timer, date_timer_cb, 1.0, 1.0);
151 128           ev_timer_start(feersum_ev_loop, &date_timer);
152             }
153              
154 224           setup_accept_watcher(lsnr, fd);
155             }
156              
157             void
158             unlisten (struct feer_server *server)
159             PPCODE:
160             {
161             trace("stopping accept\n");
162 20           ev_prepare_stop(feersum_ev_loop, &server->ep);
163 20           ev_check_stop(feersum_ev_loop, &server->ec);
164 20           ev_idle_stop(feersum_ev_loop, &server->ei);
165 40 100         for (int i = 0; i < server->n_listeners; i++) {
166 20           struct feer_listen *lsnr = &server->listeners[i];
167 20           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
168 20           ev_timer_stop(feersum_ev_loop, &lsnr->emfile_w);
169             #ifdef __linux__
170 20 100         if (lsnr->epoll_fd >= 0) {
171 2 50         if (unlikely(close(lsnr->epoll_fd) < 0))
172 0           trouble("close(epoll_fd) fd=%d: %s\n", lsnr->epoll_fd, strerror(errno));
173 2           lsnr->epoll_fd = -1;
174             }
175             #endif
176 20           lsnr->fd = -1;
177 20           lsnr->pause_flags = 0;
178             #ifdef FEERSUM_HAS_TLS
179 20           feer_tls_cleanup_listener(lsnr);
180             #endif
181 20           SvREFCNT_dec(lsnr->server_name);
182 20           lsnr->server_name = NULL;
183 20           SvREFCNT_dec(lsnr->server_port);
184 20           lsnr->server_port = NULL;
185             }
186 20           server->n_listeners = 0;
187 20 100         if (server->watchers_initialized) {
188 16           server->watchers_initialized = false;
189 16 100         if (--date_timer_refs <= 0) {
190 13           ev_timer_stop(feersum_ev_loop, &date_timer);
191 13           date_timer_refs = 0;
192             }
193             }
194             }
195              
196             void
197             pause_accept (struct feer_server *server)
198             PPCODE:
199             {
200 2 50         if (server->shutting_down) {
201             trace("cannot pause during shutdown\n");
202 0           XSRETURN_NO;
203             }
204 2           int paused_any = 0;
205 4 100         for (int i = 0; i < server->n_listeners; i++) {
206 2           struct feer_listen *lsnr = &server->listeners[i];
207 2 100         if (!(lsnr->pause_flags & FEER_PAUSE_USER)) {
208             trace("pausing accept on listener %d\n", i);
209 1 50         if (ev_is_active(&lsnr->accept_w))
210 1           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
211 1           lsnr->pause_flags |= FEER_PAUSE_USER;
212 1           paused_any = 1;
213             }
214             }
215 2 100         if (paused_any)
216 1           XSRETURN_YES;
217             else
218 1           XSRETURN_NO;
219             }
220              
221             void
222             resume_accept (struct feer_server *server)
223             PPCODE:
224             {
225 2 50         if (server->shutting_down) {
226             trace("cannot resume during shutdown\n");
227 0           XSRETURN_NO;
228             }
229 2           int resumed_any = 0;
230 4 100         for (int i = 0; i < server->n_listeners; i++) {
231 2           struct feer_listen *lsnr = &server->listeners[i];
232 2 100         if (lsnr->pause_flags & FEER_PAUSE_USER) {
233             trace("resuming accept on listener %d\n", i);
234 1           lsnr->pause_flags &= ~FEER_PAUSE_USER;
235             /* Only restart if no other reason still wants us paused */
236 1 50         if (!lsnr->pause_flags && lsnr->fd >= 0)
    50          
237 1           ev_io_start(feersum_ev_loop, &lsnr->accept_w);
238 1           resumed_any = 1;
239             }
240             }
241 2 100         if (resumed_any)
242 1           XSRETURN_YES;
243             else
244 1           XSRETURN_NO;
245             }
246              
247             bool
248             accept_is_paused (struct feer_server *server)
249             CODE:
250             {
251 3           RETVAL = (server->n_listeners > 0);
252 4 100         for (int i = 0; i < server->n_listeners; i++) {
253 3 100         if (!(server->listeners[i].pause_flags & FEER_PAUSE_USER)) {
254 2           RETVAL = 0; break;
255             }
256             }
257             }
258             OUTPUT:
259             RETVAL
260              
261             void
262             request_handler(struct feer_server *server, SV *cb)
263             PROTOTYPE: $&
264             ALIAS:
265             psgi_request_handler = 1
266             PPCODE:
267             {
268 246 50         if (unlikely(!SvOK(cb) || !SvROK(cb)))
    50          
269 0           croak("can't supply an undef handler");
270 246           SvREFCNT_dec(server->request_cb_cv);
271 246           server->request_cb_cv = newSVsv(cb);
272 246           server->request_cb_is_psgi = ix;
273             trace("assigned %s request handler %p\n",
274             ix ? "PSGI" : "Feersum", server->request_cb_cv);
275             }
276              
277             void
278             graceful_shutdown (struct feer_server *server, SV *cb)
279             PROTOTYPE: $&
280             PPCODE:
281             {
282 9 50         if (!IsCodeRef(cb))
    50          
283 0           croak("must supply a code reference");
284 9 100         if (unlikely(server->shutting_down))
285 1           croak("already shutting down");
286 8           server->shutdown_cb_cv = newSVsv(cb);
287             trace("shutting down, handler=%p, active=%d\n", SvRV(cb), server->active_conns);
288              
289 8           server->shutting_down = 1;
290 16 100         for (int i = 0; i < server->n_listeners; i++) {
291 8           struct feer_listen *lsnr = &server->listeners[i];
292 8           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
293 8           ev_timer_stop(feersum_ev_loop, &lsnr->emfile_w);
294             #ifdef __linux__
295 8 50         if (lsnr->epoll_fd >= 0) {
296 0 0         if (unlikely(close(lsnr->epoll_fd) < 0))
297 0           trouble("close(epoll_fd) fd=%d: %s\n", lsnr->epoll_fd, strerror(errno));
298 0           lsnr->epoll_fd = -1;
299             // In epoll_exclusive mode, accept_w.fd is the epoll fd (now closed)
300             // We still need to close the actual listen socket
301 0 0         if (lsnr->fd >= 0) {
302 0 0         if (unlikely(close(lsnr->fd) < 0))
303 0           trouble("close(listen fd) fd=%d: %s\n", lsnr->fd, strerror(errno));
304 0           lsnr->fd = -1;
305             }
306             } else
307             #endif
308             {
309 8 50         if (lsnr->accept_w.fd >= 0) {
310 8 50         if (unlikely(close(lsnr->accept_w.fd) < 0))
311 0           trouble("close(accept_w.fd) fd=%d: %s\n", lsnr->accept_w.fd, strerror(errno));
312 8           ev_io_set(&lsnr->accept_w, -1, EV_READ);
313 8           lsnr->fd = -1;
314             }
315             }
316             #ifdef FEERSUM_HAS_TLS
317 8           feer_tls_cleanup_listener(lsnr);
318             #endif
319 8           lsnr->pause_flags = 0; /* slot is free; clear stale pause state */
320             }
321              
322             /* Close idle keepalive connections - they won't get new requests */
323 8 50         while (feer_server_recycle_idle_conn(server))
324             ;
325              
326 8 100         if (server->active_conns <= 0 && server->shutdown_cb_cv) {
    50          
327             trace("shutdown is immediate\n");
328 7           invoke_shutdown_cb(aTHX_ server);
329             }
330             }
331              
332             double
333             read_timeout (struct feer_server *server, ...)
334             PROTOTYPE: $;$
335             CODE:
336             {
337 8032 100         if (items > 1) {
338 6020           double val = SvNV(ST(1));
339 6020 100         if (!(val > 0.0))
340 3           croak("must set a positive (non-zero) value for the timeout");
341             trace("set read_timeout %f\n", val);
342 6017           server->read_timeout = val;
343             }
344 8029 100         RETVAL = server->read_timeout;
345             }
346             OUTPUT:
347             RETVAL
348              
349             double
350             header_timeout (struct feer_server *server, ...)
351             PROTOTYPE: $;$
352             CODE:
353             {
354 19 100         if (items > 1) {
355 16           double val = SvNV(ST(1));
356 16 100         if (val < 0.0)
357 1           croak("header_timeout must be non-negative (0 to disable)");
358             trace("set header_timeout %f\n", val);
359 15           server->header_timeout = val;
360             }
361 18 50         RETVAL = server->header_timeout;
362             }
363             OUTPUT:
364             RETVAL
365              
366             double
367             write_timeout (struct feer_server *server, ...)
368             PROTOTYPE: $;$
369             CODE:
370             {
371 7 100         if (items > 1) {
372 4           double val = SvNV(ST(1));
373 4 100         if (val < 0.0)
374 1           croak("write_timeout must be non-negative (0 to disable)");
375             trace("set write_timeout %f\n", val);
376 3           server->write_timeout = val;
377             }
378 6 50         RETVAL = server->write_timeout;
379             }
380             OUTPUT:
381             RETVAL
382              
383             void
384             set_keepalive (struct feer_server *server, SV *set)
385             PPCODE:
386             {
387             trace("set keepalive %d\n", SvTRUE(set));
388 76           server->is_keepalive = SvTRUE(set);
389             }
390              
391             void
392             set_reverse_proxy (struct feer_server *server, SV *set)
393             PPCODE:
394             {
395             trace("set reverse_proxy %d\n", SvTRUE(set));
396 13           server->use_reverse_proxy = SvTRUE(set);
397             }
398              
399             int
400             get_reverse_proxy (struct feer_server *server)
401             CODE:
402             {
403 4 50         RETVAL = server->use_reverse_proxy;
404             }
405             OUTPUT:
406             RETVAL
407              
408             void
409             set_psgix_io (struct feer_server *server, SV *set)
410             PPCODE:
411             {
412 2           server->psgix_io = SvTRUE(set);
413             trace("set psgix_io %d\n", server->psgix_io);
414             }
415              
416             int
417             get_psgix_io (struct feer_server *server)
418             CODE:
419             {
420 2 50         RETVAL = server->psgix_io;
421             }
422             OUTPUT:
423             RETVAL
424              
425             void
426             set_proxy_protocol (struct feer_server *server, SV *set)
427             PPCODE:
428             {
429             trace("set proxy_protocol %d\n", SvTRUE(set));
430 50           server->use_proxy_protocol = SvTRUE(set);
431             }
432              
433             int
434             get_proxy_protocol (struct feer_server *server)
435             CODE:
436             {
437 4 50         RETVAL = server->use_proxy_protocol;
438             }
439             OUTPUT:
440             RETVAL
441              
442             void
443             set_epoll_exclusive (struct feer_server *server, SV *set)
444             PPCODE:
445             {
446             #if defined(__linux__) && defined(EPOLLEXCLUSIVE)
447             trace("set epoll_exclusive %d (native mode)\n", SvTRUE(set));
448 14           server->use_epoll_exclusive = SvTRUE(set) ? 1 : 0;
449             #else
450             PERL_UNUSED_VAR(server);
451             if (SvTRUE(set))
452             warn("EPOLLEXCLUSIVE is not available (requires Linux 4.5+)");
453             #endif
454             }
455              
456             int
457             get_epoll_exclusive (struct feer_server *server)
458             CODE:
459             {
460             #if defined(__linux__) && defined(EPOLLEXCLUSIVE)
461 10 50         RETVAL = server->use_epoll_exclusive ? 1 : 0;
462             #else
463             PERL_UNUSED_VAR(server);
464             RETVAL = 0;
465             #endif
466             }
467             OUTPUT:
468             RETVAL
469              
470             int
471             read_priority (struct feer_server *server, ...)
472             ALIAS:
473             write_priority = 1
474             accept_priority = 2
475             PROTOTYPE: $;$
476             CODE:
477             {
478             static const char *names[] = {"read", "write", "accept"};
479             PERL_UNUSED_VAR(names); /* only consumed by trace() */
480 18020           int *field = ix == 2 ? &server->accept_priority
481 30036 100         : ix == 1 ? &server->write_priority
482 12016 100         : &server->read_priority;
483 18020 100         if (items > 1) {
484 18012           int new_priority = SvIV(ST(1));
485 18012 100         if (new_priority < EV_MINPRI) new_priority = EV_MINPRI;
486 18012 100         if (new_priority > EV_MAXPRI) new_priority = EV_MAXPRI;
487             trace("set %s_priority %d\n", names[ix], new_priority);
488 18012           *field = new_priority;
489             }
490 18020 100         RETVAL = *field;
491             }
492             OUTPUT:
493             RETVAL
494              
495             int
496             max_accept_per_loop (struct feer_server *server, ...)
497             PROTOTYPE: $;$
498             CODE:
499             {
500 14 100         if (items > 1) {
501 6           int new_max = SvIV(ST(1));
502 6 100         if (new_max < 1) new_max = 1;
503             trace("set max_accept_per_loop %d\n", new_max);
504 6           server->max_accept_per_loop = new_max;
505             }
506 14 50         RETVAL = server->max_accept_per_loop;
507             }
508             OUTPUT:
509             RETVAL
510              
511             int
512             active_conns (struct feer_server *server)
513             CODE:
514 11 100         RETVAL = server->active_conns;
515             OUTPUT:
516             RETVAL
517              
518             int
519             max_connections (struct feer_server *server, ...)
520             PROTOTYPE: $;$
521             CODE:
522             {
523 19 100         if (items > 1) {
524 12           int new_max = SvIV(ST(1));
525 12 50         if (new_max < 0) new_max = 0; // 0 means unlimited
526             trace("set max_connections %d\n", new_max);
527 12           server->max_connections = new_max;
528             }
529 19 50         RETVAL = server->max_connections;
530             }
531             OUTPUT:
532             RETVAL
533              
534             size_t
535             max_read_buf (struct feer_server *server, ...)
536             PROTOTYPE: $;$
537             CODE:
538             {
539 0 0         if (items > 1) {
540 0           size_t new_max = SvUV(ST(1));
541 0 0         if (new_max == 0) new_max = MAX_READ_BUF;
542 0           server->max_read_buf = new_max;
543             }
544 0 0         RETVAL = server->max_read_buf;
545             }
546             OUTPUT:
547             RETVAL
548              
549             size_t
550             max_body_len (struct feer_server *server, ...)
551             PROTOTYPE: $;$
552             CODE:
553             {
554 2 50         if (items > 1) {
555 2           size_t new_max = SvUV(ST(1));
556 2 100         if (new_max == 0) new_max = MAX_BODY_LEN;
557 2           server->max_body_len = new_max;
558             }
559 2 50         RETVAL = server->max_body_len;
560             }
561             OUTPUT:
562             RETVAL
563              
564             size_t
565             max_uri_len (struct feer_server *server, ...)
566             PROTOTYPE: $;$
567             CODE:
568             {
569 7 100         if (items > 1) {
570 4           size_t new_max = SvUV(ST(1));
571 4 100         if (new_max == 0) new_max = MAX_URI_LEN;
572 4           server->max_uri_len = new_max;
573             }
574 7 50         RETVAL = server->max_uri_len;
575             }
576             OUTPUT:
577             RETVAL
578              
579             size_t
580             wbuf_low_water (struct feer_server *server, ...)
581             PROTOTYPE: $;$
582             CODE:
583             {
584 10 100         if (items > 1) {
585 7           SV *val = ST(1);
586 7 100         if (SvNV(val) < 0.0)
587 1           croak("wbuf_low_water must be non-negative");
588 6           server->wbuf_low_water = SvUV(val);
589             }
590 9 50         RETVAL = server->wbuf_low_water;
591             }
592             OUTPUT:
593             RETVAL
594              
595             void
596             set_multiprocess (struct feer_server *server, SV *set)
597             PPCODE:
598             {
599 3           server->multiprocess = SvTRUE(set);
600             }
601              
602             int
603             max_h2_concurrent_streams (struct feer_server *server, ...)
604             PROTOTYPE: $;$
605             CODE:
606             {
607             #ifdef FEERSUM_HAS_H2
608             if (items > 1) {
609             int n = SvIV(ST(1));
610             if (n < 1) n = 1;
611             /* Capped at FEER_H2_MAX_CONCURRENT_STREAMS because the poll-callback
612             * scan in h2_check_stream_poll_cbs uses a fixed-size stack array. */
613             if (n > FEER_H2_MAX_CONCURRENT_STREAMS) n = FEER_H2_MAX_CONCURRENT_STREAMS;
614             server->max_h2_concurrent_streams = n;
615             }
616             RETVAL = server->max_h2_concurrent_streams;
617             #else
618             PERL_UNUSED_VAR(server);
619 0 0         if (items > 1)
620 0           warn("H2 not compiled in, max_h2_concurrent_streams ignored");
621 0 0         RETVAL = 0;
622             #endif
623             }
624             OUTPUT:
625             RETVAL
626              
627             UV
628             total_requests (struct feer_server *server)
629             CODE:
630 3 50         RETVAL = server->total_requests;
631             OUTPUT:
632             RETVAL
633              
634             unsigned int
635             max_connection_reqs (struct feer_server *server, ...)
636             PROTOTYPE: $;$
637             CODE:
638             {
639 1 50         if (items > 1) {
640 1           IV n = SvIV(ST(1));
641 1 50         if (n < 0)
642 0           croak("must set a non-negative value (0 for unlimited)");
643             trace("set max requests per connection %u\n", (unsigned int)n);
644 1           server->max_connection_reqs = (unsigned int)n;
645             }
646 1 50         RETVAL = server->max_connection_reqs;
647             }
648             OUTPUT:
649             RETVAL
650              
651             void
652             _xs_destroy (struct feer_server *server)
653             PPCODE:
654             {
655             trace3("DESTROY server\n");
656             /* Stop accept watchers to prevent use-after-free if server is GC'd
657             * without unlisten() or graceful_shutdown() being called first. */
658 220           ev_prepare_stop(feersum_ev_loop, &server->ep);
659 220           ev_check_stop(feersum_ev_loop, &server->ec);
660 220           ev_idle_stop(feersum_ev_loop, &server->ei);
661 220           SvREFCNT_dec(server->request_cb_cv);
662 220           SvREFCNT_dec(server->shutdown_cb_cv);
663 424 100         for (int i = 0; i < server->n_listeners; i++) {
664 204           struct feer_listen *lsnr = &server->listeners[i];
665 204           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
666 204           ev_timer_stop(feersum_ev_loop, &lsnr->emfile_w);
667             #ifdef __linux__
668 204 50         if (lsnr->epoll_fd >= 0) {
669 0           close(lsnr->epoll_fd);
670 0           lsnr->epoll_fd = -1;
671             }
672             #endif
673 204           SvREFCNT_dec(lsnr->server_name);
674 204           SvREFCNT_dec(lsnr->server_port);
675             #ifdef FEERSUM_HAS_TLS
676 204           feer_tls_cleanup_listener(lsnr);
677             #endif
678             }
679 220 100         if (server->watchers_initialized && --date_timer_refs <= 0) {
    100          
680 115           ev_timer_stop(feersum_ev_loop, &date_timer);
681 115           date_timer_refs = 0;
682             }
683             }
684              
685             void
686             set_tls (struct feer_server *server, ...)
687             PPCODE:
688             {
689             #ifdef FEERSUM_HAS_TLS
690 172           const char *cert_file = NULL;
691 172           const char *key_file = NULL;
692 172           const char *sni_name = NULL;
693 172           int listener_idx = -1; /* -1 means last-added listener (default) */
694 172           int h2 = 0;
695             int i;
696              
697 172 100         if (items < 3 || (items - 1) % 2 != 0)
    50          
698 1           croak("set_tls requires key => value pairs (cert_file => $path, key_file => $path)");
699              
700 581 100         for (i = 1; i < items; i += 2) {
701 412           const char *key = SvPV_nolen(ST(i));
702 412           SV *val = ST(i + 1);
703 412 100         if (strcmp(key, "cert_file") == 0)
704 168           cert_file = SvPV_nolen(val);
705 244 100         else if (strcmp(key, "key_file") == 0)
706 168           key_file = SvPV_nolen(val);
707 76 100         else if (strcmp(key, "listener") == 0)
708 6           listener_idx = SvIV(val);
709 70 100         else if (strcmp(key, "h2") == 0)
710 1           h2 = SvTRUE(val) ? 1 : 0;
711 69 100         else if (strcmp(key, "sni") == 0)
712 67           sni_name = SvPV_nolen(val);
713             else
714 2           croak("set_tls: unknown option '%s'", key);
715             }
716              
717 169 100         if (!cert_file) croak("set_tls: cert_file is required");
718 166 100         if (!key_file) croak("set_tls: key_file is required");
719              
720 163 100         if (server->n_listeners == 0)
721 3           croak("set_tls: no listeners configured (call use_socket/accept_on_fd first)");
722              
723             /* Resolve listener index */
724 160 50         if (listener_idx < -1)
725 0           croak("set_tls: listener index %d out of range (0..%d or -1)",
726             listener_idx, server->n_listeners - 1);
727 160 100         if (listener_idx < 0)
728 154           listener_idx = server->n_listeners - 1;
729 160 100         if (listener_idx >= server->n_listeners)
730 2           croak("set_tls: listener index %d out of range (0..%d)",
731             listener_idx, server->n_listeners - 1);
732              
733 158           struct feer_listen *lsnr = &server->listeners[listener_idx];
734              
735             /* Validate SNI preconditions before creating context (avoids leak on croak) */
736 158           STRLEN sni_name_len = 0;
737 158 100         if (sni_name) {
738 67           sni_name_len = strlen(sni_name);
739 67 50         if (sni_name_len == 0)
740 0           croak("set_tls: SNI hostname must not be empty");
741 67 50         if (sni_name_len >= 256)
742 0           croak("set_tls: SNI hostname too long");
743 67 50         if (!lsnr->tls_ctx_ref)
744 0           croak("set_tls: set a default TLS context before adding SNI entries");
745 67 50         if (lsnr->n_sni_entries >= FEER_MAX_SNI_ENTRIES)
746 0           croak("set_tls: too many SNI entries (max %d)", FEER_MAX_SNI_ENTRIES);
747             /* SNI cert selection happens during the ALPN callback; ALPN is
748             * negotiated by the default context's on_client_hello handler.
749             * Per-SNI h2 flag would have no effect, so reject it to avoid
750             * silent misconfiguration. */
751 67 100         if (h2)
752 1           croak("set_tls: 'h2' option is listener-wide; set it on the "
753             "default certificate, not per-SNI entry");
754             }
755              
756 157           ptls_context_t *new_ctx = feer_tls_create_context(aTHX_ cert_file, key_file, h2);
757 157 100         if (!new_ctx)
758 5           croak("set_tls: failed to create TLS context");
759              
760 152 100         if (sni_name) {
761 66           STRLEN name_len = sni_name_len;
762             char lower[256];
763 790 100         for (i = 0; (size_t)i < name_len; i++)
764 724           lower[i] = ascii_lower[(unsigned char)sni_name[i]];
765 66           lower[name_len] = '\0';
766              
767             /* Replace existing entry for same hostname (live cert rotation),
768             * otherwise append. */
769 66           struct feer_sni_entry *e = NULL;
770 66 100         for (i = 0; i < lsnr->n_sni_entries; i++) {
771 63 50         if (strcmp(lsnr->sni_entries[i].hostname, lower) == 0) {
772 63           e = &lsnr->sni_entries[i];
773 63           feer_tls_ctx_ref_dec(e->ctx_ref);
774 63           break;
775             }
776             }
777 66 100         if (!e) {
778 3           e = &lsnr->sni_entries[lsnr->n_sni_entries++];
779 3           Newx(e->hostname, name_len + 1, char);
780 3           memcpy(e->hostname, lower, name_len + 1);
781             }
782 66           e->ctx_ref = feer_tls_ctx_ref_new(new_ctx);
783              
784             trace("SNI entry '%s' set on listener %d (h2=%d)\n",
785             e->hostname, listener_idx, h2);
786             } else {
787             /* Set/replace default context */
788 86 100         if (lsnr->tls_ctx_ref)
789 1           feer_tls_ctx_ref_dec(lsnr->tls_ctx_ref);
790 86           lsnr->tls_ctx_ref = feer_tls_ctx_ref_new(new_ctx);
791              
792             trace("TLS enabled on listener %d (h2=%d)\n", listener_idx, h2);
793             }
794             #else
795             PERL_UNUSED_VAR(server);
796             croak("set_tls: Feersum was not compiled with TLS support (need picotls submodule + OpenSSL; see Alien::OpenSSL)");
797             #endif
798             }
799              
800             int
801             has_tls (struct feer_server *server)
802             CODE:
803             {
804             PERL_UNUSED_VAR(server);
805             #ifdef FEERSUM_HAS_TLS
806 59 50         RETVAL = 1;
807             #else
808             RETVAL = 0;
809             #endif
810             }
811             OUTPUT:
812             RETVAL
813              
814             int
815             has_h2 (struct feer_server *server)
816             CODE:
817             {
818             PERL_UNUSED_VAR(server);
819             #ifdef FEERSUM_HAS_H2
820             RETVAL = 1;
821             #else
822 17 50         RETVAL = 0;
823             #endif
824             }
825             OUTPUT:
826             RETVAL
827              
828             BOOT:
829             {
830 141           feer_stash = gv_stashpv("Feersum", 1);
831 141           feer_conn_stash = gv_stashpv("Feersum::Connection", 1);
832 141           feer_conn_writer_stash = gv_stashpv("Feersum::Connection::Writer",1);
833 141           feer_conn_reader_stash = gv_stashpv("Feersum::Connection::Reader",1);
834             /* Ignore SIGPIPE once at module load - writes to closed sockets
835             * must return EPIPE to the caller, not terminate the process. */
836 141           signal(SIGPIPE, SIG_IGN);
837 141 50         I_EV_API("Feersum");
    50          
    50          
838              
839 141           const char *env_fl_max = getenv("FEERSUM_FREELIST_MAX");
840 141 50         if (env_fl_max) {
841 0           int n = atoi(env_fl_max);
842 0           FEERSUM_FREELIST_MAX = n < 0 ? 0 : n; /* clamp; 0 disables */
843             }
844              
845             // Allocate default server (backed by a blessed Perl SV)
846 141           default_server = new_feer_server(aTHX);
847             // Keep an extra refcount so the default server is never GC'd
848 141           SvREFCNT_inc_void_NN(default_server->self);
849              
850 141           psgi_ver = newAV();
851 141           av_extend(psgi_ver, 1); // pre-allocate for 2 elements (psgi.version = [1, 1])
852 141           av_push(psgi_ver, newSViv(1));
853 141           av_push(psgi_ver, newSViv(1));
854 141           SvREADONLY_on((SV*)psgi_ver);
855              
856 141           psgi_serv10 = newSVpvs("HTTP/1.0");
857 141           SvREADONLY_on(psgi_serv10);
858 141           psgi_serv11 = newSVpvs("HTTP/1.1");
859 141           SvREADONLY_on(psgi_serv11);
860              
861 141           method_GET = newSVpvs("GET");
862 141           SvREADONLY_on(method_GET);
863 141           method_POST = newSVpvs("POST");
864 141           SvREADONLY_on(method_POST);
865 141           method_HEAD = newSVpvs("HEAD");
866 141           SvREADONLY_on(method_HEAD);
867 141           method_PUT = newSVpvs("PUT");
868 141           SvREADONLY_on(method_PUT);
869 141           method_PATCH = newSVpvs("PATCH");
870 141           SvREADONLY_on(method_PATCH);
871 141           method_DELETE = newSVpvs("DELETE");
872 141           SvREADONLY_on(method_DELETE);
873 141           method_OPTIONS = newSVpvs("OPTIONS");
874 141           SvREADONLY_on(method_OPTIONS);
875              
876 141           status_200 = newSVpvs("200 OK");
877 141           SvREADONLY_on(status_200);
878 141           status_201 = newSVpvs("201 Created");
879 141           SvREADONLY_on(status_201);
880 141           status_204 = newSVpvs("204 No Content");
881 141           SvREADONLY_on(status_204);
882 141           status_301 = newSVpvs("301 Moved Permanently");
883 141           SvREADONLY_on(status_301);
884 141           status_302 = newSVpvs("302 Found");
885 141           SvREADONLY_on(status_302);
886 141           status_304 = newSVpvs("304 Not Modified");
887 141           SvREADONLY_on(status_304);
888 141           status_400 = newSVpvs("400 Bad Request");
889 141           SvREADONLY_on(status_400);
890 141           status_404 = newSVpvs("404 Not Found");
891 141           SvREADONLY_on(status_404);
892 141           status_500 = newSVpvs("500 Internal Server Error");
893 141           SvREADONLY_on(status_500);
894              
895 141           empty_query_sv = newSVpvs("");
896 141           SvREADONLY_on(empty_query_sv);
897              
898 141           Zero(&psgix_io_vtbl, 1, MGVTBL);
899 141           psgix_io_vtbl.svt_get = psgix_io_svt_get;
900 141           newCONSTSUB(feer_stash, "HEADER_NORM_SKIP", newSViv(HEADER_NORM_SKIP));
901 141           newCONSTSUB(feer_stash, "HEADER_NORM_UPCASE", newSViv(HEADER_NORM_UPCASE));
902 141           newCONSTSUB(feer_stash, "HEADER_NORM_LOCASE", newSViv(HEADER_NORM_LOCASE));
903 141           newCONSTSUB(feer_stash, "HEADER_NORM_UPCASE_DASH", newSViv(HEADER_NORM_UPCASE_DASH));
904 141           newCONSTSUB(feer_stash, "HEADER_NORM_LOCASE_DASH", newSViv(HEADER_NORM_LOCASE_DASH));
905              
906             trace3("Feersum booted, iomatrix %lu, FEERSUM_IOMATRIX_SIZE=%u, "
907             "feer_req %lu, feer_conn %lu\n",
908             (long unsigned int)sizeof(struct iomatrix),
909             (unsigned int)FEERSUM_IOMATRIX_SIZE,
910             (long unsigned int)sizeof(struct feer_req),
911             (long unsigned int)sizeof(struct feer_conn)
912             );
913             }
914              
915             INCLUDE: feersum_conn.xs