4 n__http__server_8c
gullradriel edited this page 2026-11-01 17:19:12 +01:00

src/n_http_server.c

HTTP/1.1 server (see n_http_server.h). More...

Structures

Name
struct _ip_slot
struct _conn
struct N_HTTP_SERVER
a server (opaque)

Functions

Name
ssize_t _write_fn(void * ctx, const void * buf, size_t len)
void * _worker(void * arg)
int _wait(_conn * c, int for_write)
int _value_is(const char * v, size_t n, const char * token)
int _unsupported_method(const char * b, size_t n)
void _stream_piece(void * ctx)
void _stat_add(N_HTTP_SERVER * srv, unsigned long long * field, long long delta)
void _serve(_conn * c)
void _send_error(_conn * c, int status)
long long _recv(_conn * c, char * dst, size_t cap)
long long _now_ms(void )
int _ip_take(N_HTTP_SERVER * srv, const char * ip)
void _ip_release(N_HTTP_SERVER * srv, const char * ip)
int _has_head_end(const char * b, size_t n)
int _handshake(_conn * c)
void _drop_sigpipe(void )
int _content_too_large(const char * b, size_t n)
int _body_start(N_HTTP_SERVER_REQUEST * req, long long max)
int _body_fail(N_HTTP_SERVER_REQUEST * req, long long rc)
long long _body_chunk(_conn * c, char * dst, size_t want)

Defines

Name
_SOCK_LEN(n)
_SOCK_INTR(e)
_SOCK_ERR()
_SOCK_AGAIN(e)
_IO_TIMEOUT
_IO_STOPPED
_IO_ERROR
_IO_CLOSED
N_HTTP_SERVER_SLICE_MS
N_HTTP_SERVER_BUF
N_HTTP_SERVER_ACCEPT_MS

Detailed Description

HTTP/1.1 server (see n_http_server.h).

Author: Castagnier Mickael

Version: 1.0

Date: 2026

Functions Documentation

function _write_fn

static ssize_t _write_fn(
    void * ctx,
    const void * buf,
    size_t len
)

function _worker

static void * _worker(
    void * arg
)

function _wait

static int _wait(
    _conn * c,
    int for_write
)

function _value_is

static int _value_is(
    const char * v,
    size_t n,
    const char * token
)

function _unsupported_method

static int _unsupported_method(
    const char * b,
    size_t n
)

function _stream_piece

static void _stream_piece(
    void * ctx
)

function _stat_add

static void _stat_add(
    N_HTTP_SERVER * srv,
    unsigned long long * field,
    long long delta
)

function _serve

static void _serve(
    _conn * c
)

function _send_error

static void _send_error(
    _conn * c,
    int status
)

function _recv

static long long _recv(
    _conn * c,
    char * dst,
    size_t cap
)

function _now_ms

static long long _now_ms(
    void 
)

function _ip_take

static int _ip_take(
    N_HTTP_SERVER * srv,
    const char * ip
)

function _ip_release

static void _ip_release(
    N_HTTP_SERVER * srv,
    const char * ip
)

function _has_head_end

static int _has_head_end(
    const char * b,
    size_t n
)

function _handshake

static int _handshake(
    _conn * c
)

function _drop_sigpipe

static void _drop_sigpipe(
    void 
)

function _content_too_large

static int _content_too_large(
    const char * b,
    size_t n
)

function _body_start

static int _body_start(
    N_HTTP_SERVER_REQUEST * req,
    long long max
)

function _body_fail

static int _body_fail(
    N_HTTP_SERVER_REQUEST * req,
    long long rc
)

function _body_chunk

static long long _body_chunk(
    _conn * c,
    char * dst,
    size_t want
)

Macros Documentation

define _SOCK_LEN

#define _SOCK_LEN(
    n
)
(n)

define _SOCK_INTR

#define _SOCK_INTR(
    e
)
((e) == EINTR)

define _SOCK_ERR

#define _SOCK_ERR(
    
)
errno

define _SOCK_AGAIN

#define _SOCK_AGAIN(
    e
)
((e) == EAGAIN || (e) == EWOULDBLOCK)

define _IO_TIMEOUT

#define _IO_TIMEOUT (-2)

define _IO_STOPPED

#define _IO_STOPPED (-3)

define _IO_ERROR

#define _IO_ERROR (-1)

define _IO_CLOSED

#define _IO_CLOSED 0

define N_HTTP_SERVER_SLICE_MS

#define N_HTTP_SERVER_SLICE_MS 250

define N_HTTP_SERVER_BUF

#define N_HTTP_SERVER_BUF (N_HTTP_REQ_HEAD_MAX + 16384u)

define N_HTTP_SERVER_ACCEPT_MS

