Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_http_server.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
30
31#include "nilorea/n_common.h"
34#include "nilorea/n_log.h"
35#include "nilorea/n_network.h"
36#include "nilorea/n_str.h"
37#ifdef HAVE_OPENSSL
38#include "nilorea/n_x509.h"
39#include <openssl/err.h>
40#include <openssl/ssl.h>
41#endif
42
43#include <fcntl.h>
44#include <poll.h>
45#include <pthread.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <sys/socket.h>
50#include <time.h>
51#include <unistd.h>
52
54static int failures = 0;
55
59static void expect_true(const char* what, int ok) {
60 if (!ok) {
61 n_log(LOG_ERR, "FAIL: %s", what);
62 failures++;
63 } else {
64 n_log(LOG_INFO, "ok: %s", what);
65 }
66}
67
71static void process_args(int argc, char** argv) {
72 int opt = 0;
73 while ((opt = getopt(argc, argv, "V:")) != EOF) {
74 if (opt == 'V') {
75 if (!strncmp("LOG_NULL", optarg, 8))
77 else if (!strncmp("LOG_NOTICE", optarg, 10))
79 else if (!strncmp("LOG_INFO", optarg, 8))
81 else if (!strncmp("LOG_ERR", optarg, 7))
83 else if (!strncmp("LOG_DEBUG", optarg, 9))
85 }
86 }
87}
88
93static N_HTTP_RESP* text(int status, const char* body) {
95 n_http_response_set_header(r, "Content-Type", "text/plain");
96 n_http_response_append(r, body, strlen(body));
97 return r;
98}
99
104static N_HTTP_RESP* handler(N_HTTP_SERVER_REQUEST* req, void* user) {
105 (void)user;
106 if (strcmp(req->path, "/hello") == 0)
107 return text(200, "hello");
108 if (strcmp(req->path, "/null") == 0)
109 return NULL;
110 if (strcmp(req->path, "/info") == 0) {
111 char b[256];
112 snprintf(b, sizeof(b), "path=%s query=%s tls=%d peer=%s", req->path, req->query, req->tls, req->peer_ip);
113 return text(200, b);
114 }
115 if (strcmp(req->path, "/echo") == 0) {
116 N_STR* body = NULL;
117 N_HTTP_RESP* r;
118 if (!n_http_server_read_body(req, 1024, &body)) {
119 free_nstr(&body);
120 return text(req->body_status, "body refused");
121 }
122 r = n_http_response_new(200);
123 n_http_response_append(r, body->data, body->written);
124 free_nstr(&body);
125 return r;
126 }
127 if (strcmp(req->path, "/file") == 0) {
128 char path[] = "ex_http_server_body_XXXXXX";
129 int fd = mkstemp(path);
130 char b[64];
131 int ok = fd >= 0 && n_http_server_read_body_to_fd(req, 65536, fd);
132 long long size = fd >= 0 ? (long long)lseek(fd, 0, SEEK_END) : -1;
133 if (fd >= 0) {
134 close(fd);
135 unlink(path);
136 }
137 if (!ok)
138 return text(req->body_status ? req->body_status : 500, "file refused");
139 snprintf(b, sizeof(b), "stored %lld", size);
140 return text(200, b);
141 }
142 if (strcmp(req->path, "/nobody") == 0)
143 return text(200, "ignored");
144 return text(404, "not found");
145}
146
148typedef struct {
150#ifdef HAVE_OPENSSL
151 SSL* ssl;
152#endif
153} client;
154
159static int cl_connect(const char* port, client* c) {
160 memset(c, 0, sizeof(*c));
161 if (netw_connect(&c->netw, "127.0.0.1", (char*)port, NETWORK_IPALL) != TRUE || !c->netw)
162 return 0;
163 netw_setsockopt(c->netw, SO_RCVTIMEO, 3000);
164 return 1;
165}
166
167#ifdef HAVE_OPENSSL
172static int cl_tls(client* c, SSL_CTX* ctx) {
173 c->ssl = SSL_new(ctx);
174 SSL_set_fd(c->ssl, (int)c->netw->link.sock);
175 SSL_set_tlsext_host_name(c->ssl, "localhost");
176 return SSL_connect(c->ssl) == 1;
177}
178#endif
179
184static void cl_send(client* c, const char* s, size_t n) {
185#ifdef HAVE_OPENSSL
186 if (c->ssl) {
187 SSL_write(c->ssl, s, (int)n);
188 return;
189 }
190#endif
191 while (n > 0) {
192 ssize_t w = send(c->netw->link.sock, s, n, MSG_NOSIGNAL);
193 if (w <= 0)
194 return;
195 s += w;
196 n -= (size_t)w;
197 }
198}
199
203static void cl_sends(client* c, const char* s) {
204 cl_send(c, s, strlen(s));
205}
206
212static long cl_recv(client* c, char* b, size_t n) {
213#ifdef HAVE_OPENSSL
214 if (c->ssl) {
215 int r = SSL_read(c->ssl, b, (int)n);
216 if (r > 0)
217 return r;
218 r = SSL_get_error(c->ssl, r);
219 ERR_clear_error();
220 return r == SSL_ERROR_ZERO_RETURN || r == SSL_ERROR_SYSCALL ? 0 : -1;
221 }
222#endif
223 return (long)recv(c->netw->link.sock, b, n, 0);
224}
225
228static void cl_close(client* c) {
229 if (!c->netw)
230 return;
231#ifdef HAVE_OPENSSL
232 if (c->ssl) {
233 SSL_shutdown(c->ssl);
234 SSL_free(c->ssl);
235 c->ssl = NULL;
236 ERR_clear_error();
237 }
238#endif
239 netw_close(&c->netw);
240}
241
243typedef struct {
244 int status;
245 char head[4096];
246 char body[70000];
247 long body_len;
248} reply;
249
251static char pending[200000];
253static size_t npending = 0;
254
260static int read_reply(client* c, int head_only, reply* r) {
261 const char* end = NULL;
262 memset(r, 0, sizeof(*r));
263 while (!(end = strstr(pending, "\r\n\r\n"))) {
264 long n;
265 if (npending >= sizeof(pending) - 1)
266 return 0;
267 n = cl_recv(c, pending + npending, sizeof(pending) - 1 - npending);
268 if (n <= 0)
269 return 0;
270 npending += (size_t)n;
271 pending[npending] = '\0';
272 }
273 {
274 size_t hl = (size_t)(end - pending) + 4;
275 long clen = 0;
276 const char* cl;
277 memcpy(r->head, pending, hl < sizeof(r->head) - 1 ? hl : sizeof(r->head) - 1);
278 sscanf(pending, "HTTP/1.1 %d", &r->status);
279 cl = strstr(r->head, "Content-Length: ");
280 if (cl)
281 clen = atol(cl + 16);
282 if (head_only || r->status == 100 || r->status == 204 || r->status == 304)
283 clen = 0;
284 while ((long)(npending - hl) < clen) {
285 long n = cl_recv(c, pending + npending, sizeof(pending) - 1 - npending);
286 if (n <= 0)
287 return 0;
288 npending += (size_t)n;
289 pending[npending] = '\0';
290 }
291 memcpy(r->body, pending + hl, (size_t)clen);
292 r->body[clen] = '\0';
293 r->body_len = clen;
294 memmove(pending, pending + hl + clen, npending - hl - (size_t)clen + 1);
295 npending -= hl + (size_t)clen;
296 }
297 return r->status;
298}
299
304static int closed_by_server(client* c, int ms) {
305 char b[64];
306 struct pollfd p;
307 p.fd = (int)c->netw->link.sock;
308 p.events = POLLIN;
309 if (npending > 0)
310 return 0;
311#ifdef HAVE_OPENSSL
312 if (c->ssl && SSL_pending(c->ssl) > 0)
313 return 0;
314#endif
315 if (poll(&p, 1, ms) <= 0)
316 return 0;
317 return cl_recv(c, b, sizeof(b)) == 0;
318}
319
321typedef struct {
323 int ok;
324} runner;
325
329static void* run_server(void* p) {
330 runner* r = (runner*)p;
331 r->ok = n_http_server_run(r->srv);
332 return NULL;
333}
334
340static N_HTTP_SERVER* start(N_HTTP_SERVER_CONFIG* cfg, char* port, int base) {
341 int p;
342 for (p = base; p < base + 100; p++) {
343 N_HTTP_SERVER* s;
344 snprintf(port, 16, "%d", p);
345 cfg->port = port;
346 s = n_http_server_new(cfg);
347 if (s)
348 return s;
349 }
350 return NULL;
351}
352
354static void test_plain(void) {
356 N_HTTP_SERVER* srv;
358 runner run;
359 pthread_t th;
360 char port[16];
361 client c;
362 static reply r;
363 long long t0;
364
366 cfg.handler = handler;
367 cfg.workers = 2;
368 cfg.header_timeout_ms = 300;
369 cfg.idle_timeout_ms = 300;
370 cfg.body_timeout_ms = 300;
371 cfg.write_timeout_ms = 1000;
372 cfg.max_body = 100000;
373 srv = start(&cfg, port, 18600);
374 expect_true("server bound", srv != NULL);
375 if (!srv)
376 return;
377 run.srv = srv;
378 run.ok = 0;
379 pthread_create(&th, NULL, run_server, &run);
380
381 /* keep-alive, pipelining, HEAD */
382 if (cl_connect(port, &c)) {
383 npending = 0;
384 cl_sends(&c, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
385 expect_true("GET 200", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "hello") == 0);
386 expect_true("keep-alive: no Connection: close", strstr(r.head, "Connection: close") == NULL);
387 expect_true("Date sent", strstr(r.head, "\r\nDate: ") != NULL);
388 cl_sends(&c, "GET /info?a=1&b=2 HTTP/1.1\r\nHost: t\r\n\r\n");
389 expect_true("second request on the same connection", read_reply(&c, 0, &r) == 200 && strstr(r.body, "path=/info query=a=1&b=2 tls=0 peer=127.0.0.1"));
390 cl_sends(&c, "HEAD /hello HTTP/1.1\r\nHost: t\r\n\r\nGET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
391 expect_true("pipelined HEAD: length, no body", read_reply(&c, 1, &r) == 200 && strstr(r.head, "Content-Length: 5\r\n") && r.body_len == 0);
392 expect_true("pipelined GET after it", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "hello") == 0);
393 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 3\r\n\r\nabc");
394 expect_true("POST body echoed", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "abc") == 0);
395 cl_sends(&c, "GET /null HTTP/1.1\r\nHost: t\r\n\r\n");
396 expect_true("NULL response: 500", read_reply(&c, 0, &r) == 500);
397 cl_sends(&c, "GET /hello HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n");
398 expect_true("Connection: close honoured", read_reply(&c, 0, &r) == 200 && strstr(r.head, "Connection: close") && closed_by_server(&c, 1000));
399 cl_close(&c);
400 }
401 /* idle timeout after a keep-alive response */
402 if (cl_connect(port, &c)) {
403 npending = 0;
404 cl_sends(&c, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
405 read_reply(&c, 0, &r);
406 t0 = (long long)time(NULL);
407 expect_true("idle connection closed after the idle timeout", closed_by_server(&c, 2000) && (long long)time(NULL) - t0 <= 2);
408 cl_close(&c);
409 }
410 /* HTTP/1.0 */
411 if (cl_connect(port, &c)) {
412 npending = 0;
413 cl_sends(&c, "GET /hello HTTP/1.0\r\n\r\n");
414 expect_true("HTTP/1.0 without keep-alive closes", read_reply(&c, 0, &r) == 200 && strstr(r.head, "Connection: close") && closed_by_server(&c, 1000));
415 cl_close(&c);
416 }
417 /* bodies */
418 if (cl_connect(port, &c)) {
419 npending = 0;
420 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 2000\r\n\r\n");
421 expect_true("body over the handler's cap: 413 and close", read_reply(&c, 0, &r) == 413 && closed_by_server(&c, 1000));
422 cl_close(&c);
423 }
424 if (cl_connect(port, &c)) {
425 npending = 0;
426 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 200000\r\n\r\n");
427 expect_true("body over max_body: 413 before the handler", read_reply(&c, 0, &r) == 413 && strstr(r.body, "413") && closed_by_server(&c, 1000));
428 cl_close(&c);
429 }
430 if (cl_connect(port, &c)) {
431 static char big[60000 + 128];
432 size_t i, hl;
433 npending = 0;
434 hl = (size_t)snprintf(big, sizeof(big), "POST /file HTTP/1.1\r\nHost: t\r\nContent-Length: 60000\r\n\r\n");
435 for (i = 0; i < 60000; i++)
436 big[hl + i] = (char)('a' + i % 26);
437 cl_send(&c, big, hl + 60000);
438 expect_true("60000-byte body stored in a file", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "stored 60000") == 0);
439 cl_close(&c);
440 }
441 if (cl_connect(port, &c)) {
442 npending = 0;
443 cl_sends(&c, "POST /nobody HTTP/1.1\r\nHost: t\r\nContent-Length: 4\r\n\r\nabcd");
444 expect_true("unread body: answered, then closed", read_reply(&c, 0, &r) == 200 && strstr(r.head, "Connection: close") && closed_by_server(&c, 1000));
445 cl_close(&c);
446 }
447 if (cl_connect(port, &c)) {
448 npending = 0;
449 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 10\r\n\r\nabc");
450 expect_true("body cut short: 408 after the body deadline", read_reply(&c, 0, &r) == 408);
451 cl_close(&c);
452 }
453 if (cl_connect(port, &c)) {
454 npending = 0;
455 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 5\r\nExpect: 100-continue\r\n\r\n");
456 expect_true("100 Continue sent when the body is read", read_reply(&c, 0, &r) == 100);
457 cl_sends(&c, "hello");
458 expect_true("then the final response", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "hello") == 0);
459 cl_close(&c);
460 }
461 if (cl_connect(port, &c)) {
462 npending = 0;
463 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 5\r\nExpect: something-else\r\n\r\n");
464 expect_true("unknown Expect: 417", read_reply(&c, 0, &r) == 417);
465 cl_close(&c);
466 }
467 /* bad requests */
468 if (cl_connect(port, &c)) {
469 npending = 0;
470 cl_sends(&c, "BREW /pot HTTP/1.1\r\nHost: t\r\n\r\n");
471 expect_true("unknown method: 501 and close", read_reply(&c, 0, &r) == 501 && closed_by_server(&c, 1000));
472 cl_close(&c);
473 }
474 if (cl_connect(port, &c)) {
475 npending = 0;
476 cl_sends(&c, "TRACE / HTTP/1.1\r\nHost: t\r\n\r\n");
477 expect_true("TRACE: 501", read_reply(&c, 0, &r) == 501);
478 cl_close(&c);
479 }
480 if (cl_connect(port, &c)) {
481 npending = 0;
482 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 1073742300\r\n\r\n");
483 expect_true("Content-Length above every cap: 413", read_reply(&c, 0, &r) == 413);
484 cl_close(&c);
485 }
486 if (cl_connect(port, &c)) {
487 npending = 0;
488 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\ncontent-length: 99999999999999999999999 \r\n\r\n");
489 expect_true("Content-Length of 23 digits: 413", read_reply(&c, 0, &r) == 413);
490 cl_close(&c);
491 }
492 if (cl_connect(port, &c)) {
493 npending = 0;
494 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 1073742300\r\nContent-Length: 1073742300\r\n\r\n");
495 expect_true("two huge Content-Length headers: 400", read_reply(&c, 0, &r) == 400);
496 cl_close(&c);
497 }
498 if (cl_connect(port, &c)) {
499 npending = 0;
500 cl_sends(&c, "POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 12x\r\n\r\n");
501 expect_true("malformed Content-Length: 400", read_reply(&c, 0, &r) == 400);
502 cl_close(&c);
503 }
504 if (cl_connect(port, &c)) {
505 npending = 0;
506 cl_sends(&c, "get / HTTP/1.1\r\nHost: t\r\n\r\n");
507 expect_true("lowercase method: 400", read_reply(&c, 0, &r) == 400);
508 cl_close(&c);
509 }
510 if (cl_connect(port, &c)) {
511 npending = 0;
512 cl_sends(&c, "GET\t/ HTTP/1.1\r\nHost: t\r\n\r\n");
513 expect_true("malformed request line: 400", read_reply(&c, 0, &r) == 400);
514 cl_close(&c);
515 }
516 if (cl_connect(port, &c)) {
517 npending = 0;
518 cl_sends(&c, "GET /hello HTTP/1.1\r\n\r\n");
519 expect_true("HTTP/1.1 without Host: 400", read_reply(&c, 0, &r) == 400);
520 cl_close(&c);
521 }
522 if (cl_connect(port, &c)) {
523 static char huge[20000];
524 npending = 0;
525 memset(huge, 'a', sizeof(huge));
526 memcpy(huge, "GET /hello HTTP/1.1\r\nHost: t\r\nX-Big: ", 37);
527 cl_send(&c, huge, sizeof(huge));
528 expect_true("head over 16 KiB: 431", read_reply(&c, 0, &r) == 431);
529 cl_close(&c);
530 }
531 /* slow peers */
532 if (cl_connect(port, &c)) {
533 npending = 0;
534 cl_sends(&c, "GET /hello HTTP/1.1\r\nHo");
535 expect_true("head not finished in time: 408", read_reply(&c, 0, &r) == 408 && closed_by_server(&c, 1000));
536 cl_close(&c);
537 }
538 if (cl_connect(port, &c)) {
539 int i;
540 struct timespec ts0, ts1;
541 long elapsed;
542 struct pollfd p;
543 npending = 0;
544 p.fd = (int)c.netw->link.sock;
545 p.events = POLLIN;
546 clock_gettime(CLOCK_MONOTONIC, &ts0);
547 /* one byte every 100 ms for 600 ms: every single read is quick, so
548 * only a TOTAL head deadline (300 ms) stops it; stop sending as soon
549 * as the answer is readable, without reading it */
550 for (i = 0; i < 6; i++) {
551 if (poll(&p, 1, 0) > 0)
552 break;
553 cl_send(&c, "G", 1);
554 usleep(100000);
555 }
556 expect_true("byte-at-a-time head: 408", read_reply(&c, 0, &r) == 408);
557 clock_gettime(CLOCK_MONOTONIC, &ts1);
558 elapsed = (ts1.tv_sec - ts0.tv_sec) * 1000 + (ts1.tv_nsec - ts0.tv_nsec) / 1000000;
559 expect_true("byte-at-a-time head: stopped by the total deadline, not per read", elapsed < 550);
560 cl_close(&c);
561 }
562 if (cl_connect(port, &c)) {
563 npending = 0;
564 expect_true("silent connection closed after the header timeout", closed_by_server(&c, 2000));
565 cl_close(&c);
566 }
568 t0 = (long long)time(NULL);
569 pthread_join(th, NULL);
570 expect_true("run() returns after stop", run.ok == TRUE && (long long)time(NULL) - t0 <= 2);
571 n_http_server_stats(srv, &st);
572 n_log(LOG_INFO, "stats: accepted %llu refused %llu active %llu requests %llu timeouts %llu bad %llu", st.accepted, st.refused, st.active, st.requests, st.timeouts, st.bad_requests);
573 expect_true("stats", st.requests >= 10 && st.timeouts >= 3 && st.bad_requests >= 4 && st.active == 0);
574 n_http_server_free(&srv);
575 expect_true("free sets NULL", srv == NULL);
576}
577
580static void test_capacity(void) {
582 N_HTTP_SERVER* srv;
583 runner run;
584 pthread_t th;
585 char port[16];
586 client c, c2, c3, c4, c5;
587 static reply r;
588
590 cfg.handler = handler;
591 cfg.workers = 2;
592 cfg.header_timeout_ms = 5000;
593 srv = start(&cfg, port, 18800);
594 expect_true("capacity server bound", srv != NULL);
595 if (!srv)
596 return;
597 run.srv = srv;
598 run.ok = 0;
599 pthread_create(&th, NULL, run_server, &run);
600 /* 2 workers hold c and c2, c3 and c4 fill the queue of 2, c5 is refused */
601 memset(&c3, 0, sizeof(c3));
602 memset(&c4, 0, sizeof(c4));
603 /* let the workers take c and c2 before the queue is filled */
604 if (cl_connect(port, &c) && cl_connect(port, &c2)) {
605 usleep(300000);
606 if (cl_connect(port, &c3) && cl_connect(port, &c4)) {
607 usleep(300000);
608 npending = 0;
609 if (cl_connect(port, &c5)) {
610 expect_true("over capacity: 503", read_reply(&c5, 0, &r) == 503);
611 cl_close(&c5);
612 }
613 }
614 }
615 cl_close(&c4);
616 cl_close(&c3);
617 cl_close(&c2);
618 cl_close(&c);
620 pthread_join(th, NULL);
621 expect_true("capacity server stopped while connections were held", run.ok == TRUE);
622 n_http_server_free(&srv);
623}
624
625#ifdef HAVE_OPENSSL
630static int write_pem(const N_STR* pem, char* path) {
631 int fd = mkstemp(path);
632 int ok = fd >= 0 && write(fd, pem->data, pem->written) == (ssize_t)pem->written;
633 if (fd >= 0)
634 close(fd);
635 return ok;
636}
637
639static void test_tls(void) {
640 N_STR *ca_cert = NULL, *ca_key = NULL, *cert = NULL, *key = NULL;
641 char cert_path[] = "ex_http_server_cert_XXXXXX", key_path[] = "ex_http_server_key_XXXXXX";
643 N_HTTP_SERVER* srv;
645 runner run;
646 pthread_t th;
647 char port[16];
648 client c;
649 static reply r;
650
651 if (n_x509_generate_ca("ex_http_server CA", 2, &ca_cert, &ca_key) != 0 ||
652 n_x509_mint_host_cert("localhost", ca_cert, ca_key, 2, &cert, &key) != 0 || !write_pem(cert, cert_path) || !write_pem(key, key_path)) {
653 expect_true("test certificate", 0);
654 return;
655 }
657 cfg.handler = handler;
658 cfg.workers = 4;
659 cfg.handshake_timeout_ms = 300;
660 cfg.tls_key_file = key_path;
661 cfg.tls_cert_file = cert_path;
662 srv = start(&cfg, port, 18700);
663 expect_true("TLS server bound", srv != NULL);
664 cfg.tls_cert_file = NULL;
665 expect_true("key without certificate refused", n_http_server_new(&cfg) == NULL);
666 if (srv) {
667 run.srv = srv;
668 run.ok = 0;
669 pthread_create(&th, NULL, run_server, &run);
670 SSL_CTX* cctx = SSL_CTX_new(TLS_client_method());
671 SSL_CTX_set_verify(cctx, SSL_VERIFY_NONE, NULL);
672 if (cl_connect(port, &c) && cl_tls(&c, cctx)) {
673 npending = 0;
674 expect_true("TLS 1.3 negotiated", strcmp(SSL_get_version(c.ssl), "TLSv1.3") == 0);
675 cl_sends(&c, "GET /info HTTP/1.1\r\nHost: t\r\n\r\n");
676 expect_true("GET over TLS", read_reply(&c, 0, &r) == 200 && strstr(r.body, "tls=1"));
677 cl_sends(&c, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
678 expect_true("keep-alive over TLS", read_reply(&c, 0, &r) == 200 && strcmp(r.body, "hello") == 0);
679 } else {
680 expect_true("TLS client handshake", 0);
681 }
682 cl_close(&c);
683 if (cl_connect(port, &c)) {
684 npending = 0;
685 expect_true("no handshake: closed after the handshake timeout", closed_by_server(&c, 2000));
686 cl_close(&c);
687 }
688 if (cl_connect(port, &c)) {
689 npending = 0;
690 cl_sends(&c, "GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
691 expect_true("plain HTTP on the TLS port: closed", read_reply(&c, 0, &r) == 0);
692 cl_close(&c);
693 }
694 SSL_CTX_free(cctx);
696 pthread_join(th, NULL);
697 n_http_server_stats(srv, &st);
698 expect_true("TLS failures counted", st.tls_failures >= 2);
699 n_http_server_free(&srv);
700 }
701 unlink(cert_path);
702 unlink(key_path);
703 free_nstr(&ca_cert);
704 free_nstr(&ca_key);
705 free_nstr(&cert);
706 free_nstr(&key);
707}
708#endif
709
714int main(int argc, char** argv) {
717 process_args(argc, argv);
718#ifdef HAVE_OPENSSL
720#endif
721
723 expect_true("config without handler refused", n_http_server_new(&bad) == NULL);
724 bad.handler = handler;
725 bad.workers = 0;
726 expect_true("config without workers refused", n_http_server_new(&bad) == NULL);
727 n_http_server_stop(NULL);
728 n_http_server_free(NULL);
729
730 test_plain();
732#ifdef HAVE_OPENSSL
733 test_tls();
735#endif
736
737 if (failures) {
738 n_log(LOG_ERR, "ex_http_server: %d failure(s)", failures);
739 return 1;
740 }
741 n_log(LOG_NOTICE, "ex_http_server: all checks passed");
742 return 0;
743}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static void run_server(char *port, double fake_offset, int nb_rounds)
N_HTTP_SERVER * srv
the server
static char pending[200000]
leftover bytes between responses on one connection
int ok
run() result
char head[4096]
the head
static long cl_recv(client *c, char *b, size_t n)
receive some bytes (3 s socket timeout)
static int read_reply(client *c, int head_only, reply *r)
read one response
NETWORK * netw
the socket
static N_HTTP_RESP * handler(N_HTTP_SERVER_REQUEST *req, void *user)
the application under test
long body_len
body length
static N_HTTP_RESP * text(int status, const char *body)
text response
static int closed_by_server(client *c, int ms)
has the server closed the connection (within ms)?
static size_t npending
number of leftover bytes
SSL * ssl
TLS, or NULL.
static void test_plain(void)
the plain HTTP server
static void test_tls(void)
the TLS server
int status
status code, 0 when none
static void cl_sends(client *c, const char *s)
send a NUL-terminated string
static N_HTTP_SERVER * start(N_HTTP_SERVER_CONFIG *cfg, char *port, int base)
start a server on the first free port from base
static void test_capacity(void)
the connection cap, on a server whose deadlines are long enough for the held connections to stay open...
static int write_pem(const N_STR *pem, char *path)
write a PEM string to a temp file
static int cl_tls(client *c, SSL_CTX *ctx)
upgrade a client connection to TLS
static int cl_connect(const char *port, client *c)
connect to the server
static void cl_send(client *c, const char *s, size_t n)
send all bytes
char body[70000]
the body
static void cl_close(client *c)
close a client connection
a test client connection, plain or TLS
a parsed response as the client saw it
server thread state
int run
Definition ex_kafka.c:56
char * cert
char * port
volatile char key[227]
TRUE while the key is held.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
int n_http_response_append(N_HTTP_RESP *r, const void *data, size_t len)
append bytes to the memory body (drops a file body)
int n_http_response_set_header(N_HTTP_RESP *r, const char *name, const char *value)
set a header: remove every header of that name, then add it
N_HTTP_RESP * n_http_response_new(int status)
new response with a status, keep-alive off, HTTP/1.1
a response under construction (fields are private, use the functions)
int body_timeout_ms
body deadline, ms (default 60000)
int header_timeout_ms
request head deadline from its first byte, ms (default 10000)
unsigned long long requests
requests answered
int tls
non-zero over TLS
unsigned long long timeouts
connections ended by a deadline
int body_status
after a failed body read: the status to answer (408, 413...)
unsigned long long active
connections being served now
size_t workers
worker threads = most concurrent connections (default 32)
const char * port
port to bind ("7430")
int write_timeout_ms
deadline to write one response, ms (default 60000)
unsigned long long accepted
connections accepted
long long max_body
largest Content-Length accepted at all (default 32 MiB); handlers pass their own, lower cap to the bo...
const char * tls_key_file
PEM private key, NULL for plain HTTP.
const char * tls_cert_file
PEM certificate chain, NULL for plain HTTP.
const char * path
the path, without the query string, still percent-encoded
n_http_server_handler handler
the application
unsigned long long refused
connections refused (queue full, per-IP cap)
const char * peer_ip
peer address as text
const char * query
the query string without '?
int idle_timeout_ms
wait for the next request on a kept-alive connection, ms (default 15000)
unsigned long long tls_failures
failed TLS handshakes
unsigned long long bad_requests
400 / 413 / 417 / 431 answered by the server
int handshake_timeout_ms
TLS handshake deadline, ms (default 10000).
void n_http_server_stats(N_HTTP_SERVER *srv, N_HTTP_SERVER_STATS *out)
copy of the counters
int n_http_server_read_body_to_fd(N_HTTP_SERVER_REQUEST *req, long long max, int fd)
read the whole body into an open file descriptor, refusing more than max
int n_http_server_run(N_HTTP_SERVER *srv)
serve until n_http_server_stop()
N_HTTP_SERVER * n_http_server_new(const N_HTTP_SERVER_CONFIG *cfg)
bind the listening socket and load the TLS context
void n_http_server_stop(N_HTTP_SERVER *srv)
ask the server to stop (async-signal-safe)
int n_http_server_read_body(N_HTTP_SERVER_REQUEST *req, long long max, N_STR **out)
read the whole body into memory, refusing more than max
void n_http_server_free(N_HTTP_SERVER **srv)
free a stopped server and set *srv to NULL
void n_http_server_config_defaults(N_HTTP_SERVER_CONFIG *cfg)
fill a config with the documented defaults
server settings; start from n_http_server_config_defaults()
a request as the handler sees it (fields are read-only)
counters, see n_http_server_stats()
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
A box including a string and his lenght.
Definition n_str.h:61
int n_x509_generate_ca(const char *cn, int days, N_STR **ca_cert_pem, N_STR **ca_key_pem)
Generate a self-signed certificate authority (CA) keypair and cert.
Definition n_x509.c:226
int n_x509_mint_host_cert(const char *host, const N_STR *ca_cert_pem, const N_STR *ca_key_pem, int days, N_STR **leaf_cert_pem, N_STR **leaf_key_pem)
Mint a per-host leaf certificate signed by the given CA.
Definition n_x509.c:285
N_SOCKET link
networking socket
Definition n_network.h:454
SOCKET sock
a normal socket
Definition n_network.h:356
int netw_init_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1335
int netw_unload_openssl(void)
Do not directly use, internal api.
Definition n_network.c:1368
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
int netw_setsockopt(NETWORK *netw, int optname, int value)
Modify common socket options on the given netw.
Definition n_network.c:969
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:3367
int netw_connect(NETWORK **netw, char *host, char *port, int ip_version)
Use this to connect a NETWORK to any listening one, unrestricted send/recv lists.
Definition n_network.c:2898
Structure of a NETWORK.
Definition n_network.h:375
Common headers and low-level functions & define.
HTTP/1.1 response: validated headers, memory or file body, framing.
a server (opaque)
HTTP/1.1 server: TLS or plain, deadlines, limits, keep-alive.
Generic log system.
Network Engine.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.