File Coverage

Feersum.xs
Criterion Covered Total %
statement 328 369 88.8
branch 187 256 73.0
condition n/a
subroutine n/a
pod n/a
total 515 625 82.4


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