#define N_HTTP_SERVER_ACCEPT_MS 250

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_http_server.h"

#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_network.h"
#include "nilorea/n_str.h"

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>

#ifndef __windows__
#include <poll.h>
#include <sys/socket.h>
#endif

#ifdef HAVE_OPENSSL
#include <openssl/err.h>
#include <openssl/ssl.h>
#endif

/* bytes the connection buffer holds: a full head plus room for pipelining */
#define N_HTTP_SERVER_BUF (N_HTTP_REQ_HEAD_MAX + 16384u)
/* longest single wait slice, so a stop request is noticed quickly */
#define N_HTTP_SERVER_SLICE_MS 250
/* accept loop timeout, ms */
#define N_HTTP_SERVER_ACCEPT_MS 250

/* Socket error codes: Winsock does not set errno. */
#ifdef __windows__
#define _SOCK_ERR() WSAGetLastError()
#define _SOCK_AGAIN(e) ((e) == WSAEWOULDBLOCK)
#define _SOCK_INTR(e) ((e) == WSAEINTR)
#define _SOCK_LEN(n) ((int)(n))
#else
#define _SOCK_ERR() errno
#define _SOCK_AGAIN(e) ((e) == EAGAIN || (e) == EWOULDBLOCK)
#define _SOCK_INTR(e) ((e) == EINTR)
#define _SOCK_LEN(n) (n)
#endif

/* I/O results below 0 */
#define _IO_CLOSED 0
#define _IO_ERROR (-1)
#define _IO_TIMEOUT (-2)
#define _IO_STOPPED (-3)

/* one peer address and its open connections */
typedef struct {
    char ip[64];
    size_t count;
} _ip_slot;

struct N_HTTP_SERVER {
    N_HTTP_SERVER_CONFIG cfg;
    NETWORK* listen;
#ifdef HAVE_OPENSSL
    SSL_CTX* ctx;
#endif
    atomic_int stop;
    pthread_mutex_t lock;
    pthread_cond_t cond;
    NETWORK** queue; /* accepted connections waiting for a worker */
    size_t qhead, qlen;
    pthread_t* threads;
    size_t nb_threads;
    /* peer addresses with their open connection count (per-IP cap) */
    _ip_slot* ips;
    N_HTTP_SERVER_STATS stats;
};

typedef struct {
    N_HTTP_SERVER* srv;
    NETWORK* netw;
#ifdef HAVE_OPENSSL
    SSL* ssl;
#endif
    char* buf;
    size_t len;
    long long deadline; /* current operation's absolute deadline, ms */
    int idle_phase;     /* a stop may interrupt the current wait */
    /* the request being served */
    size_t head_len;
    long long body_left; /* body bytes not read yet */
    int body_read;       /* the handler asked for the body */
} _conn;

/* Monotonic clock in ms. */
static long long _now_ms(void) {
#ifdef __windows__
    return (long long)GetTickCount64();
#else
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
#endif
}

static void _stat_add(N_HTTP_SERVER* srv, unsigned long long* field, long long delta) {
    pthread_mutex_lock(&srv->lock);
    *field = (unsigned long long)((long long)*field + delta);
    pthread_mutex_unlock(&srv->lock);
}

/* Wait until the socket is readable (or writable), the deadline passes, or a
 * stop is requested during an idle wait. Returns 1, _IO_TIMEOUT, _IO_STOPPED
 * or _IO_ERROR. */
static int _wait(_conn* c, int for_write) {
    for (;;) {
        long long left = c->deadline - _now_ms();
        int slice, rc;
        if (c->idle_phase && atomic_load(&c->srv->stop))
            return _IO_STOPPED;
        if (left <= 0)
            return _IO_TIMEOUT;
        slice = left > N_HTTP_SERVER_SLICE_MS ? N_HTTP_SERVER_SLICE_MS : (int)left;
#ifdef __windows__
        {
            WSAPOLLFD p;
            p.fd = c->netw->link.sock;
            p.events = for_write ? POLLWRNORM : POLLRDNORM;
            p.revents = 0;
            rc = WSAPoll(&p, 1, slice);
        }
#else
        {
            struct pollfd p;
            p.fd = (int)c->netw->link.sock;
            p.events = for_write ? POLLOUT : POLLIN;
            p.revents = 0;
            rc = poll(&p, 1, slice);
        }
#endif
        if (rc > 0)
            return 1;
        if (rc < 0 && !_SOCK_INTR(_SOCK_ERR()))
            return _IO_ERROR;
    }
}

