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 "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 79           struct feer_server *s = new_feer_server(aTHX);
30 79           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 224           struct feer_listen *lsnr = &server->listeners[server->n_listeners > 0 ? server->n_listeners - 1 : 0];
50 224           SvREFCNT_dec(lsnr->server_name);
51 224           lsnr->server_name = newSVsv(name);
52 224           SvREADONLY_on(lsnr->server_name);
53              
54 224           SvREFCNT_dec(lsnr->server_port);
55 224           lsnr->server_port = newSVsv(port);
56 224           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 223           socklen_t addr_len = sizeof(addr);
65             struct feer_listen *lsnr;
66              
67 223 100         if (server->n_listeners == 0) {
68 172           lsnr = &server->listeners[0];
69 172           server->n_listeners = 1;
70             } else {
71 51           lsnr = NULL;
72 192 100         for (int j = 0; j < server->n_listeners; j++) {
73 141 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 51 50         if (!lsnr) {
82 51 50         if (server->n_listeners < FEER_MAX_LISTENERS) {
83 51           lsnr = &server->listeners[server->n_listeners];
84 51           Zero(lsnr, 1, struct feer_listen);
85 51           lsnr->server = server;
86 51           lsnr->fd = -1;
87 51           lsnr->is_tcp = 1;
88             #ifdef __linux__
89 51           lsnr->epoll_fd = -1;
90             #endif
91 51           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 223           Zero(&addr, 1, struct sockaddr_storage);
100 223 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 223           switch (addr.ss_family) {
107 223           case AF_INET:
108             case AF_INET6:
109 223           lsnr->is_tcp = 1;
110             #ifdef TCP_DEFER_ACCEPT
111             trace("going to defer accept on %d\n",fd);
112 223 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 223           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 223           feersum_ev_loop = EV_DEFAULT;
125 223           lsnr->fd = fd;
126              
127             // Only init per-server watchers once (on first listener)
128 223 100         if (!server->watchers_initialized) {
129 172           server->watchers_initialized = true;
130              
131 172           ev_prepare_init(&server->ep, prepare_cb);
132 172           server->ep.data = (void *)server;
133 172           ev_prepare_start(feersum_ev_loop, &server->ep);
134              
135 172           ev_check_init(&server->ec, check_cb);
136 172           server->ec.data = (void *)server;
137 172           ev_check_start(feersum_ev_loop, &server->ec);
138              
139 172           ev_idle_init(&server->ei, idle_cb);
140 172           server->ei.data = (void *)server;
141              
142 172           date_timer_refs++;
143 51 100         } else if (!ev_is_active(&server->ep)) {
144             // Re-arm prepare watcher for runtime listener addition
145 29           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 223 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 223           setup_accept_watcher(lsnr, fd);
157             }
158              
159             void
160             unlisten (struct feer_server *server)
161             PPCODE:
162             {
163             trace("stopping accept\n");
164 20           ev_prepare_stop(feersum_ev_loop, &server->ep);
165 20           ev_check_stop(feersum_ev_loop, &server->ec);
166 20           ev_idle_stop(feersum_ev_loop, &server->ei);
167 40 100         for (int i = 0; i < server->n_listeners; i++) {
168 20           struct feer_listen *lsnr = &server->listeners[i];
169 20           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
170 20           ev_timer_stop(feersum_ev_loop, &lsnr->emfile_w);
171             #ifdef __linux__
172 20 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 20           lsnr->fd = -1;
179 20           lsnr->pause_flags = 0;
180             #ifdef FEERSUM_HAS_TLS
181 20           feer_tls_cleanup_listener(lsnr);
182             #endif
183 20           SvREFCNT_dec(lsnr->server_name);
184 20           lsnr->server_name = NULL;
185 20           SvREFCNT_dec(lsnr->server_port);
186 20           lsnr->server_port = NULL;
187             }
188 20           server->n_listeners = 0;
189 20 100         if (server->watchers_initialized) {
190 16           server->watchers_initialized = false;
191 16 100         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 245 50         if (unlikely(!SvOK(cb) || !SvROK(cb)))
    50          
271 0           croak("can't supply an undef handler");
272 245           SvREFCNT_dec(server->request_cb_cv);
273 245           server->request_cb_cv = newSVsv(cb);
274 245           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 2           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 2 50         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             PERL_UNUSED_VAR(names); /* only consumed by trace() */
482 18020           int *field = ix == 2 ? &server->accept_priority
483 30036 100         : ix == 1 ? &server->write_priority
484 12016 100         : &server->read_priority;
485 18020 100         if (items > 1) {
486 18012           int new_priority = SvIV(ST(1));
487 18012 100         if (new_priority < EV_MINPRI) new_priority = EV_MINPRI;
488 18012 100         if (new_priority > EV_MAXPRI) new_priority = EV_MAXPRI;
489             trace("set %s_priority %d\n", names[ix], new_priority);
490 18012           *field = new_priority;
491             }
492 18020 100         RETVAL = *field;
493             }
494             OUTPUT:
495             RETVAL
496              
497             int
498             max_accept_per_loop (struct feer_server *server, ...)
499             PROTOTYPE: $;$
500             CODE:
501             {
502 14 100         if (items > 1) {
503 6           int new_max = SvIV(ST(1));
504 6 100         if (new_max < 1) new_max = 1;
505             trace("set max_accept_per_loop %d\n", new_max);
506 6           server->max_accept_per_loop = new_max;
507             }
508 14 50         RETVAL = server->max_accept_per_loop;
509             }
510             OUTPUT:
511             RETVAL
512              
513             int
514             active_conns (struct feer_server *server)
515             CODE:
516 11 100         RETVAL = server->active_conns;
517             OUTPUT:
518             RETVAL
519              
520             int
521             max_connections (struct feer_server *server, ...)
522             PROTOTYPE: $;$
523             CODE:
524             {
525 19 100         if (items > 1) {
526 12           int new_max = SvIV(ST(1));
527 12 50         if (new_max < 0) new_max = 0; // 0 means unlimited
528             trace("set max_connections %d\n", new_max);
529 12           server->max_connections = new_max;
530             }
531 19 50         RETVAL = server->max_connections;
532             }
533             OUTPUT:
534             RETVAL
535              
536             size_t
537             max_read_buf (struct feer_server *server, ...)
538             PROTOTYPE: $;$
539             CODE:
540             {
541 0 0         if (items > 1) {
542 0           size_t new_max = SvUV(ST(1));
543 0 0         if (new_max == 0) new_max = MAX_READ_BUF;
544 0           server->max_read_buf = new_max;
545             }
546 0 0         RETVAL = server->max_read_buf;
547             }
548             OUTPUT:
549             RETVAL
550              
551             size_t
552             max_body_len (struct feer_server *server, ...)
553             PROTOTYPE: $;$
554             CODE:
555             {
556 2 50         if (items > 1) {
557 2           size_t new_max = SvUV(ST(1));
558 2 100         if (new_max == 0) new_max = MAX_BODY_LEN;
559 2           server->max_body_len = new_max;
560             }
561 2 50         RETVAL = server->max_body_len;
562             }
563             OUTPUT:
564             RETVAL
565              
566             size_t
567             max_uri_len (struct feer_server *server, ...)
568             PROTOTYPE: $;$
569             CODE:
570             {
571 7 100         if (items > 1) {
572 4           size_t new_max = SvUV(ST(1));
573 4 100         if (new_max == 0) new_max = MAX_URI_LEN;
574 4           server->max_uri_len = new_max;
575             }
576 7 50         RETVAL = server->max_uri_len;
577             }
578             OUTPUT:
579             RETVAL
580              
581             size_t
582             wbuf_low_water (struct feer_server *server, ...)
583             PROTOTYPE: $;$
584             CODE:
585             {
586 10 100         if (items > 1) {
587 7           SV *val = ST(1);
588 7 100         if (SvNV(val) < 0.0)
589 1           croak("wbuf_low_water must be non-negative");
590 6           server->wbuf_low_water = SvUV(val);
591             }
592 9 50         RETVAL = server->wbuf_low_water;
593             }
594             OUTPUT:
595             RETVAL
596              
597             void
598             set_multiprocess (struct feer_server *server, SV *set)
599             PPCODE:
600             {
601 3           server->multiprocess = SvTRUE(set);
602             }
603              
604             int
605             max_h2_concurrent_streams (struct feer_server *server, ...)
606             PROTOTYPE: $;$
607             CODE:
608             {
609             #ifdef FEERSUM_HAS_H2
610             if (items > 1) {
611             int n = SvIV(ST(1));
612             if (n < 1) n = 1;
613             /* Capped at FEER_H2_MAX_CONCURRENT_STREAMS because the poll-callback
614             * scan in h2_check_stream_poll_cbs uses a fixed-size stack array. */
615             if (n > FEER_H2_MAX_CONCURRENT_STREAMS) n = FEER_H2_MAX_CONCURRENT_STREAMS;
616             server->max_h2_concurrent_streams = n;
617             }
618             RETVAL = server->max_h2_concurrent_streams;
619             #else
620             PERL_UNUSED_VAR(server);
621 0 0         if (items > 1)
622 0           warn("H2 not compiled in, max_h2_concurrent_streams ignored");
623 0 0         RETVAL = 0;
624             #endif
625             }
626             OUTPUT:
627             RETVAL
628              
629             UV
630             total_requests (struct feer_server *server)
631             CODE:
632 3 50         RETVAL = server->total_requests;
633             OUTPUT:
634             RETVAL
635              
636             unsigned int
637             max_connection_reqs (struct feer_server *server, ...)
638             PROTOTYPE: $;$
639             CODE:
640             {
641 1 50         if (items > 1) {
642 1           IV n = SvIV(ST(1));
643 1 50         if (n < 0)
644 0           croak("must set a non-negative value (0 for unlimited)");
645             trace("set max requests per connection %u\n", (unsigned int)n);
646 1           server->max_connection_reqs = (unsigned int)n;
647             }
648 1 50         RETVAL = server->max_connection_reqs;
649             }
650             OUTPUT:
651             RETVAL
652              
653             void
654             _xs_destroy (struct feer_server *server)
655             PPCODE:
656             {
657             trace3("DESTROY server\n");
658             /* Stop accept watchers to prevent use-after-free if server is GC'd
659             * without unlisten() or graceful_shutdown() being called first. */
660 219           ev_prepare_stop(feersum_ev_loop, &server->ep);
661 219           ev_check_stop(feersum_ev_loop, &server->ec);
662 219           ev_idle_stop(feersum_ev_loop, &server->ei);
663 219           SvREFCNT_dec(server->request_cb_cv);
664 219           SvREFCNT_dec(server->shutdown_cb_cv);
665 422 100         for (int i = 0; i < server->n_listeners; i++) {
666 203           struct feer_listen *lsnr = &server->listeners[i];
667 203           ev_io_stop(feersum_ev_loop, &lsnr->accept_w);
668 203           ev_timer_stop(feersum_ev_loop, &lsnr->emfile_w);
669             #ifdef __linux__
670 203 50         if (lsnr->epoll_fd >= 0) {
671 0           close(lsnr->epoll_fd);
672 0           lsnr->epoll_fd = -1;
673             }
674             #endif
675 203           SvREFCNT_dec(lsnr->server_name);
676 203           SvREFCNT_dec(lsnr->server_port);
677             #ifdef FEERSUM_HAS_TLS
678 203           feer_tls_cleanup_listener(lsnr);
679             #endif
680             }
681 219 100         if (server->watchers_initialized && --date_timer_refs <= 0) {
    100          
682 114           ev_timer_stop(feersum_ev_loop, &date_timer);
683 114           date_timer_refs = 0;
684             }
685             }
686              
687             void
688             set_tls (struct feer_server *server, ...)
689             PPCODE:
690             {
691             #ifdef FEERSUM_HAS_TLS
692 171           const char *cert_file = NULL;
693 171           const char *key_file = NULL;
694 171           const char *sni_name = NULL;
695 171           int listener_idx = -1; /* -1 means last-added listener (default) */
696 171           int h2 = 0;
697             int i;
698              
699 171 100         if (items < 3 || (items - 1) % 2 != 0)
    50          
700 1           croak("set_tls requires key => value pairs (cert_file => $path, key_file => $path)");
701              
702 578 100         for (i = 1; i < items; i += 2) {
703 410           const char *key = SvPV_nolen(ST(i));
704 410           SV *val = ST(i + 1);
705 410 100         if (strcmp(key, "cert_file") == 0)
706 167           cert_file = SvPV_nolen(val);
707 243 100         else if (strcmp(key, "key_file") == 0)
708 167           key_file = SvPV_nolen(val);
709 76 100         else if (strcmp(key, "listener") == 0)
710 6           listener_idx = SvIV(val);
711 70 100         else if (strcmp(key, "h2") == 0)
712 1           h2 = SvTRUE(val) ? 1 : 0;
713 69 100         else if (strcmp(key, "sni") == 0)
714 67           sni_name = SvPV_nolen(val);
715             else
716 2           croak("set_tls: unknown option '%s'", key);
717             }
718              
719 168 100         if (!cert_file) croak("set_tls: cert_file is required");
720 165 100         if (!key_file) croak("set_tls: key_file is required");
721              
722 162 100         if (server->n_listeners == 0)
723 3           croak("set_tls: no listeners configured (call use_socket/accept_on_fd first)");
724              
725             /* Resolve listener index */
726 159 50         if (listener_idx < -1)
727 0           croak("set_tls: listener index %d out of range (0..%d or -1)",
728             listener_idx, server->n_listeners - 1);
729 159 100         if (listener_idx < 0)
730 153           listener_idx = server->n_listeners - 1;
731 159 100         if (listener_idx >= server->n_listeners)
732 2           croak("set_tls: listener index %d out of range (0..%d)",
733             listener_idx, server->n_listeners - 1);
734              
735 157           struct feer_listen *lsnr = &server->listeners[listener_idx];
736              
737             /* Validate SNI preconditions before creating context (avoids leak on croak) */
738 157           STRLEN sni_name_len = 0;
739 157 100         if (sni_name) {
740 67           sni_name_len = strlen(sni_name);
741 67 50         if (sni_name_len == 0)
742 0           croak("set_tls: SNI hostname must not be empty");
743 67 50         if (sni_name_len >= 256)
744 0           croak("set_tls: SNI hostname too long");
745 67 50         if (!lsnr->tls_ctx_ref)
746 0           croak("set_tls: set a default TLS context before adding SNI entries");
747 67 50         if (lsnr->n_sni_entries >= FEER_MAX_SNI_ENTRIES)
748 0           croak("set_tls: too many SNI entries (max %d)", FEER_MAX_SNI_ENTRIES);
749             /* SNI cert selection happens during the ALPN callback; ALPN is
750             * negotiated by the default context's on_client_hello handler.
751             * Per-SNI h2 flag would have no effect, so reject it to avoid
752             * silent misconfiguration. */
753 67 100         if (h2)
754 1           croak("set_tls: 'h2' option is listener-wide; set it on the "
755             "default certificate, not per-SNI entry");
756             }
757              
758 156           ptls_context_t *new_ctx = feer_tls_create_context(aTHX_ cert_file, key_file, h2);
759 156 100         if (!new_ctx)
760 5           croak("set_tls: failed to create TLS context");
761              
762 151 100         if (sni_name) {
763 66           STRLEN name_len = sni_name_len;
764             char lower[256];
765 790 100         for (i = 0; (size_t)i < name_len; i++)
766 724           lower[i] = ascii_lower[(unsigned char)sni_name[i]];
767 66           lower[name_len] = '\0';
768              
769             /* Replace existing entry for same hostname (live cert rotation),
770             * otherwise append. */
771 66           struct feer_sni_entry *e = NULL;
772 66 100         for (i = 0; i < lsnr->n_sni_entries; i++) {
773 63 50         if (strcmp(lsnr->sni_entries[i].hostname, lower) == 0) {
774 63           e = &lsnr->sni_entries[i];
775 63           feer_tls_ctx_ref_dec(e->ctx_ref);
776 63           break;
777             }
778             }
779 66 100         if (!e) {
780 3           e = &lsnr->sni_entries[lsnr->n_sni_entries++];
781 3           Newx(e->hostname, name_len + 1, char);
782 3           memcpy(e->hostname, lower, name_len + 1);
783             }
784 66           e->ctx_ref = feer_tls_ctx_ref_new(new_ctx);
785              
786             trace("SNI entry '%s' set on listener %d (h2=%d)\n",
787             e->hostname, listener_idx, h2);
788             } else {
789             /* Set/replace default context */
790 85 100         if (lsnr->tls_ctx_ref)
791 1           feer_tls_ctx_ref_dec(lsnr->tls_ctx_ref);
792 85           lsnr->tls_ctx_ref = feer_tls_ctx_ref_new(new_ctx);
793              
794             trace("TLS enabled on listener %d (h2=%d)\n", listener_idx, h2);
795             }
796             #else
797             PERL_UNUSED_VAR(server);
798             croak("set_tls: Feersum was not compiled with TLS support (need picotls submodule + OpenSSL; see Alien::OpenSSL)");
799             #endif
800             }
801              
802             int
803             has_tls (struct feer_server *server)
804             CODE:
805             {
806             PERL_UNUSED_VAR(server);
807             #ifdef FEERSUM_HAS_TLS
808 58 50         RETVAL = 1;
809             #else
810             RETVAL = 0;
811             #endif
812             }
813             OUTPUT:
814             RETVAL
815              
816             int
817             has_h2 (struct feer_server *server)
818             CODE:
819             {
820             PERL_UNUSED_VAR(server);
821             #ifdef FEERSUM_HAS_H2
822             RETVAL = 1;
823             #else
824 17 50         RETVAL = 0;
825             #endif
826             }
827             OUTPUT:
828             RETVAL
829              
830             BOOT:
831             {
832 140           feer_stash = gv_stashpv("Feersum", 1);
833 140           feer_conn_stash = gv_stashpv("Feersum::Connection", 1);
834 140           feer_conn_writer_stash = gv_stashpv("Feersum::Connection::Writer",1);
835 140           feer_conn_reader_stash = gv_stashpv("Feersum::Connection::Reader",1);
836             /* Ignore SIGPIPE once at module load - writes to closed sockets
837             * must return EPIPE to the caller, not terminate the process. */
838 140           signal(SIGPIPE, SIG_IGN);
839 140 50         I_EV_API("Feersum");
    50          
    50          
840              
841 140           const char *env_fl_max = getenv("FEERSUM_FREELIST_MAX");
842 140 50         if (env_fl_max) {
843 0           int n = atoi(env_fl_max);
844 0           FEERSUM_FREELIST_MAX = n < 0 ? 0 : n; /* clamp; 0 disables */
845             }
846              
847             // Allocate default server (backed by a blessed Perl SV)
848 140           default_server = new_feer_server(aTHX);
849             // Keep an extra refcount so the default server is never GC'd
850 140           SvREFCNT_inc_void_NN(default_server->self);
851              
852 140           psgi_ver = newAV();
853 140           av_extend(psgi_ver, 1); // pre-allocate for 2 elements (psgi.version = [1, 1])
854 140           av_push(psgi_ver, newSViv(1));
855 140           av_push(psgi_ver, newSViv(1));
856 140           SvREADONLY_on((SV*)psgi_ver);
857              
858 140           psgi_serv10 = newSVpvs("HTTP/1.0");
859 140           SvREADONLY_on(psgi_serv10);
860 140           psgi_serv11 = newSVpvs("HTTP/1.1");
861 140           SvREADONLY_on(psgi_serv11);
862              
863 140           method_GET = newSVpvs("GET");
864 140           SvREADONLY_on(method_GET);
865 140           method_POST = newSVpvs("POST");
866 140           SvREADONLY_on(method_POST);
867 140           method_HEAD = newSVpvs("HEAD");
868 140           SvREADONLY_on(method_HEAD);
869 140           method_PUT = newSVpvs("PUT");
870 140           SvREADONLY_on(method_PUT);
871 140           method_PATCH = newSVpvs("PATCH");
872 140           SvREADONLY_on(method_PATCH);
873 140           method_DELETE = newSVpvs("DELETE");
874 140           SvREADONLY_on(method_DELETE);
875 140           method_OPTIONS = newSVpvs("OPTIONS");
876 140           SvREADONLY_on(method_OPTIONS);
877              
878 140           status_200 = newSVpvs("200 OK");
879 140           SvREADONLY_on(status_200);
880 140           status_201 = newSVpvs("201 Created");
881 140           SvREADONLY_on(status_201);
882 140           status_204 = newSVpvs("204 No Content");
883 140           SvREADONLY_on(status_204);
884 140           status_301 = newSVpvs("301 Moved Permanently");
885 140           SvREADONLY_on(status_301);
886 140           status_302 = newSVpvs("302 Found");
887 140           SvREADONLY_on(status_302);
888 140           status_304 = newSVpvs("304 Not Modified");
889 140           SvREADONLY_on(status_304);
890 140           status_400 = newSVpvs("400 Bad Request");
891 140           SvREADONLY_on(status_400);
892 140           status_404 = newSVpvs("404 Not Found");
893 140           SvREADONLY_on(status_404);
894 140           status_500 = newSVpvs("500 Internal Server Error");
895 140           SvREADONLY_on(status_500);
896              
897 140           empty_query_sv = newSVpvs("");
898 140           SvREADONLY_on(empty_query_sv);
899              
900 140           Zero(&psgix_io_vtbl, 1, MGVTBL);
901 140           psgix_io_vtbl.svt_get = psgix_io_svt_get;
902 140           newCONSTSUB(feer_stash, "HEADER_NORM_SKIP", newSViv(HEADER_NORM_SKIP));
903 140           newCONSTSUB(feer_stash, "HEADER_NORM_UPCASE", newSViv(HEADER_NORM_UPCASE));
904 140           newCONSTSUB(feer_stash, "HEADER_NORM_LOCASE", newSViv(HEADER_NORM_LOCASE));
905 140           newCONSTSUB(feer_stash, "HEADER_NORM_UPCASE_DASH", newSViv(HEADER_NORM_UPCASE_DASH));
906 140           newCONSTSUB(feer_stash, "HEADER_NORM_LOCASE_DASH", newSViv(HEADER_NORM_LOCASE_DASH));
907              
908             trace3("Feersum booted, iomatrix %lu, FEERSUM_IOMATRIX_SIZE=%u, "
909             "feer_req %lu, feer_conn %lu\n",
910             (long unsigned int)sizeof(struct iomatrix),
911             (unsigned int)FEERSUM_IOMATRIX_SIZE,
912             (long unsigned int)sizeof(struct feer_req),
913             (long unsigned int)sizeof(struct feer_conn)
914             );
915             }
916              
917             INCLUDE: feersum_conn.xs