39#include <openssl/err.h>
40#include <openssl/ssl.h>
49#include <sys/socket.h>
73 while ((opt = getopt(argc, argv,
"V:")) != EOF) {
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))
106 if (strcmp(req->
path,
"/hello") == 0)
107 return text(200,
"hello");
108 if (strcmp(req->
path,
"/null") == 0)
110 if (strcmp(req->
path,
"/info") == 0) {
112 snprintf(b,
sizeof(b),
"path=%s query=%s tls=%d peer=%s", req->
path, req->
query, req->
tls, req->
peer_ip);
115 if (strcmp(req->
path,
"/echo") == 0) {
127 if (strcmp(req->
path,
"/file") == 0) {
128 char path[] =
"ex_http_server_body_XXXXXX";
129 int fd = mkstemp(path);
132 long long size = fd >= 0 ? (
long long)lseek(fd, 0, SEEK_END) : -1;
139 snprintf(b,
sizeof(b),
"stored %lld", size);
142 if (strcmp(req->
path,
"/nobody") == 0)
143 return text(200,
"ignored");
144 return text(404,
"not found");
160 memset(c, 0,
sizeof(*c));
173 c->
ssl = SSL_new(ctx);
175 SSL_set_tlsext_host_name(c->
ssl,
"localhost");
176 return SSL_connect(c->
ssl) == 1;
187 SSL_write(c->
ssl, s, (
int)n);
192 ssize_t w = send(c->
netw->
link.
sock, s, n, MSG_NOSIGNAL);
215 int r = SSL_read(c->
ssl, b, (
int)n);
218 r = SSL_get_error(c->
ssl, r);
220 return r == SSL_ERROR_ZERO_RETURN || r == SSL_ERROR_SYSCALL ? 0 : -1;
233 SSL_shutdown(c->
ssl);
261 const char* end = NULL;
262 memset(r, 0,
sizeof(*r));
263 while (!(end = strstr(
pending,
"\r\n\r\n"))) {
274 size_t hl = (size_t)(end -
pending) + 4;
279 cl = strstr(r->
head,
"Content-Length: ");
281 clen = atol(cl + 16);
284 while ((
long)(
npending - hl) < clen) {
292 r->
body[clen] =
'\0';
312 if (c->
ssl && SSL_pending(c->
ssl) > 0)
315 if (poll(&p, 1, ms) <= 0)
317 return cl_recv(c, b,
sizeof(b)) == 0;
342 for (p = base; p < base + 100; p++) {
344 snprintf(
port, 16,
"%d", p);
384 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
386 expect_true(
"keep-alive: no Connection: close", strstr(r.
head,
"Connection: close") == 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");
393 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 3\r\n\r\nabc");
395 cl_sends(&c,
"GET /null HTTP/1.1\r\nHost: t\r\n\r\n");
397 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHost: t\r\nConnection: close\r\n\r\n");
404 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
406 t0 = (
long long)time(NULL);
413 cl_sends(&c,
"GET /hello HTTP/1.0\r\n\r\n");
420 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 2000\r\n\r\n");
426 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 200000\r\n\r\n");
431 static char big[60000 + 128];
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);
443 cl_sends(&c,
"POST /nobody HTTP/1.1\r\nHost: t\r\nContent-Length: 4\r\n\r\nabcd");
449 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 10\r\n\r\nabc");
455 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 5\r\nExpect: 100-continue\r\n\r\n");
463 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 5\r\nExpect: something-else\r\n\r\n");
470 cl_sends(&c,
"BREW /pot HTTP/1.1\r\nHost: t\r\n\r\n");
476 cl_sends(&c,
"TRACE / HTTP/1.1\r\nHost: t\r\n\r\n");
482 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 1073742300\r\n\r\n");
488 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\ncontent-length: 99999999999999999999999 \r\n\r\n");
494 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 1073742300\r\nContent-Length: 1073742300\r\n\r\n");
500 cl_sends(&c,
"POST /echo HTTP/1.1\r\nHost: t\r\nContent-Length: 12x\r\n\r\n");
506 cl_sends(&c,
"get / HTTP/1.1\r\nHost: t\r\n\r\n");
512 cl_sends(&c,
"GET\t/ HTTP/1.1\r\nHost: t\r\n\r\n");
518 cl_sends(&c,
"GET /hello HTTP/1.1\r\n\r\n");
523 static char huge[20000];
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));
534 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHo");
540 struct timespec ts0, ts1;
546 clock_gettime(CLOCK_MONOTONIC, &ts0);
550 for (i = 0; i < 6; i++) {
551 if (poll(&p, 1, 0) > 0)
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);
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);
601 memset(&c3, 0,
sizeof(c3));
602 memset(&c4, 0,
sizeof(c4));
620 pthread_join(th, NULL);
621 expect_true(
"capacity server stopped while connections were held",
run.ok == TRUE);
631 int fd = mkstemp(path);
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";
670 SSL_CTX* cctx = SSL_CTX_new(TLS_client_method());
671 SSL_CTX_set_verify(cctx, SSL_VERIFY_NONE, NULL);
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");
677 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
690 cl_sends(&c,
"GET /hello HTTP/1.1\r\nHost: t\r\n\r\n");
696 pthread_join(th, NULL);
714int main(
int argc,
char** argv) {
void process_args(int argc, char **argv)
static void expect_true(const char *label, int cond)
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
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
static N_HTTP_RESP * handler(N_HTTP_SERVER_REQUEST *req, void *user)
the application under test
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
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
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
volatile char key[227]
TRUE while the key is held.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
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
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...
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
A box including a string and his lenght.
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.
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.
N_SOCKET link
networking socket
SOCKET sock
a normal socket
int netw_init_openssl(void)
Do not directly use, internal api.
int netw_unload_openssl(void)
Do not directly use, internal api.
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
int netw_setsockopt(NETWORK *netw, int optname, int value)
Modify common socket options on the given netw.
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
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.
Common headers and low-level functions & define.
HTTP/1.1 response: validated headers, memory or file body, framing.
HTTP/1.1 server: TLS or plain, deadlines, limits, keep-alive.
N_STR and string function declaration.
X.509 helpers: self-signed CA generation and per-host leaf minting.