/* Read at most cap bytes. Returns > 0, _IO_CLOSED, or a negative _IO_ code. */
static long long _recv(_conn* c, char* dst, size_t cap) {
    if (cap > 1 << 30)
        cap = 1 << 30;
    for (;;) {
#ifdef HAVE_OPENSSL
        if (c->ssl) {
            int n = SSL_read(c->ssl, dst, (int)cap), err;
            if (n > 0)
                return n;
            err = SSL_get_error(c->ssl, n);
            if (err == SSL_ERROR_ZERO_RETURN)
                return _IO_CLOSED;
            if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
                int w = _wait(c, err == SSL_ERROR_WANT_WRITE);
                if (w != 1)
                    return w;
                continue;
            }
            ERR_clear_error();
            return err == SSL_ERROR_SYSCALL && n == 0 ? _IO_CLOSED : _IO_ERROR;
        }
#endif
        {
            ssize_t n = (ssize_t)recv(c->netw->link.sock, dst, _SOCK_LEN(cap), 0);
            int w, e;
            if (n > 0)
                return n;
            if (n == 0)
                return _IO_CLOSED;
            e = _SOCK_ERR();
            if (_SOCK_INTR(e))
                continue;
            if (!_SOCK_AGAIN(e))
                return _IO_ERROR;
            w = _wait(c, 0);
            if (w != 1)
                return w;
        }
    }
}

/* Discard a SIGPIPE raised by our own write on a closed socket (it is
 * blocked in worker threads, so it stays pending in this thread). */
static void _drop_sigpipe(void) {
#if !defined(__windows__) && defined(__linux__)
    sigset_t set;
    struct timespec zero = {0, 0};
    sigemptyset(&set);
    sigaddset(&set, SIGPIPE);
    while (sigtimedwait(&set, NULL, &zero) > 0);
#endif
}

/* Write function for n_http_response_write(). */
static ssize_t _write_fn(void* ctx, const void* buf, size_t len) {
    _conn* c = (_conn*)ctx;
    if (len > 1 << 30)
        len = 1 << 30;
    for (;;) {
#ifdef HAVE_OPENSSL
        if (c->ssl) {
            int n = SSL_write(c->ssl, buf, (int)len), err;
            if (n > 0)
                return n;
            err = SSL_get_error(c->ssl, n);
            if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
                int w = _wait(c, err == SSL_ERROR_WANT_WRITE);
                if (w != 1)
                    return -1;
                continue;
            }
            ERR_clear_error();
            _drop_sigpipe();
            return -1;
        }
#endif
        {
#ifdef MSG_NOSIGNAL
            ssize_t n = (ssize_t)send(c->netw->link.sock, buf, _SOCK_LEN(len), MSG_NOSIGNAL);
#else
            ssize_t n = (ssize_t)send(c->netw->link.sock, (const char*)buf, _SOCK_LEN(len), 0);
#endif
            int e = n < 0 ? _SOCK_ERR() : 0;
            if (n > 0)
                return n;
            if (n < 0 && _SOCK_INTR(e))
                continue;
            if (n < 0 && _SOCK_AGAIN(e)) {
                if (_wait(c, 1) != 1)
                    return -1;
                continue;
            }
            _drop_sigpipe();
            return -1;
        }
    }
}

/* Before each piece of a streamed body: the write deadline counts from it. */
static void _stream_piece(void* ctx) {
    _conn* c = (_conn*)ctx;
    c->deadline = _now_ms() + c->srv->cfg.write_timeout_ms;
}

/* Send a short server-generated response and mark the connection closed. */
static void _send_error(_conn* c, int status) {
    N_HTTP_RESP* r = n_http_response_new(status);
    char body[96];
    if (!r)
        return;
    snprintf(body, sizeof(body), "%d %s\n", status, n_http_status_reason(status));
    n_http_response_set_header(r, "Content-Type", "text/plain; charset=utf-8");
    n_http_response_set_header(r, "Cache-Control", "no-store");
    n_http_response_set_header(r, "X-Content-Type-Options", "nosniff");
    n_http_response_append(r, body, strlen(body));
    n_http_response_set_connection(r, 0, 1, 0);
    c->deadline = _now_ms() + c->srv->cfg.write_timeout_ms;
    c->idle_phase = 0;
    n_http_response_write(r, _write_fn, c);
    n_http_response_free(&r);
}

#ifdef HAVE_OPENSSL
/* Non-blocking TLS handshake under the handshake deadline. */
static int _handshake(_conn* c) {
    c->ssl = SSL_new(c->srv->ctx);
    if (!c->ssl)
        return FALSE;
    SSL_set_fd(c->ssl, (int)c->netw->link.sock);
    c->deadline = _now_ms() + c->srv->cfg.handshake_timeout_ms;
    c->idle_phase = 1;
    for (;;) {
        int rc = SSL_accept(c->ssl), err, w;
        if (rc == 1)
            return TRUE;
        err = SSL_get_error(c->ssl, rc);
        if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
            ERR_clear_error();
            _drop_sigpipe();
            return FALSE;
        }
        w = _wait(c, err == SSL_ERROR_WANT_WRITE);
        if (w != 1) {
            if (w == _IO_TIMEOUT)
                _stat_add(c->srv, &c->srv->stats.timeouts, 1);
            return FALSE;
        }
    }
}
#endif

