File Coverage

Feersum.xs
Criterion Covered Total %
statement 335 378 88.6
branch 179 248 72.1
condition n/a
subroutine n/a
pod n/a
total 514 626 82.1


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