/* Header value equals token, ASCII case-insensitive (Connection, Expect). */
static int _value_is(const char* v, size_t n, const char* token) {
    size_t i, t = strlen(token);
    if (!v || n != t)
        return 0;
    for (i = 0; i < n; i++) {
        char a = v[i], b = token[i];
        if (a >= 'A' && a <= 'Z')
            a = (char)(a - 'A' + 'a');
        if (a != b)
            return 0;
    }
    return 1;
}

/* Read up to want body bytes into dst: first the bytes already buffered after
 * the head, then from the socket. Returns the count or a negative _IO_ code. */
static long long _body_chunk(_conn* c, char* dst, size_t want) {
    size_t avail = c->len - c->head_len;
    if (avail > 0) {
        size_t take = avail < want ? avail : want;
        memcpy(dst, c->buf + c->head_len, take);
        memmove(c->buf + c->head_len, c->buf + c->head_len + take, avail - take);
        c->len -= take;
        return (long long)take;
    }
    return _recv(c, dst, want);
}

/* Common checks of the two body readers. Returns TRUE to go on. */
static int _body_start(N_HTTP_SERVER_REQUEST* req, long long max) {
    _conn* c = (_conn*)req->conn;
    req->body_status = 0;
    if (c->body_read) {
        req->body_status = 500;
        return FALSE;
    }
    c->body_read = 1;
    if (req->head.content_length > max || req->head.content_length > c->srv->cfg.max_body) {
        req->body_status = 413;
        return FALSE;
    }
    {
        size_t elen = 0;
        const char* e = n_http_server_header(req, "expect", &elen);
        if (e && c->body_left > 0) {
            static const char cont[] = "HTTP/1.1 100 Continue\r\n\r\n";
            c->deadline = _now_ms() + c->srv->cfg.write_timeout_ms;
            c->idle_phase = 0;
            if (_write_fn(c, cont, sizeof(cont) - 1) != (ssize_t)(sizeof(cont) - 1)) {
                req->body_status = 400;
                return FALSE;
            }
        }
    }
    c->deadline = _now_ms() + c->srv->cfg.body_timeout_ms;
    c->idle_phase = 0;
    return TRUE;
}

static int _body_fail(N_HTTP_SERVER_REQUEST* req, long long rc) {
    _conn* c = (_conn*)req->conn;
    if (rc == _IO_TIMEOUT) {
        req->body_status = 408;
        _stat_add(c->srv, &c->srv->stats.timeouts, 1);
    } else {
        req->body_status = 400;
    }
    return FALSE;
}

int n_http_server_read_body(N_HTTP_SERVER_REQUEST* req, long long max, N_STR** out) {
    _conn* c;
    __n_assert(req, return FALSE);
    __n_assert(out, return FALSE);
    c = (_conn*)req->conn;
    if (!_body_start(req, max))
        return FALSE;
    if (!*out) {
        *out = new_nstr((size_t)req->head.content_length + 1);
        if (!*out) {
            req->body_status = 500;
            return FALSE;
        }
    } else if (resize_nstr(*out, (*out)->written + (size_t)req->head.content_length + 1) != TRUE) {
        req->body_status = 500;
        return FALSE;
    }
    while (c->body_left > 0) {
        long long n = _body_chunk(c, (*out)->data + (*out)->written, (size_t)c->body_left);
        if (n <= 0)
            return _body_fail(req, n);
        (*out)->written += (size_t)n;
        (*out)->data[(*out)->written] = '\0';
        c->body_left -= n;
    }
    return TRUE;
}

int n_http_server_read_body_to_fd(N_HTTP_SERVER_REQUEST* req, long long max, int fd) {
    _conn* c;
    char chunk[16384];
    __n_assert(req, return FALSE);
    c = (_conn*)req->conn;
    if (fd < 0) {
        req->body_status = 500;
        return FALSE;
    }
    if (!_body_start(req, max))
        return FALSE;
    while (c->body_left > 0) {
        size_t want = c->body_left < (long long)sizeof(chunk) ? (size_t)c->body_left : sizeof(chunk);
        long long n = _body_chunk(c, chunk, want);
        size_t done = 0;
        if (n <= 0)
            return _body_fail(req, n);
        while (done < (size_t)n) {
            ssize_t w = write(fd, chunk + done, (size_t)n - done);
            if (w < 0 && errno == EINTR)
                continue;
            if (w <= 0) {
                req->body_status = 500;
                return FALSE;
            }
            done += (size_t)w;
        }
        c->body_left -= n;
    }
    return TRUE;
}

const char* n_http_server_header(const N_HTTP_SERVER_REQUEST* req, const char* name, size_t* len) {
    if (!req)
        return NULL;
    return n_http_request_header(&req->head, req->raw, name, len);
}

/* Is there a CRLFCRLF in the first n bytes? (a head too long ends without one) */
static int _has_head_end(const char* b, size_t n) {
    size_t i;
    for (i = 0; i + 3 < n; i++)
        if (b[i] == '\r' && b[i + 1] == '\n' && b[i + 2] == '\r' && b[i + 3] == '\n')
            return 1;
    return 0;
}

/* Does the buffer start with a well-formed method other than GET, HEAD and
 * POST? RFC 9110 section 9.1: such a method is answered 501, not 400. */
static int _unsupported_method(const char* b, size_t n) {
    size_t i;
    for (i = 0; i < n && i <= 20; i++) {
        if (b[i] == ' ')
            break;
        if (!((b[i] >= 'A' && b[i] <= 'Z') || b[i] == '-' || b[i] == '_'))
            return 0;
    }
    if (i == 0 || i >= n || b[i] != ' ')
        return 0;
    return !((i == 3 && memcmp(b, "GET", 3) == 0) || (i == 4 && (memcmp(b, "HEAD", 4) == 0 || memcmp(b, "POST", 4) == 0)));
}

/* Is the head complete, with exactly one Content-Length, a well-formed
 * number above every body cap (N_HTTP_REQ_BODY_LIMIT_MAX)? RFC 9110 section
 * 15.5.14: too large a body is a 413, not a malformed request. */
static int _content_too_large(const char* b, size_t n) {
    size_t i = 0, found = 0, end;
    int too_large = 0;
    for (end = 0; end + 3 < n; end++)
        if (b[end] == '\r' && b[end + 1] == '\n' && b[end + 2] == '\r' && b[end + 3] == '\n')
            break;
    if (end + 3 >= n)
        return 0;
    /* skip the request line */
    while (i + 1 < end && !(b[i] == '\r' && b[i + 1] == '\n'))
        i++;
    while (i + 2 <= end) {
        size_t ls = i + 2, le = ls, v, ve, digits = 0;
        unsigned long long val = 0;
        while (le + 1 < end + 2 && !(b[le] == '\r' && b[le + 1] == '\n'))
            le++;
        i = le;
        if (le - ls < 15 || strncasecmp(b + ls, "content-length:", 15) != 0)
            continue;
        found++;
        v = ls + 15;
        ve = le;
        while (v < ve && (b[v] == ' ' || b[v] == '\t'))
            v++;
        while (ve > v && (b[ve - 1] == ' ' || b[ve - 1] == '\t'))
            ve--;
        if (v == ve)
            return 0;
        for (; v < ve; v++) {
            if (b[v] < '0' || b[v] > '9')
                return 0;
            if (++digits > 19) {
                too_large = 1;
                continue;
            }
            val = val * 10u + (unsigned)(b[v] - '0');
        }
        if (val > (unsigned long long)N_HTTP_REQ_BODY_LIMIT_MAX)
            too_large = 1;
        if (i >= end)
            break;
    }
    return found == 1 && too_large;
}

/* Serve every request of one connection. */
static void _serve(_conn* c) {
    N_HTTP_SERVER* srv = c->srv;
    n_http_req_limits lim;
    size_t served = 0;

    lim.methods = N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_GET) | N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_HEAD) | N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_POST);
    lim.body_max = N_HTTP_REQ_BODY_LIMIT_MAX;
    lim.require_host = srv->cfg.require_host;

    while (served < srv->cfg.max_requests_per_conn) {
        N_HTTP_SERVER_REQUEST* req = NULL;
        N_HTTP_RESP* resp = NULL;
        n_http_req_status st;
        int keep, got_first = c->len > 0;
        size_t clen = 0;
        const char* conn_hdr;

        /* wait for, then read, the whole head */
        c->idle_phase = !got_first;
        c->deadline = _now_ms() + (got_first ? srv->cfg.header_timeout_ms : (served == 0 ? srv->cfg.header_timeout_ms : srv->cfg.idle_timeout_ms));
        Malloc(req, N_HTTP_SERVER_REQUEST, 1);
        if (!req)
            return;
        for (;;) {
            long long n;
            st = n_http_request_parse_ex(c->buf, c->len, &lim, &req->head);
            if (st != N_HTTP_REQ_INCOMPLETE)
                break;
            n = _recv(c, c->buf + c->len, N_HTTP_SERVER_BUF - c->len);
            if (n <= 0) {
                if (n == _IO_TIMEOUT && got_first) {
                    _stat_add(srv, &srv->stats.timeouts, 1);
                    _send_error(c, 408);
                } else if (n == _IO_TIMEOUT) {
                    _stat_add(srv, &srv->stats.timeouts, 1);
                }
                Free(req);
                return;
            }
            if (!got_first) {
                got_first = 1;
                c->idle_phase = 0;
                c->deadline = _now_ms() + srv->cfg.header_timeout_ms;
            }
            c->len += (size_t)n;
        }
        if (st == N_HTTP_REQ_ERROR) {
            int status = 400;
            if (c->len >= N_HTTP_REQ_HEAD_MAX && !_has_head_end(c->buf, N_HTTP_REQ_HEAD_MAX))
                status = 431;
            else if (_unsupported_method(c->buf, c->len))
                status = 501;
            else if (_content_too_large(c->buf, c->len))
                status = 413;
            _stat_add(srv, &srv->stats.bad_requests, 1);
            _send_error(c, status);
            Free(req);
            return;
        }

        /* the request as the handler sees it */
        c->head_len = req->head.head_len;
        c->body_left = req->head.content_length;
        c->body_read = 0;
        req->raw = c->buf;
        req->peer_ip = c->netw->link.ip ? c->netw->link.ip : "";
#ifdef HAVE_OPENSSL
        req->tls = c->ssl != NULL;
#endif
        req->conn = c;
        {
            const char* q = strchr(req->head.path, '?');
            size_t plen = q ? (size_t)(q - req->head.path) : strlen(req->head.path);
            memcpy(req->target, req->head.path, plen);
            req->target[plen] = '\0';
            req->path = req->target;
            req->path_len = plen;
            if (q) {
                size_t qlen = strlen(q + 1);
                memcpy(req->target + plen + 1, q + 1, qlen + 1);
                req->query = req->target + plen + 1;
                req->query_len = qlen;
            } else {
                req->target[plen + 1] = '\0';
                req->query = req->target + plen + 1;
                req->query_len = 0;
            }
        }
        if (req->head.content_length > srv->cfg.max_body) {
            _stat_add(srv, &srv->stats.bad_requests, 1);
            _send_error(c, 413);
            Free(req);
            return;
        }
        {
            size_t elen = 0;
            const char* e = n_http_server_header(req, "expect", &elen);
            if (e && !_value_is(e, elen, "100-continue")) {
                _stat_add(srv, &srv->stats.bad_requests, 1);
                _send_error(c, 417);
                Free(req);
                return;
            }
        }

        resp = srv->cfg.handler ? srv->cfg.handler(req, srv->cfg.user) : NULL;
        if (!resp)
            resp = n_http_response_new(500);
        if (!resp) {
            Free(req);
            return;
        }

        /* keep the connection only when the request was fully consumed */
        conn_hdr = n_http_server_header(req, "connection", &clen);
        if (req->head.version_minor == 1)
            keep = !_value_is(conn_hdr, clen, "close");
        else
            keep = _value_is(conn_hdr, clen, "keep-alive");
        if (c->body_left > 0 || atomic_load(&srv->stop) || served + 1 >= srv->cfg.max_requests_per_conn || resp->status == 400 ||
            resp->status == 408 || resp->status == 413)
            keep = 0;
        n_http_response_set_connection(resp, keep, req->head.version_minor, req->head.method == N_HTTP_REQ_HEAD);
        c->deadline = _now_ms() + srv->cfg.write_timeout_ms;
        c->idle_phase = 0;
        if (!n_http_response_write_ex(resp, _write_fn, c, _stream_piece))
            keep = 0;
        /* an HTTP/1.0 streamed body ended by closing */
        if (resp->stream && req->head.version_minor == 0)
            keep = 0;
        _stat_add(srv, &srv->stats.requests, 1);
        n_http_response_free(&resp);
        Free(req);
        served++;
        if (!keep)
            return;
        /* drop this request's head; any bytes after it are the next request */
        memmove(c->buf, c->buf + c->head_len, c->len - c->head_len);
        c->len -= c->head_len;
    }
}

/* Per-IP accounting, under srv->lock. Returns TRUE when the address may open
 * one more connection. There are 2 * workers slots, more than the connections
 * that can be open or queued at once. */
static int _ip_take(N_HTTP_SERVER* srv, const char* ip) {
    size_t i, slots = srv->cfg.workers * 2, free_slot = slots;
    if (!srv->cfg.max_per_ip)
        return TRUE;
    for (i = 0; i < slots; i++) {
        if (srv->ips[i].count && strcmp(srv->ips[i].ip, ip) == 0) {
            if (srv->ips[i].count >= srv->cfg.max_per_ip)
                return FALSE;
            srv->ips[i].count++;
            return TRUE;
        }
        if (!srv->ips[i].count && free_slot == slots)
            free_slot = i;
    }
    if (free_slot == slots)
        return FALSE;
    snprintf(srv->ips[free_slot].ip, sizeof(srv->ips[free_slot].ip), "%s", ip);
    srv->ips[free_slot].count = 1;
    return TRUE;
}

static void _ip_release(N_HTTP_SERVER* srv, const char* ip) {
    size_t i;
    if (!srv->cfg.max_per_ip)
        return;
    for (i = 0; i < srv->cfg.workers * 2; i++)
        if (srv->ips[i].count && strcmp(srv->ips[i].ip, ip) == 0) {
            srv->ips[i].count--;
            return;
        }
}

static void* _worker(void* arg) {
    N_HTTP_SERVER* srv = (N_HTTP_SERVER*)arg;
    char* buf = NULL;
#if !defined(__windows__)
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGPIPE);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
#endif
    Malloc(buf, char, N_HTTP_SERVER_BUF + 1);
    if (!buf)
        return NULL;
    for (;;) {
        NETWORK* netw = NULL;
        _conn c;
        pthread_mutex_lock(&srv->lock);
        while (srv->qlen == 0 && !atomic_load(&srv->stop))
            pthread_cond_wait(&srv->cond, &srv->lock);
        if (srv->qlen == 0) {
            pthread_mutex_unlock(&srv->lock);
            break;
        }
        netw = srv->queue[srv->qhead];
        srv->qhead = (srv->qhead + 1) % srv->cfg.workers;
        srv->qlen--;
        srv->stats.active++;
        pthread_mutex_unlock(&srv->lock);

        memset(&c, 0, sizeof(c));
        c.srv = srv;
        c.netw = netw;
        c.buf = buf;
        netw_set_blocking(netw, 0);
#ifdef HAVE_OPENSSL
        if (srv->ctx && !_handshake(&c)) {
            _stat_add(srv, &srv->stats.tls_failures, 1);
        } else
#endif
        {
            _serve(&c);
        }
#ifdef HAVE_OPENSSL
        if (c.ssl) {
            SSL_shutdown(c.ssl); /* one try: the socket is non-blocking */
            ERR_clear_error();
            _drop_sigpipe();
            SSL_free(c.ssl);
        }
#endif
        pthread_mutex_lock(&srv->lock);
        _ip_release(srv, netw->link.ip ? netw->link.ip : "");
        srv->stats.active--;
        pthread_mutex_unlock(&srv->lock);
        netw_close(&netw);
    }
    Free(buf);
    return NULL;
}

void n_http_server_config_defaults(N_HTTP_SERVER_CONFIG* cfg) {
    __n_assert(cfg, return);
    memset(cfg, 0, sizeof(*cfg));
    cfg->addr = "127.0.0.1";
    cfg->port = "8080";
    cfg->workers = 32;
    cfg->handshake_timeout_ms = 10000;
    cfg->header_timeout_ms = 10000;
    cfg->idle_timeout_ms = 15000;
    cfg->body_timeout_ms = 60000;
    cfg->write_timeout_ms = 60000;
    cfg->max_requests_per_conn = 1000;
    cfg->max_body = 32LL * 1024 * 1024;
    cfg->require_host = 1;
}

N_HTTP_SERVER* n_http_server_new(const N_HTTP_SERVER_CONFIG* cfg) {
    N_HTTP_SERVER* srv = NULL;
    __n_assert(cfg, return NULL);
    if (!cfg->addr || !cfg->port || !cfg->handler || cfg->workers == 0 || cfg->workers > 4096 || cfg->handshake_timeout_ms <= 0 ||
        cfg->header_timeout_ms <= 0 || cfg->idle_timeout_ms <= 0 || cfg->body_timeout_ms <= 0 || cfg->write_timeout_ms <= 0 ||
        cfg->max_requests_per_conn == 0 || cfg->max_body < 0 || cfg->max_body > N_HTTP_REQ_BODY_LIMIT_MAX ||
        (!cfg->tls_key_file) != (!cfg->tls_cert_file)) {
        n_log(LOG_ERR, "http server: invalid configuration");
        return NULL;
    }
    Malloc(srv, N_HTTP_SERVER, 1);
    __n_assert(srv, return NULL);
    srv->cfg = *cfg;
    atomic_init(&srv->stop, 0);
    pthread_mutex_init(&srv->lock, NULL);
    pthread_cond_init(&srv->cond, NULL);
    Malloc(srv->queue, NETWORK*, cfg->workers);
    Malloc(srv->threads, pthread_t, cfg->workers);
    Malloc(srv->ips, _ip_slot, cfg->workers * 2);
    if (!srv->queue || !srv->threads || !srv->ips) {
        n_http_server_free(&srv);
        return NULL;
    }
    if (cfg->tls_key_file) {
#ifdef HAVE_OPENSSL
        srv->ctx = netw_ssl_server_ctx_new(cfg->tls_key_file, cfg->tls_cert_file);
        if (!srv->ctx) {
            n_http_server_free(&srv);
            return NULL;
        }
#else
        n_log(LOG_ERR, "http server: TLS requested but the library was built without OpenSSL");
        n_http_server_free(&srv);
        return NULL;
#endif
    }
    if (netw_make_listening(&srv->listen, (char*)cfg->addr, (char*)cfg->port, 128, NETWORK_IPALL) != TRUE || !srv->listen) {
        n_log(LOG_ERR, "http server: cannot listen on %s:%s", cfg->addr, cfg->port);
        n_http_server_free(&srv);
        return NULL;
    }
    return srv;
}

void n_http_server_stop(N_HTTP_SERVER* srv) {
    if (srv)
        atomic_store(&srv->stop, 1);
}

int n_http_server_run(N_HTTP_SERVER* srv) {
    size_t i;
    __n_assert(srv, return FALSE);
    __n_assert(srv->listen, return FALSE);
    for (i = 0; i < srv->cfg.workers; i++) {
        if (pthread_create(&srv->threads[i], NULL, _worker, srv) != 0) {
            n_log(LOG_ERR, "http server: cannot start worker %zu", i);
            atomic_store(&srv->stop, 1);
            break;
        }
        srv->nb_threads++;
    }
    while (!atomic_load(&srv->stop)) {
        int rv = 0;
        NETWORK* netw = netw_accept_from_ex(srv->listen, 0, 0, N_HTTP_SERVER_ACCEPT_MS, &rv);
        int queued = 0;
        if (!netw)
            continue;
        pthread_mutex_lock(&srv->lock);
        srv->stats.accepted++;
        if (srv->qlen < srv->cfg.workers && !atomic_load(&srv->stop) && _ip_take(srv, netw->link.ip ? netw->link.ip : "")) {
            srv->queue[(srv->qhead + srv->qlen) % srv->cfg.workers] = netw;
            srv->qlen++;
            queued = 1;
            pthread_cond_signal(&srv->cond);
        } else {
            srv->stats.refused++;
        }
        pthread_mutex_unlock(&srv->lock);
        if (!queued) {
            /* over capacity: a plain-HTTP peer gets a 503, a TLS one a reset */
            if (
#ifdef HAVE_OPENSSL
                !srv->ctx
#else
                1
#endif
            ) {
                static const char busy[] = "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\nConnection: close\r\nRetry-After: 1\r\n\r\n";
#if defined(MSG_NOSIGNAL) && defined(MSG_DONTWAIT)
                /* mingw-w64 defines MSG_NOSIGNAL but not MSG_DONTWAIT */
                send(netw->link.sock, busy, sizeof(busy) - 1, MSG_NOSIGNAL | MSG_DONTWAIT);
#else
                send(netw->link.sock, busy, sizeof(busy) - 1, 0);
#endif
            }
            netw_close(&netw);
        }
    }
    pthread_mutex_lock(&srv->lock);
    pthread_cond_broadcast(&srv->cond);
    pthread_mutex_unlock(&srv->lock);
    for (i = 0; i < srv->nb_threads; i++)
        pthread_join(srv->threads[i], NULL);
    srv->nb_threads = 0;
    /* connections still queued when the workers left */
    while (srv->qlen > 0) {
        netw_close(&srv->queue[srv->qhead]);
        srv->qhead = (srv->qhead + 1) % srv->cfg.workers;
        srv->qlen--;
    }
    return TRUE;
}

void n_http_server_free(N_HTTP_SERVER** srv) {
    if (!srv || !*srv)
        return;
    if ((*srv)->listen)
        netw_close(&(*srv)->listen);
#ifdef HAVE_OPENSSL
    if ((*srv)->ctx)
        SSL_CTX_free((*srv)->ctx);
#endif
    FreeNoLog((*srv)->queue);
    FreeNoLog((*srv)->threads);
    FreeNoLog((*srv)->ips);
    pthread_mutex_destroy(&(*srv)->lock);
    pthread_cond_destroy(&(*srv)->cond);
    FreeNoLog(*srv);
}

void n_http_server_stats(N_HTTP_SERVER* srv, N_HTTP_SERVER_STATS* out) {
    __n_assert(srv, return);
    __n_assert(out, return);
    pthread_mutex_lock(&srv->lock);
    *out = srv->stats;
    pthread_mutex_unlock(&srv->lock);
}