Files
cHTTP/tinyhttp.c
T
2025-04-24 22:00:30 +02:00

2605 lines
64 KiB
C

////////////////////////////////////////////////////////////////////////////
// LICENSE //
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2025 Francesco Cozzuto
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////
#include "tinyhttp.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#if TINYHTTP_SERVER_ENABLE
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#define CLOSESOCKET closesocket
#elif defined(__linux__)
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#define SOCKET int
#define CLOSESOCKET close
#define INVALID_SOCKET -1
#endif
#endif
#define MAX_U16 ((unsigned short) -1)
#define MAX_U32 ((unsigned int) -1)
#define MAX_U64 ((unsigned long long) -1ULL)
#define ASSERT(X) {if (!(X)) __builtin_trap();}
#define COUNTOF(X) (sizeof(X)/sizeof((X)[0]))
#define DUMP_IO 0
#if TINYHTTP_ROUTER_ENABLE
#error "The router interface isn't ready yet"
#endif
#if TINYHTTP_HTTPS_ENABLE
#error "HTTPS is not available yet"
#endif
////////////////////////////////////////////////////////////////////////////////////
// UTILITIES //
////////////////////////////////////////////////////////////////////////////////////
// TODO: Only allow this when not compiled in stand-alone mode
static int print(const char *src, int len)
{
if (len < 0)
len = strlen(src);
#if defined(_WIN32)
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
int num = 0;
while (num < len) {
DWORD cpy;
if (!WriteFile(handle, src + num, len - num, &cpy, NULL))
return -1;
num += cpy;
}
return 0;
#elif defined(__linux__)
int fd = STDOUT_FILENO;
int num = 0;
while (num < len) {
int ret = write(fd, src + num, len - num);
if (ret < 0) {
if (ret == EINTR)
continue;
return -1;
}
num += ret;
}
return 0;
#endif
}
void tinyhttp_printbytes(char *prefix, const char *src, int len)
{
if (src == NULL) {
print(prefix, -1);
print(" (null)\n", -1);
return;
}
int cur = 0;
int newline = 1;
while (cur < len) {
int start = cur;
while (cur < len && src[cur] != '\n' && src[cur] != '\r')
cur++;
if (newline) {
print(prefix, -1);
newline = 0;
}
print(src + start, cur - start);
if (cur < len) {
if (src[cur] == '\r')
print("\\r", -1);
else {
print("\\n\n", -1);
newline = 1;
}
cur++;
}
}
if (cur > 0 && src[cur-1] != '\n')
print("\n", -1);
}
void tinyhttp_printstate_(int state, const char *file, const char *line)
{
print("state = ", -1);
if (state & TINYHTTP_STREAM_DIED)
print("DIED ", -1);
if (state & TINYHTTP_STREAM_SEND)
print("SEND ", -1);
if (state & TINYHTTP_STREAM_RECV)
print("RECV ", -1);
if (state & TINYHTTP_STREAM_READY)
print("READY ", -1);
if (state & TINYHTTP_STREAM_CLOSE)
print("CLOSE ", -1);
if (state & TINYHTTP_STREAM_REUSE)
print("REUSE ", -1);
if (state & TINYHTTP_STREAM_SEND_STARTED)
print("SEND_STARTED ", -1);
if (state & TINYHTTP_STREAM_RECV_STARTED)
print("RECV_STARTED ", -1);
print(" (in ", -1);
print(file, -1);
print(":", -1);
print(line, -1);
print(")\n", -1);
}
static int is_digit(char c)
{
return c >= '0' && c <= '9';
}
static int is_hex_digit(char c)
{
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
static int hex_digit_to_int(char c)
{
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return c - '0';
}
static char to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
return c;
}
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2)
{
if (s1.len != s2.len)
return 0;
for (int i = 0; i < s1.len; i++)
if (s1.ptr[i] != s2.ptr[i])
return 0;
return 1;
}
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2)
{
if (s1.len != s2.len)
return 0;
for (int i = 0; i < s1.len; i++)
if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i]))
return 0;
return 1;
}
static TinyHTTPString trim(TinyHTTPString s)
{
int i = 0;
while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t'))
i++;
if (i == s.len) {
s.ptr = NULL;
s.len = 0;
} else {
s.ptr += i;
s.len -= i;
while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\n')
s.len--;
}
return s;
}
////////////////////////////////////////////////////////////////////////////////////
// HTTP REQUEST PARSER //
////////////////////////////////////////////////////////////////////////////////////
static ptrdiff_t
parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
{
ptrdiff_t off;
ptrdiff_t cur = 0;
int found = 0;
for (ptrdiff_t peek = 0; len - peek > 3; peek++) {
if (src[peek+0] == '\r' &&
src[peek+1] == '\n' && // Boyer-Moore?
src[peek+2] == '\r' &&
src[peek+3] == '\n') {
found = 1;
break;
}
}
if (!found)
return 0;
if (len - cur > 3
&& src[cur+0] == 'G'
&& src[cur+1] == 'E'
&& src[cur+2] == 'T'
&& src[cur+3] == ' ') {
cur += 4;
req->method = TINYHTTP_METHOD_GET;
} else if (len - cur > 4
&& src[cur+0] == 'P'
&& src[cur+1] == 'O'
&& src[cur+2] == 'S'
&& src[cur+3] == 'T'
&& src[cur+4] == ' ') {
cur += 5;
req->method = TINYHTTP_METHOD_POST;
} else
return -405;
off = cur;
while (cur < len && src[cur] != ' ') // TODO: More robust
cur++;
req->path = (TinyHTTPString) { src + off, cur - off };
if (len - cur <= 5
|| src[cur+0] != ' '
|| src[cur+1] != 'H'
|| src[cur+2] != 'T'
|| src[cur+3] != 'T'
|| src[cur+4] != 'P'
|| src[cur+5] != '/')
return -400;
cur += 6;
if (3 < len - cur
&& src[cur+0] == '1'
&& src[cur+1] == '.'
&& src[cur+2] == '1'
&& src[cur+3] == '\r'
&& src[cur+4] == '\n') {
cur += 5;
req->minor = 1;
} else if (4 < len - cur
&& src[cur+0] == '1'
&& src[cur+1] == '.'
&& src[cur+2] == '0'
&& src[cur+3] == '\r'
&& src[cur+4] == '\n') {
cur += 5;
req->minor = 0;
} else if (2 < len - cur
&& src[cur+0] == '1'
&& src[cur+1] == '\r'
&& src[cur+2] == '\n') {
cur += 3;
req->minor = 0;
} else {
return -505;
}
req->num_headers = 0;
while (len - cur < 2 || src[cur+0] != '\r' || src[cur+1] != '\n') {
ptrdiff_t name_off = cur;
while (cur < len && src[cur] != ':') // TODO: robust
cur++;
TinyHTTPString name = { src + name_off, cur - name_off };
name = trim(name);
if (cur == len)
return -400;
cur++;
ptrdiff_t value_off = cur;
while (cur < len && src[cur] != '\r')
cur++;
TinyHTTPString value = { src + value_off, cur - value_off };
value = trim(value);
if (cur == len)
return -400;
cur++;
if (cur == len || src[cur] != '\n')
return -400;
cur++;
// TODO: Validate name and value:
// 1) No spaces are allowed after the name
// 2) Spaces should be trimmed from the value
if (req->num_headers < TINYHTTP_HEADER_LIMIT)
req->headers[req->num_headers++] = (TinyHTTPHeader) { name, value };
}
cur += 2;
return cur;
}
int tinyhttp_findheader(TinyHTTPRequest *req, TinyHTTPString name)
{
for (int i = 0; i < req->num_headers; i++) {
TinyHTTPHeader *header = &req->headers[i];
if (tinyhttp_streqcase(header->name, name))
return i;
}
return -1;
}
enum {
TRANSFER_ENCODING_CHUNKED,
TRANSFER_ENCODING_COMPRESS,
TRANSFER_ENCODING_DEFLATE,
TRANSFER_ENCODING_GZIP,
};
static int
parse_transfer_encoding(const char *src, ptrdiff_t len, int *items, int max)
{
int num = 0;
ptrdiff_t cur = 0;
for (;;) {
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
cur++;
if (6 < len - cur
&& to_lower(src[cur+0]) == 'c'
&& to_lower(src[cur+1]) == 'h'
&& to_lower(src[cur+2]) == 'u'
&& to_lower(src[cur+3]) == 'n'
&& to_lower(src[cur+4]) == 'k'
&& to_lower(src[cur+5]) == 'e'
&& to_lower(src[cur+6]) == 'd') {
if (num == max)
return -1;
items[num++] = TRANSFER_ENCODING_CHUNKED;
cur += 7;
} else if (7 < len - cur
&& to_lower(src[cur+0]) == 'c'
&& to_lower(src[cur+1]) == 'o'
&& to_lower(src[cur+2]) == 'm'
&& to_lower(src[cur+3]) == 'p'
&& to_lower(src[cur+4]) == 'r'
&& to_lower(src[cur+5]) == 'e'
&& to_lower(src[cur+6]) == 's'
&& to_lower(src[cur+7]) == 's') {
if (num == max)
return -1;
items[num++] = TRANSFER_ENCODING_COMPRESS;
cur += 8;
} else if (6 < len - cur
&& to_lower(src[cur+0]) == 'd'
&& to_lower(src[cur+1]) == 'e'
&& to_lower(src[cur+2]) == 'f'
&& to_lower(src[cur+3]) == 'l'
&& to_lower(src[cur+4]) == 'a'
&& to_lower(src[cur+5]) == 't'
&& to_lower(src[cur+6]) == 'e') {
if (num == max)
return -1;
items[num++] = TRANSFER_ENCODING_DEFLATE;
cur += 7;
} else if (3 < len - cur
&& to_lower(src[cur+0]) == 'g'
&& to_lower(src[cur+1]) == 'z'
&& to_lower(src[cur+2]) == 'i'
&& to_lower(src[cur+3]) == 'p') {
if (num == max)
return -1;
items[num++] = TRANSFER_ENCODING_GZIP;
cur += 4;
} else {
return -1;
}
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
cur++;
if (cur == len)
break;
if (src[cur] != ',')
return -1;
cur++;
}
return num;
}
static int
parse_content_length(const char *src, ptrdiff_t len, unsigned long long *out)
{
ptrdiff_t cur = 0;
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
cur++;
if (cur == len || !is_digit(src[cur]))
return -1;
unsigned long long buf = 0;
do {
int d = src[cur++] - '0';
if (buf > (MAX_U64 - d) / 10)
return -1;
buf = buf * 10 + d;
} while (cur < len && is_digit(src[cur]));
*out = buf;
return 0;
}
int tinyhttp_parserequest(char *src, ptrdiff_t len,
unsigned long long body_limit, TinyHTTPRequest *req)
{
ptrdiff_t ret = parse_request_head(src, len, req);
if (ret <= 0)
return ret;
ptrdiff_t head_len = ret;
int transfer_encoding_index = tinyhttp_findheader(req, TINYHTTP_STRING("Transfer-Encoding"));
if (transfer_encoding_index >= 0) {
TinyHTTPHeader *header = &req->headers[transfer_encoding_index];
int items[8];
int num = parse_transfer_encoding(header->value.ptr, header->value.len, items, COUNTOF(items));
if (num < 0)
return -400;
typedef struct {
ptrdiff_t offset;
ptrdiff_t length;
} Chunk;
ptrdiff_t body_length = 0;
Chunk chunks[TINYHTTP_CHUNK_LIMIT];
int num_chunks = 0;
ptrdiff_t cur = head_len;
for (;;) {
if (cur == len)
return 0;
if (!is_hex_digit(src[cur]))
return -400;
unsigned long long chunk_length = 0;
do {
int d = hex_digit_to_int(src[cur++]);
if (chunk_length > (MAX_U64 - d) / 16)
return -400;
chunk_length = chunk_length * 16 + d;
} while (cur < len && is_hex_digit(src[cur]));
if (chunk_length > body_limit - body_length) {
// TODO
}
body_length += chunk_length;
if (cur == len)
return 0;
if (src[cur] != '\r')
return -400;
cur++;
if (cur == len)
return 0;
if (src[cur] != '\n')
return -400;
cur++;
ptrdiff_t chunk_offset = cur;
cur += chunk_length; // TODO: Check overflow
if (cur >= len)
return 0;
if (src[cur] != '\r')
return -400;
cur++;
if (cur == len)
return 0;
if (src[cur] != '\n')
return -400;
cur++;
if (chunk_length == 0)
break;
if (num_chunks == TINYHTTP_CHUNK_LIMIT)
return -500;
chunks[num_chunks++] = (Chunk) { chunk_offset, chunk_length };
}
// Pack all of the chunks tightly after the head
ptrdiff_t next = head_len;
for (int i = 0; i < num_chunks; i++) {
memmove(src + next, src + chunks[i].offset, chunks[i].length);
next += chunks[i].length;
}
req->body = src + head_len;
req->body_len = body_length;
return cur;
}
int content_length_index = tinyhttp_findheader(req, TINYHTTP_STRING("Content-Length"));
if (content_length_index >= 0) {
TinyHTTPHeader *header = &req->headers[content_length_index];
unsigned long long content_length;
if (parse_content_length(header->value.ptr, header->value.len, &content_length) < 0)
return -400;
if (content_length > body_limit || content_length > MAX_U32)
return -413;
if (content_length < (unsigned long long) (len - head_len))
return 0;
req->body = src + head_len;
req->body_len = content_length;
return head_len + content_length;
}
req->body = NULL;
req->body_len = 0;
return head_len;
}
////////////////////////////////////////////////////////////////////////////////////
// BYTE QUEUE //
////////////////////////////////////////////////////////////////////////////////////
//
// This is the implementation of a byte queue useful
// for systems that need to process streams of bytes.
//
// It features sticky errors, a zero-copy interface,
// and a safe mechanism to patch previously written
// bytes.
//
// Only up to 4GB of data can be stored at once.
static void*
callback_malloc(TinyHTTPByteQueue *queue, ptrdiff_t len)
{
return queue->memfunc(TINYHTTP_MEM_MALLOC, NULL, len, queue->memfuncdata);
}
static void
callback_free(TinyHTTPByteQueue *queue, void *ptr, ptrdiff_t len)
{
queue->memfunc(TINYHTTP_MEM_FREE, ptr, len, queue->memfuncdata);
}
// Initialize the queue
static void
byte_queue_init(TinyHTTPByteQueue *queue, unsigned int limit, TinyHTTPMemoryFunc memfunc, void *memfuncdata)
{
queue->flags = 0;
queue->lock = 0;
queue->head = 0;
queue->size = 0;
queue->used = 0;
queue->curs = 0;
queue->limit = limit;
queue->data = NULL;
queue->read_target = NULL;
queue->memfunc = memfunc;
queue->memfuncdata = memfuncdata;
}
// Deinitialize the queue
static void
byte_queue_free(TinyHTTPByteQueue *queue)
{
if (queue->read_target) {
if (queue->read_target != queue->data)
callback_free(queue, queue->read_target, queue->read_target_size);
queue->read_target = NULL;
queue->read_target_size = 0;
}
callback_free(queue, queue->data, queue->size);
queue->data = NULL;
}
static int
byte_queue_error(TinyHTTPByteQueue *queue)
{
return queue->flags & BYTE_QUEUE_ERROR;
}
static void
byte_queue_setlimit(TinyHTTPByteQueue *queue, unsigned int value)
{
queue->limit = value;
}
static char*
byte_queue_peek(TinyHTTPByteQueue *queue, ptrdiff_t *len)
{
if ((queue->flags & (BYTE_QUEUE_ERROR)) || queue->data == NULL) {
*len = 0;
return NULL;
}
*len = queue->used;
return queue->data + queue->head;
}
// Start a read operation on the queue.
//
// This function returnes the pointer to the memory region containing the bytes
// to read. Callers can't read more than [*len] bytes from it. To complete the
// read, the [byte_queue_read_ack] function must be called with the number of
// bytes that were acknowledged by the caller.
//
// Note:
// - You can't have more than one pending read.
static char*
byte_queue_read_buf(TinyHTTPByteQueue *queue, ptrdiff_t *len)
{
if (queue->flags & BYTE_QUEUE_ERROR) {
*len = 0;
return NULL;
}
ASSERT((queue->flags & BYTE_QUEUE_READ) == 0);
queue->flags |= BYTE_QUEUE_READ;
queue->read_target = queue->data;
queue->read_target_size = queue->size;
*len = queue->used;
if (queue->data == NULL)
return NULL;
return queue->data + queue->head;
}
// Complete a previously started operation on the queue.
static void
byte_queue_read_ack(TinyHTTPByteQueue *queue, ptrdiff_t num)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_READ) == 0)
return;
queue->flags &= ~BYTE_QUEUE_READ;
ASSERT(num <= queue->used);
queue->head += num;
queue->used -= num;
queue->curs += num;
if (queue->read_target) {
if (queue->read_target != queue->data)
callback_free(queue, queue->read_target, queue->read_target_size);
queue->read_target = NULL;
queue->read_target_size = 0;
}
}
static int
byte_queue_read_started(TinyHTTPByteQueue *queue)
{
return (queue->flags & BYTE_QUEUE_READ) == BYTE_QUEUE_READ;
}
static char*
byte_queue_write_buf(TinyHTTPByteQueue *queue, ptrdiff_t *cap)
{
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) {
*cap = 0;
return NULL;
}
ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
queue->flags |= BYTE_QUEUE_WRITE;
*cap = queue->size - (queue->head + queue->used);
return queue->data + (queue->head + queue->used);
}
static void
byte_queue_write_ack(TinyHTTPByteQueue *queue, ptrdiff_t num)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
return;
queue->flags &= ~BYTE_QUEUE_WRITE;
queue->used += num;
}
static int
byte_queue_write_started(TinyHTTPByteQueue *queue)
{
return (queue->flags & BYTE_QUEUE_WRITE) == BYTE_QUEUE_WRITE;
}
// Sets the minimum capacity for the next write operation
// and returns 1 if the content of the queue was moved, else
// 0 is returned.
//
// You must not call this function while a write is pending.
// In other words, you must do this:
//
// byte_queue_write_setmincap(queue, mincap);
// dst = byte_queue_write_buf(queue, &cap);
// ...
// byte_queue_write_ack(num);
//
// And NOT this:
//
// dst = byte_queue_write_buf(queue, &cap);
// byte_queue_write_setmincap(queue, mincap); <-- BAD
// ...
// byte_queue_write_ack(num);
//
static int
byte_queue_write_setmincap(TinyHTTPByteQueue *queue, ptrdiff_t mincap)
{
// Sticky error
if (queue->flags & BYTE_QUEUE_ERROR)
return 0;
// In general, the queue's contents look like this:
//
// size
// v
// [___xxxxxxxxxxxx________]
// ^ ^ ^
// 0 head head + used
//
// This function needs to make sure that at least [mincap]
// bytes are available on the right side of the content.
//
// We have 3 cases:
//
// 1) If there is enough memory already, this function doesn't
// need to do anything.
//
// 2) If there isn't enough memory on the right but there is
// enough free memory if we cound the left unused region,
// then the content is moved back to the
// start of the buffer.
//
// 3) If there isn't enough memory considering both sides, this
// function needs to allocate a new buffer.
//
// If there are pending read or write operations, the application
// is holding pointers to the buffer, so we need to make sure
// to not invalidate them. The only real problem is pending reads
// since this function can only be called before starting a write
// opearation.
//
// To avoid invalidating the read pointer when we allocate a new
// buffer, we don't free the old buffer. Instead, we store the
// pointer in the "old" field so that the read ack function can
// free it.
//
// To avoid invalidating the pointer when we are moving back the
// content since there is enough memory at the start of the buffer,
// we just avoid that. Even if there is enough memory considering
// left and right free regions, we allocate a new buffer.
ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
unsigned int total_free_space = queue->size - queue->used;
unsigned int free_space_after_data = queue->size - queue->used - queue->head;
int moved = 0;
if (free_space_after_data < mincap) {
if (total_free_space < mincap || (queue->read_target == queue->data)) {
// Resize required
if (queue->used + mincap > queue->limit) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
unsigned int size;
if (queue->size > MAX_U32 / 2)
size = MAX_U32;
else
size = 2 * queue->size;
if (size < queue->used + mincap)
size = queue->used + mincap;
if (size > queue->limit)
size = queue->limit;
char *data = callback_malloc(queue, size);
if (!data) {
queue->flags |= BYTE_QUEUE_ERROR;
return 0;
}
if (queue->used > 0)
memcpy(data, queue->data + queue->head, queue->used);
if (queue->read_target != queue->data)
callback_free(queue, queue->data, queue->size);
queue->data = data;
queue->head = 0;
queue->size = size;
} else {
// Move required
memmove(queue->data, queue->data + queue->head, queue->used);
queue->head = 0;
}
moved = 1;
}
return moved;
}
static TinyHTTPByteQueueOffset
byte_queue_offset(TinyHTTPByteQueue *queue)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return (TinyHTTPByteQueueOffset) { 0 };
return (TinyHTTPByteQueueOffset) { queue->curs + queue->used };
}
static unsigned int
byte_queue_size_from_offset(TinyHTTPByteQueue *queue, TinyHTTPByteQueueOffset off)
{
return queue->curs + queue->used - off;
}
static void
byte_queue_patch(TinyHTTPByteQueue *queue, TinyHTTPByteQueueOffset off,
char *src, unsigned int len)
{
// Check that the offset is in range
ASSERT(off >= queue->curs && off - queue->curs < queue->used);
// Check that the length is in range
ASSERT(len <= queue->used - (off - queue->curs));
// Perform the patch
char *dst = queue->data + queue->head + (off - queue->curs);
memcpy(dst, src, len);
}
static void
byte_queue_remove_after_lock(TinyHTTPByteQueue *queue)
{
ASSERT(queue->flags & BYTE_QUEUE_LOCK);
unsigned long long num = (queue->curs + queue->head) - queue->lock;
ASSERT(num <= queue->used);
queue->used -= num;
}
static void
byte_queue_write(TinyHTTPByteQueue *queue, const char *str)
{
ptrdiff_t cap;
ptrdiff_t len = strlen(str);
byte_queue_write_setmincap(queue, len);
char *dst = byte_queue_write_buf(queue, &cap);
if (dst) memcpy(dst, str, len);
byte_queue_write_ack(queue, len);
}
static void
byte_queue_write_fmt2(TinyHTTPByteQueue *queue, const char *fmt, va_list args)
{
if (queue->flags & BYTE_QUEUE_ERROR)
return;
va_list args2;
va_copy(args2, args);
ptrdiff_t cap;
byte_queue_write_setmincap(queue, 128);
char *dst = byte_queue_write_buf(queue, &cap);
int len = vsnprintf(dst, cap, fmt, args);
if (len < 0) {
queue->flags |= BYTE_QUEUE_ERROR;
va_end(args2);
va_end(args);
return;
}
if (len > cap) {
byte_queue_write_ack(queue, 0);
byte_queue_write_setmincap(queue, len+1);
dst = byte_queue_write_buf(queue, &cap);
vsnprintf(dst, cap, fmt, args2);
}
byte_queue_write_ack(queue, len);
va_end(args2);
va_end(args);
}
static void
byte_queue_write_fmt(TinyHTTPByteQueue *queue, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
byte_queue_write_fmt2(queue, fmt, args);
va_end(args);
}
static unsigned int
byte_queue_read_size(TinyHTTPByteQueue *queue)
{
if (queue->lock & BYTE_QUEUE_LOCK)
return queue->curs + queue->used - queue->lock;
return queue->used;
}
static void
byte_queue_read_lock(TinyHTTPByteQueue *queue)
{
ASSERT((queue->flags & BYTE_QUEUE_LOCK) == 0);
queue->lock = queue->curs + queue->used;
queue->flags |= BYTE_QUEUE_LOCK;
}
static void
byte_queue_read_unlock(TinyHTTPByteQueue *queue)
{
ASSERT(queue->flags & BYTE_QUEUE_LOCK);
queue->flags &= ~BYTE_QUEUE_LOCK;
}
////////////////////////////////////////////////////////////////////////////////////
// HTTP STREAM //
////////////////////////////////////////////////////////////////////////////////////
#define EIGHT_ZEROS "00000000"
#define TEN_SPACES " "
static const char*
get_status_text(int code)
{
switch(code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 208: return "Already Reported";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 306: return "Switch Proxy";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 418: return "I'm a teapot";
case 420: return "Enhance your calm";
case 422: return "Unprocessable Entity";
case 426: return "Upgrade Required";
case 429: return "Too many requests";
case 431: return "Request Header Fields Too Large";
case 449: return "Retry With";
case 451: return "Unavailable For Legal Reasons";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
case 509: return "Bandwidth Limit Exceeded";
}
return "???";
}
// See tinyhttp.h
void tinyhttp_stream_init(TinyHTTPStream *stream, TinyHTTPMemoryFunc memfunc, void *memfuncdata)
{
// Since we are the server, we are expecting
// the client to send data first.
stream->state = TINYHTTP_STREAM_RECV;
// We only use the output state when a request
// has been received.
stream->output_state = TINYHTTP_OUTPUT_STATE_NONE;
// Set the maximum content length
stream->bodylimit = 1<<29; // 500MB
stream->reqsize = 0;
stream->numexch = 0;
byte_queue_init(&stream->in, 1<<29, memfunc, memfuncdata);
byte_queue_init(&stream->out, 1<<29, memfunc, memfuncdata);
}
// See tinyhttp.h
void tinyhttp_stream_free(TinyHTTPStream *stream)
{
byte_queue_free(&stream->out);
byte_queue_free(&stream->in);
stream->state = 0;
}
// See tinyhttp.h
void tinyhttp_stream_kill(TinyHTTPStream *stream)
{
stream->state |= TINYHTTP_STREAM_DIED;
}
// See tinyhttp.h
int tinyhttp_stream_state(TinyHTTPStream *stream)
{
// The state is stored in stream->state, but the
// TINYHTTP_STREAM_SEND and TINYHTTP_STREAM_RECV are evaluated
// lazily to avoid possible invalid states.
int state = stream->state;
if ((state & TINYHTTP_STREAM_DIED) == 0) {
if (stream->reqsize > 0)
state |= TINYHTTP_STREAM_READY;
// If there is data to read in the output buffer,
// we are interested in sending data.
if (byte_queue_read_size(&stream->out))
state |= TINYHTTP_STREAM_SEND;
// If we don't have a buffered request and the
// connection is not closing, we are interested
// in receiving data.
if ((state & (TINYHTTP_STREAM_READY | TINYHTTP_STREAM_CLOSE)) == 0)
state |= TINYHTTP_STREAM_RECV;
}
if (byte_queue_write_started(&stream->in))
state |= TINYHTTP_STREAM_RECV_STARTED;
if (byte_queue_read_started(&stream->out))
state |= TINYHTTP_STREAM_SEND_STARTED;
return state;
}
// See tinyhttp.h
char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED) {
*cap = 0;
return NULL;
}
// Make sure at least this free space is available
ptrdiff_t minrecv = 1<<9;
int resized = byte_queue_write_setmincap(&stream->in, minrecv);
// If the input data was moved and a request was
// buffered, we need to update the pointers of the
// parsed request. We do this by parsing again.
if (resized && (stream->state & TINYHTTP_STREAM_READY)) {
char *src;
ptrdiff_t len;
// Get the new input data location
src = byte_queue_peek(&stream->in, &len);
// Parse again. We assume everything will go
// well as it did the first time.
tinyhttp_parserequest(src, len, stream->bodylimit, &stream->req);
}
// Forward the write region from the input buffer
return byte_queue_write_buf(&stream->in, cap);
}
static int
should_keep_alive(TinyHTTPStream *stream)
{
ASSERT(stream->reqsize > 0);
// If the parent system doesn't want us to reuse
// the connection, we certainly can't keep alive.
if ((stream->state & TINYHTTP_STREAM_REUSE) == 0)
return 0;
if (stream->numexch >= 100) // TODO: Make this a parameter
return 0;
TinyHTTPRequest *req = &stream->req;
// If the client is using HTTP/1.0, we can't
// keep alive.
if (req->minor == 0)
return 0;
// TODO: This assumes "Connection" can only hold a single token,
// but this is not true.
int i = tinyhttp_findheader(req, TINYHTTP_STRING("Connection"));
if (i >= 0 && tinyhttp_streqcase(req->headers[i].value, TINYHTTP_STRING("Close")))
return 0;
return 1;
}
static void
process_next_request(TinyHTTPStream *stream)
{
// Try parsing the request from the buffered bytes.
ptrdiff_t len;
char *src = byte_queue_read_buf(&stream->in, &len); // TODO: What if this returns NULL?
int ret = tinyhttp_parserequest(src, len, stream->bodylimit, &stream->req);
// Request is incomplete
if (ret == 0) {
byte_queue_read_ack(&stream->in, 0);
return;
}
// Invalid request
if (ret < 0) {
byte_queue_read_ack(&stream->in, 0);
int status = -ret;
byte_queue_write_fmt(&stream->out, "HTTP/1.1 %d %s\r\n", status, get_status_text(status));
if (byte_queue_error(&stream->out)) {
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
stream->state |= TINYHTTP_STREAM_CLOSE;
return;
}
// Request buffered
ASSERT(ret > 0);
stream->reqsize = ret;
// Start up the output state machine
stream->output_state = TINYHTTP_OUTPUT_STATE_STATUS;
// Configure chunked coding for this request
stream->chunked = 0;
// Determine whether this connection will be
// kept alive after this request/response exchange;
stream->keepalive = should_keep_alive(stream);
// Don't allow bytes written to the output buffer
// from this point on to be send over the network
// until the response was completely buffered.
//
// This is because if an error occurs while responding,
// we may want to reset everything and start from
// scratch.
byte_queue_read_lock(&stream->out);
}
// See tinyhttp.h
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
byte_queue_write_ack(&stream->in, num);
// Since new data was ready, the state of the
// connection may need to change.
// If a request was already buffered, the state
// won't change until a response is generated.
if (stream->reqsize > 0)
return;
process_next_request(stream);
}
// See tinyhttp.h
char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return NULL;
return byte_queue_read_buf(&stream->out, len);
}
// See tinyhttp.h
void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
byte_queue_read_ack(&stream->out, num);
if (byte_queue_read_size(&stream->out) == 0 && (stream->state & TINYHTTP_STREAM_CLOSE)) {
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
}
// See tinyhttp.h
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (value)
stream->state |= TINYHTTP_STREAM_REUSE;
else
stream->state &= ~TINYHTTP_STREAM_REUSE;
}
// See tinyhttp.h
void tinyhttp_stream_setbodylimit(TinyHTTPStream *stream, unsigned long long value)
{
stream->bodylimit = value;
}
// See tinyhttp.h
void tinyhttp_stream_setinbuflimit(TinyHTTPStream *stream, unsigned int value)
{
byte_queue_setlimit(&stream->in, value);
}
// See tinyhttp.h
void tinyhttp_stream_setoutbuflimit(TinyHTTPStream *stream, unsigned int value)
{
byte_queue_setlimit(&stream->out, value);
}
// See tinyhttp.h
TinyHTTPRequest *tinyhttp_stream_request(TinyHTTPStream *stream)
{
if (stream->reqsize > 0)
return &stream->req;
return NULL;
}
// See tinyhttp.h
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_STATUS) {
if (stream->output_state != TINYHTTP_OUTPUT_STATE_NONE)
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
return;
}
byte_queue_write_fmt(&stream->out, "HTTP/1.%d %d %s\r\n",
stream->req.minor, status, get_status_text(status));
stream->output_state = TINYHTTP_OUTPUT_STATE_HEADER;
}
// See tinyhttp.h
void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
tinyhttp_stream_response_header_fmt(stream, fmt, args);
va_end(args);
}
// See tinyhttp.h
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_HEADER) {
if (stream->output_state != TINYHTTP_OUTPUT_STATE_NONE)
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
return;
}
byte_queue_write_fmt2(&stream->out, fmt, args);
}
static void
append_special_headers(TinyHTTPStream *stream)
{
if (stream->keepalive)
byte_queue_write(&stream->out, "Connection: Keep-Alive\r\n");
else
byte_queue_write(&stream->out, "Connection: Close\r\n");
if (stream->chunked)
byte_queue_write(&stream->out, "Transfer-Encoding: Chunked\r\n");
else {
byte_queue_write(&stream->out, "Content-Length: ");
stream->content_length_value_offset = byte_queue_offset(&stream->out);
byte_queue_write(&stream->out, TEN_SPACES "\r\n");
}
byte_queue_write(&stream->out, "\r\n");
stream->content_length_offset = byte_queue_offset(&stream->out);
}
void tinyhttp_stream_response_body(TinyHTTPStream *stream, const char *src, int len)
{
if (len < 0) len = strlen(src);
tinyhttp_stream_response_body_setmincap(stream, len);
ptrdiff_t cap;
char *dst = tinyhttp_stream_response_body_buf(stream, &cap);
if (dst)
memcpy(dst, src, len);
tinyhttp_stream_response_body_ack(stream, len);
}
// See tinyhttp.h
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
append_special_headers(stream);
stream->output_state = TINYHTTP_OUTPUT_STATE_BODY;
}
if (stream->output_state != TINYHTTP_OUTPUT_STATE_BODY) {
if (stream->output_state != TINYHTTP_OUTPUT_STATE_NONE)
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
return;
}
// Always add some extra padding in case we need
// to append a chunk header.
byte_queue_write_setmincap(&stream->out, mincap + 20);
}
// See tinyhttp.h
char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_DIED)
return NULL;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
append_special_headers(stream);
stream->output_state = TINYHTTP_OUTPUT_STATE_BODY;
}
if (stream->output_state != TINYHTTP_OUTPUT_STATE_BODY) {
if (stream->output_state != TINYHTTP_OUTPUT_STATE_NONE)
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
*cap = 0;
return NULL;
}
if (stream->chunked)
byte_queue_write(&stream->out, EIGHT_ZEROS "\r\n");
return byte_queue_write_buf(&stream->out, cap);
}
// See tinyhttp.h
void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_BODY) {
if (stream->output_state != TINYHTTP_OUTPUT_STATE_NONE)
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
return;
}
if (stream->chunked) {
if ((num & 0xffffffff)) {
stream->output_state = TINYHTTP_OUTPUT_STATE_ERROR;
return;
}
char tmp[8];
tmp[7] = (num >> 28) & 0xF;
tmp[6] = (num >> 24) & 0xF;
tmp[5] = (num >> 20) & 0xF;
tmp[4] = (num >> 16) & 0xF;
tmp[3] = (num >> 12) & 0xF;
tmp[2] = (num >> 8) & 0xF;
tmp[1] = (num >> 4) & 0xF;
tmp[0] = (num >> 0) & 0xF;
for (int i = 0; i < 8; i++) {
if (tmp[i] < 10)
tmp[i] += '0';
else
tmp[i] += 'a';
}
byte_queue_patch(&stream->out, byte_queue_offset(&stream->out) - 10, tmp, 8);
}
byte_queue_write_ack(&stream->out, num);
if (stream->chunked)
byte_queue_write(&stream->out, "\r\n");
}
// See tinyhttp.h
void tinyhttp_stream_response_send(TinyHTTPStream *stream)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_NONE)
return;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
append_special_headers(stream);
stream->output_state = TINYHTTP_OUTPUT_STATE_BODY;
}
if (stream->output_state == TINYHTTP_OUTPUT_STATE_BODY) {
if (stream->chunked)
byte_queue_write(&stream->out, "0\r\n\r\n");
else {
ptrdiff_t content_length = byte_queue_size_from_offset(&stream->out, stream->content_length_offset);
ASSERT(content_length >= 0);
if (content_length > MAX_U32) {
// TODO
}
char tmp[10];
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
tmp[7] = '0' + content_length / 100; content_length %= 100;
tmp[8] = '0' + content_length / 10; content_length %= 10;
tmp[9] = '0' + content_length;
int i = 0;
while (i < 9 && tmp[i] == '0')
i++;
byte_queue_patch(&stream->out, stream->content_length_value_offset, tmp + i, 10 - i);
}
}
if (stream->output_state == TINYHTTP_OUTPUT_STATE_ERROR) {
byte_queue_remove_after_lock(&stream->out);
byte_queue_write(&stream->out,
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Length: 0\r\n"
"Connection: Close\r\n"
"\r\n"
);
}
if (byte_queue_error(&stream->out)) {
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
#if DUMP_IO
ptrdiff_t ressize = (byte_queue_offset(&stream->out) - stream->out.lock);
tinyhttp_printbytes("R << ", stream->out.data + stream->out.head, ressize);
#endif
byte_queue_read_ack(&stream->in, stream->reqsize);
byte_queue_read_unlock(&stream->out);
stream->reqsize = 0;
if (stream->keepalive)
process_next_request(stream);
else
stream->state |= TINYHTTP_STREAM_CLOSE;
}
// See tinyhttp.h
void tinyhttp_stream_response_undo(TinyHTTPStream *stream)
{
// Sticky error
if (stream->state & TINYHTTP_STREAM_DIED)
return;
byte_queue_remove_after_lock(&stream->out);
stream->output_state = TINYHTTP_OUTPUT_STATE_STATUS;
}
////////////////////////////////////////////////////////////////////////////////////
// HTTP SERVER //
////////////////////////////////////////////////////////////////////////////////////
struct TinyHTTPServer {
#if defined(_WIN32)
int deinit_winsock;
HANDLE iocp;
OVERLAPPED plain_accept_overlapped;
OVERLAPPED secure_accept_overlapped;
SOCKET plain_accept_target;
SOCKET secure_accept_target;
LPFN_ACCEPTEX accept_func;
char plain_accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
char secure_accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
OVERLAPPED recv_overlapped[TINYHTTP_SERVER_CONN_LIMIT];
OVERLAPPED send_overlapped[TINYHTTP_SERVER_CONN_LIMIT];
#elif defined(__linux__)
int epoll_fd;
#endif
SOCKET plain_listen_socket;
SOCKET secure_listen_socket;
int num_conns;
int ready_head;
int ready_count;
int ready_queue[TINYHTTP_SERVER_CONN_LIMIT];
unsigned short stream_gens[TINYHTTP_SERVER_CONN_LIMIT];
TinyHTTPStream stream_state[TINYHTTP_SERVER_CONN_LIMIT];
SOCKET stream_sockets[TINYHTTP_SERVER_CONN_LIMIT];
};
static int
socket_set_block(SOCKET fd, int value)
{
#if defined(__linux__)
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return -1;
if (value)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -1;
return 0;
#elif defined(_WIN32)
u_long mode = !value;
int ret = ioctlsocket(fd, FIONBIO, &mode);
if (ret == SOCKET_ERROR)
return -1;
return 0;
#else
return -1;
#endif
}
static SOCKET
socket_listen(const char *addr, int port, int backlog, int reuse)
{
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == INVALID_SOCKET)
return INVALID_SOCKET;
if (socket_set_block(fd, 0) < 0) {
CLOSESOCKET(fd);
return INVALID_SOCKET;
}
if (reuse) {
int one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)) < 0) {
CLOSESOCKET(fd);
return INVALID_SOCKET;
}
}
struct in_addr bind_buf2;
if (addr == NULL)
bind_buf2.s_addr = htonl(INADDR_ANY);
else if (inet_pton(AF_INET, addr, &bind_buf2) != 1) {
CLOSESOCKET(fd);
return INVALID_SOCKET;
}
struct sockaddr_in bind_buf;
bind_buf.sin_family = AF_INET;
bind_buf.sin_port = htons(port);
bind_buf.sin_addr = bind_buf2;
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
CLOSESOCKET(fd);
return INVALID_SOCKET;
}
if (listen(fd, backlog) < 0) {
CLOSESOCKET(fd);
return INVALID_SOCKET;
}
return fd;
}
static TinyHTTPStream*
response_to_stream(TinyHTTPResponse res)
{
if (res.server == NULL)
return NULL;
if (res.idx >= TINYHTTP_SERVER_CONN_LIMIT)
return NULL;
if (res.gen != res.server->stream_gens[res.idx])
return NULL;
return &res.server->stream_state[res.idx];
}
// TODO: The generation counters allow freeing streams while the user is still
// holding a response handle that references them, but the user may still
// be holding the pointer to the request structure. How to fix this? Should
// callers only be allowed to access TinyHTTPRequest before the next call
// to [tinyhttp_server_wait]? Or should connections be marked as pending
// until [tinyhttp_server_send] is called?
static void
invalidate_handles_to_stream(TinyHTTPServer *server, TinyHTTPStream *stream)
{
int idx = stream - server->stream_state;
unsigned short *gen = &server->stream_gens[idx];
(*gen)++;
if (*gen == MAX_U16 || *gen == 0)
*gen = 1;
}
static void
remove_from_ready_queue(TinyHTTPServer *server, int idx)
{
#define QUEUE_AT_INDEX(I) server->ready_queue[(server->ready_head + (I)) % TINYHTTP_SERVER_CONN_LIMIT]
int i = 0;
while (i < server->ready_count && QUEUE_AT_INDEX(i) != idx)
i++;
if (i == server->ready_count)
return;
while (i < server->ready_count-1) {
QUEUE_AT_INDEX(i) = QUEUE_AT_INDEX(i+1);
i++;
}
server->ready_count--;
}
static void*
default_memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
#if defined(__linux__)
static void
server_free_platform(TinyHTTPServer *server)
{
close(server->epoll_fd);
}
static int
server_init_platform(TinyHTTPServer *server,
TinyHTTPServerConfig config)
{
server->epoll_fd = epoll_create1(0);
if (server->epoll_fd < 0)
return -1;
struct epoll_event epoll_buf;
epoll_buf.data.fd = -1;
epoll_buf.events = EPOLLIN;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_ADD, server->plain_listen_socket, &epoll_buf) < 0) {
close(server->epoll_fd);
return -1;
}
if (config.secure) {
epoll_buf.data.fd = -2;
epoll_buf.events = EPOLLIN;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_ADD, server->secure_listen_socket, &epoll_buf) < 0) {
close(server->epoll_fd);
return -1;
}
}
return 0;
}
static unsigned long long
get_current_time_ms(void)
{
struct timespec ts;
int result = clock_gettime(CLOCK_REALTIME, &ts);
if (result)
return MAX_U64;
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int
accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secure)
{
int errors = 0;
while (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT) {
SOCKET accepted_socket = accept(listen_socket, NULL, NULL);
if (accepted_socket == INVALID_SOCKET) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
errors++;
if (errors == 1000)
break;
continue;
}
errors = 0;
int one = 1;
if (setsockopt(accepted_socket, IPPROTO_TCP, TCP_NODELAY, (char*) &one, sizeof(one)) < 0) {
CLOSESOCKET(accepted_socket);
continue;
}
if (socket_set_block(accepted_socket, 0)) {
CLOSESOCKET(accepted_socket);
continue;
}
int idx = 0;
while (server->stream_sockets[idx] != INVALID_SOCKET)
idx++;
server->stream_sockets[idx] = accepted_socket;
TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, default_memfunc, NULL);
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1);
int state = tinyhttp_stream_state(stream);
struct epoll_event epoll_buf;
epoll_buf.data.fd = idx;
epoll_buf.events = 0;
if (state & TINYHTTP_STREAM_RECV) epoll_buf.events |= EPOLLIN;
if (state & TINYHTTP_STREAM_SEND) epoll_buf.events |= EPOLLOUT;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_ADD, accepted_socket, &epoll_buf) < 0) {
CLOSESOCKET(accepted_socket);
tinyhttp_stream_free(stream);
continue;
}
if (secure) {
// TODO
}
server->num_conns++;
}
return 0;
}
static int
process_network_events(TinyHTTPServer *server, int timeout)
{
// TODO: timeouts
struct epoll_event batch[TINYHTTP_SERVER_EPOLL_BATCH_SIZE];
int num;
do
num = epoll_wait(server->epoll_fd, batch, TINYHTTP_SERVER_EPOLL_BATCH_SIZE, timeout);
while (num < 0 && errno == EINTR);
for (int i = 0; i < num; i++) {
int idx = batch[i].data.fd;
int flags = batch[i].events;
if (idx == -1) {
// New plain connections
if (accept_from_listen_socket(server, server->plain_listen_socket, 0) < 0)
return -1;
} else if (idx == -2) {
// New secure connections
if (accept_from_listen_socket(server, server->secure_listen_socket, 1) < 0)
return -1;
} else {
SOCKET sock = server->stream_sockets[idx];
TinyHTTPStream *stream = &server->stream_state[idx];
if (flags & (EPOLLERR | EPOLLHUP))
tinyhttp_stream_kill(stream);
int old_state = tinyhttp_stream_state(stream);
int state = old_state;
if (flags & EPOLLIN) {
while (state & TINYHTTP_STREAM_RECV) {
ptrdiff_t cap;
char *dst = tinyhttp_stream_recv_buf(stream, &cap);
if (dst == NULL)
continue;
int ret = recv(sock, dst, cap, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
tinyhttp_stream_kill(stream);
break;
}
if (ret == 0) {
tinyhttp_stream_kill(stream);
break;
}
#if DUMP_IO
tinyhttp_printbytes("N >> ", dst, ret);
#endif
tinyhttp_stream_recv_ack(stream, ret);
state = tinyhttp_stream_state(stream);
}
tinyhttp_stream_recv_ack(stream, 0);
}
if (flags & EPOLLOUT) {
while (state & TINYHTTP_STREAM_SEND) {
ptrdiff_t len;
char *src = tinyhttp_stream_send_buf(stream, &len);
if (src == NULL)
continue;
int ret = send(sock, src, len, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
tinyhttp_stream_kill(stream);
break;
}
if (ret == 0) {
tinyhttp_stream_kill(stream);
break;
}
#if DUMP_IO
tinyhttp_printbytes("N << ", src, ret);
#endif
tinyhttp_stream_send_ack(stream, ret);
state = tinyhttp_stream_state(stream);
}
tinyhttp_stream_send_ack(stream, 0);
}
int new_state = tinyhttp_stream_state(&server->stream_state[idx]);
if ((old_state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND)) != (new_state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))) {
struct epoll_event tmp;
tmp.data.fd = idx;
tmp.events = 0;
if (new_state & TINYHTTP_STREAM_RECV) tmp.events |= EPOLLIN;
if (new_state & TINYHTTP_STREAM_SEND) tmp.events |= EPOLLOUT;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_MOD, sock, &tmp) < 0) {
tinyhttp_stream_kill(stream);
new_state = tinyhttp_stream_state(stream);
}
}
if ((new_state & TINYHTTP_STREAM_READY) && !(old_state & TINYHTTP_STREAM_READY)) {
int ready_idx = (server->ready_head + server->ready_count) % TINYHTTP_SERVER_CONN_LIMIT;
server->ready_queue[ready_idx] = idx;
server->ready_count++;
}
if (new_state & TINYHTTP_STREAM_DIED) {
remove_from_ready_queue(server, idx);
CLOSESOCKET(sock);
tinyhttp_stream_free(stream);
invalidate_handles_to_stream(server, stream);
server->stream_sockets[idx] = INVALID_SOCKET;
server->num_conns--;
}
}
}
return 0;
}
#elif defined(_WIN32)
static void
server_free_platform(TinyHTTPServer *server)
{
CloseHandle(server->iocp);
if (server->deinit_winsock)
WSACleanup();
}
static SOCKET
start_accept_operation(LPFN_ACCEPTEX *accept_func,
SOCKET listen_socket, OVERLAPPED *overlapped,
char *buf, int buflen)
{
if (*accept_func == NULL) {
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
unsigned long num;
int ret = WSAIoctl(listen_socket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx, sizeof(GuidAcceptEx),
&lpfnAcceptEx, sizeof(lpfnAcceptEx),
&num, NULL, NULL);
if (ret == SOCKET_ERROR)
return INVALID_SOCKET;
*accept_func = lpfnAcceptEx;
}
SOCKET target_socket = socket(AF_INET, SOCK_STREAM, 0);
if (target_socket == INVALID_SOCKET)
return INVALID_SOCKET;
memset(overlapped, 0, sizeof(OVERLAPPED));
DWORD num;
int ok = (*accept_func)(
(SOCKET) listen_socket, target_socket,
buf, buflen - ((sizeof(struct sockaddr_in) + 16) * 2),
sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16,
&num, overlapped);
if (!ok && WSAGetLastError() != ERROR_IO_PENDING) {
CLOSESOCKET(target_socket);
return INVALID_SOCKET;
}
return target_socket;
}
static int server_init_platform(TinyHTTPServer *server,
TinyHTTPServerConfig config)
{
server->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
if (server->iocp == INVALID_HANDLE_VALUE)
return -1;
if (CreateIoCompletionPort((HANDLE) server->plain_listen_socket, server->iocp, 0, 0) == NULL) {
CloseHandle(server->iocp);
return -1;
}
server->accept_func = NULL;
server->plain_accept_target = start_accept_operation(
&server->accept_func,
server->plain_listen_socket,
&server->plain_accept_overlapped,
server->plain_accept_buf,
sizeof(server->plain_accept_buf));
if (server->plain_accept_target == INVALID_SOCKET)
return -1;
#if TINYHTTP_HTTPS_ENABLE
if (server->secure_listen_socket) {
if (CreateIoCompletionPort((HANDLE) server->secure_listen_socket, server->iocp, 0, 0) == NULL) {
CLOSESOCKET(server->plain_accept_target);
CloseHandle(server->iocp);
return -1;
}
server->secure_accept_target = start_accept_operation(
&server->accept_func,
server->secure_listen_socket,
&server->secure_accept_overlapped,
server->secure_accept_buf,
sizeof(server->secure_accept_buf));
if (server->secure_accept_target == INVALID_SOCKET) {
CLOSESOCKET(server->plain_accept_target);
CloseHandle(server->iocp);
return -1;
}
}
#endif // TINYHTTP_HTTPS_ENABLE
return 0;
}
static unsigned long long
get_current_time_ms(void)
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER uli;
uli.LowPart = ft.dwLowDateTime;
uli.HighPart = ft.dwHighDateTime;
// Convert Windows file time (100ns since 1601-01-01) to
// Unix epoch time (seconds since 1970-01-01)
// 116444736000000000 = number of 100ns intervals from 1601 to 1970
return (uli.QuadPart - 116444736000000000ULL) / 10000ULL; // TODO: Make sure this is returning miliseconds
}
static int
start_stream_operations(TinyHTTPStream *stream, SOCKET sock,
OVERLAPPED *recv_overlapped, OVERLAPPED *send_overlapped)
{
int state = tinyhttp_stream_state(stream);
if ((state & TINYHTTP_STREAM_RECV) && !(state & TINYHTTP_STREAM_RECV_STARTED)) {
ptrdiff_t cap;
char *dst = tinyhttp_stream_recv_buf(stream, &cap);
memset(recv_overlapped, 0, sizeof(*recv_overlapped));
int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_overlapped);
if (!ok && GetLastError() != ERROR_IO_PENDING)
return -1;
#if DUMP_IO
printf("RECV STARTED (cap=%lld)\n", cap);
#endif
}
if (state & TINYHTTP_STREAM_SEND && !(state & TINYHTTP_STREAM_SEND_STARTED)) {
ptrdiff_t len;
char *src = tinyhttp_stream_send_buf(stream, &len);
memset(send_overlapped, 0, sizeof(*send_overlapped));
int ok = WriteFile((HANDLE) sock, src, len, NULL, send_overlapped);
if (!ok && GetLastError() != ERROR_IO_PENDING)
return -1;
#if DUMP_IO
printf("SEND STARTED (len=%lld)\n", len);
#endif
}
return 0;
}
static void
intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secure)
{
if (server->num_conns == TINYHTTP_SERVER_CONN_LIMIT) {
CLOSESOCKET(accepted_socket);
return;
}
int one = 1;
if (setsockopt(accepted_socket, IPPROTO_TCP, TCP_NODELAY, (char*) &one, sizeof(one)) < 0) {
CLOSESOCKET(accepted_socket);
return;
}
if (socket_set_block(accepted_socket, 0) < 0) {
CLOSESOCKET(accepted_socket);
return;
}
int idx = 0;
while (server->stream_sockets[idx] != INVALID_SOCKET)
idx++;
server->stream_sockets[idx] = accepted_socket;
TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, default_memfunc, NULL);
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1);
if (CreateIoCompletionPort((HANDLE) accepted_socket, server->iocp, 0, 0) == NULL) {
tinyhttp_stream_free(stream);
server->stream_sockets[idx] = INVALID_SOCKET;
CLOSESOCKET(accepted_socket);
return;
}
OVERLAPPED *recv_overlapped = &server->recv_overlapped[idx];
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
if (start_stream_operations(stream, accepted_socket, recv_overlapped, send_overlapped) < 0) {
tinyhttp_stream_free(stream);
server->stream_sockets[idx] = INVALID_SOCKET;
CLOSESOCKET(accepted_socket);
return;
}
if (secure) {
// TODO
}
server->num_conns++;
}
static int
process_network_events(TinyHTTPServer *server, int timeout)
{
// TODO: timeouts
DWORD timeout2;
if (timeout < 0)
timeout2 = INFINITE;
else
timeout2 = timeout;
DWORD transferred;
ULONG_PTR key;
OVERLAPPED *overlapped;
BOOL result = GetQueuedCompletionStatus(server->iocp,
&transferred, &key, &overlapped, timeout2);
// Handle timeouts and error on the completion function itself
if (!result && overlapped == NULL) {
if (GetLastError() == WAIT_TIMEOUT)
return 0;
return -1;
}
ASSERT(overlapped);
if (overlapped == &server->plain_accept_overlapped) {
if (result) {
// New plain connection
#if DUMP_IO
printf("ACCEPT COMPLETED (plain)\n");
#endif
SOCKET accepted_socket = server->plain_accept_target;
server->plain_accept_target = INVALID_SOCKET;
intern_accepted_socket(server, accepted_socket, 0);
} else {
// Accept failed
CLOSESOCKET(server->plain_accept_target);
// TODO
}
server->plain_accept_target = start_accept_operation(
&server->accept_func,
server->plain_listen_socket,
&server->plain_accept_overlapped,
server->plain_accept_buf,
sizeof(server->plain_accept_buf));
if (server->plain_accept_target == INVALID_SOCKET)
return -1; // Can't recover
return 0;
}
if (overlapped == &server->secure_accept_overlapped) {
if (result) {
// New secure connection
#if DUMP_IO
printf("ACCEPT COMPLETED (secure)\n");
#endif
SOCKET accepted_socket = server->plain_accept_target;
server->plain_accept_target = INVALID_SOCKET;
intern_accepted_socket(server, accepted_socket, 1);
} else {
// Accept failed
CLOSESOCKET(server->secure_accept_target);
// TODO
}
server->secure_accept_target = start_accept_operation(
&server->accept_func,
server->secure_listen_socket,
&server->secure_accept_overlapped,
server->secure_accept_buf,
sizeof(server->secure_accept_buf));
if (server->secure_accept_target == INVALID_SOCKET)
return -1; // Can't recover
return 0;
}
int idx = key;
TinyHTTPStream *stream = &server->stream_state[idx];
SOCKET sock = server->stream_sockets[idx];
OVERLAPPED *recv_overlapped = &server->recv_overlapped[idx];
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
int old_state = tinyhttp_stream_state(stream);
if (!result) {
// A read or write operation failed
tinyhttp_stream_kill(stream);
} else {
if (recv_overlapped == overlapped) {
#if DUMP_IO
printf("RECV COMPLETED (num=%ld)\n", transferred);
tinyhttp_printbytes("N >> ", stream->in.data + stream->in.head, transferred);
#endif
tinyhttp_stream_recv_ack(stream, transferred);
if (transferred == 0)
tinyhttp_stream_kill(stream);
} else {
ASSERT(send_overlapped == overlapped);
#if DUMP_IO
printf("SEND COMPLETED (num=%ld)\n", transferred);
tinyhttp_printbytes("N << ", stream->out.data + stream->out.head, transferred);
#endif
tinyhttp_stream_send_ack(stream, transferred);
}
if (start_stream_operations(stream, sock, recv_overlapped, send_overlapped) < 0)
tinyhttp_stream_kill(stream);
}
int state = tinyhttp_stream_state(stream);
if ((state & TINYHTTP_STREAM_READY) && (old_state & TINYHTTP_STREAM_READY) == 0) {
int ready_idx = (server->ready_head + server->ready_count) % TINYHTTP_SERVER_CONN_LIMIT;
server->ready_queue[ready_idx] = idx;
server->ready_count++;
}
if (state & TINYHTTP_STREAM_DIED) {
tinyhttp_stream_free(stream);
remove_from_ready_queue(server, idx);
CLOSESOCKET(sock);
invalidate_handles_to_stream(server, stream);
server->stream_sockets[idx] = INVALID_SOCKET;
server->num_conns--;
}
return 0;
}
#endif
TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config)
{
#if !TINYHTTP_HTTPS_ENABLE
if (config.secure)
return NULL;
#endif
TinyHTTPServer *server = malloc(sizeof(TinyHTTPServer));
if (server == NULL)
return NULL;
server->num_conns = 0;
server->ready_head = 0;
server->ready_count = 0;
for (int i = 0; i < TINYHTTP_SERVER_CONN_LIMIT; i++) {
server->stream_gens[i] = 1;
server->stream_sockets[i] = INVALID_SOCKET;
}
server->plain_listen_socket = socket_listen(config.plain_addr,
config.plain_port, config.plain_backlog, config.reuse);
#if defined(_WIN32)
server->deinit_winsock = 0;
if (server->plain_listen_socket == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) {
WSADATA data;
if (WSAStartup(MAKEWORD(2, 2), &data) == NO_ERROR) {
server->deinit_winsock = 1;
server->plain_listen_socket = socket_listen(config.plain_addr,
config.plain_port, config.plain_backlog, config.reuse);
}
}
#endif
if (server->plain_listen_socket == INVALID_SOCKET) {
free(server);
return NULL;
}
server->secure_listen_socket = INVALID_SOCKET;
#if TINYHTTP_HTTPS_ENABLE
if (config.secure) {
server->secure_listen_socket = socket_listen(config.secure_addr,
config.secure_port, config.secure_backlog, config.reuse);
if (server->secure_listen_socket == INVALID_SOCKET) {
CLOSESOCKET(server->plain_listen_socket);
free(server);
return NULL;
}
}
#endif // TINYHTTP_HTTPS_ENABLE
if (server_init_platform(server, config) < 0) {
CLOSESOCKET(server->plain_listen_socket);
if (server->secure_listen_socket != INVALID_SOCKET)
CLOSESOCKET(server->secure_listen_socket);
free(server);
return NULL;
}
return server;
}
void tinyhttp_server_free(TinyHTTPServer *server)
{
for (int i = 0; i < TINYHTTP_SERVER_CONN_LIMIT; i++) {
if (server->stream_sockets[i] != INVALID_SOCKET) {
CLOSESOCKET(server->stream_sockets[i]);
tinyhttp_stream_free(&server->stream_state[i]);
}
}
CLOSESOCKET(server->plain_listen_socket);
if (server->secure_listen_socket != INVALID_SOCKET)
CLOSESOCKET(server->secure_listen_socket);
server_free_platform(server);
free(server);
}
int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req,
TinyHTTPResponse *res, int timeout)
{
unsigned long long start_time_ms = -1ULL;
if (timeout >= 0) {
start_time_ms = get_current_time_ms();
if (start_time_ms == -1ULL)
return -1;
}
while (server->ready_count == 0) {
int timeout2;
if (timeout < 0)
timeout2 = -1;
else {
unsigned long long current_time_ms = get_current_time_ms();
if (current_time_ms == -1ULL)
return -1;
if (current_time_ms < start_time_ms)
return -1;
if (current_time_ms - start_time_ms > INT_MAX)
return -1;
int elapsed = (int) (current_time_ms - start_time_ms);
if (elapsed > timeout)
return 1;
timeout2 = timeout - elapsed;
}
int ret = process_network_events(server, timeout2);
if (ret < 0)
return -1;
}
ASSERT(server->ready_count > 0);
int idx = server->ready_queue[server->ready_head];
server->ready_head = (server->ready_head + 1) % TINYHTTP_SERVER_CONN_LIMIT;
server->ready_count--;
unsigned short gen = server->stream_gens[idx];
TinyHTTPStream *stream = &server->stream_state[idx];
*res = (TinyHTTPResponse) { .server=server, .gen=gen, .idx=idx };
*req = tinyhttp_stream_request(stream);
return 0;
}
void tinyhttp_response_status(TinyHTTPResponse res, int status)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_status(stream, status);
}
void tinyhttp_response_header(TinyHTTPResponse res, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
tinyhttp_response_header_fmt(res, fmt, args);
va_end(args);
}
void tinyhttp_response_header_fmt(TinyHTTPResponse res, const char *fmt, va_list args)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_header_fmt(stream, fmt, args);
}
void tinyhttp_response_body_setmincap(TinyHTTPResponse res, ptrdiff_t mincap)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_body_setmincap(stream, mincap);
}
char* tinyhttp_response_body_buf(TinyHTTPResponse res, ptrdiff_t *cap)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL) {
*cap = 0;
return NULL; // Invalid handle
}
return tinyhttp_stream_response_body_buf(stream, cap);
}
void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_body_ack(stream, num);
}
void tinyhttp_response_body(TinyHTTPResponse res, char *src, int len)
{
if (len < 0) len = strlen(src);
tinyhttp_response_body_setmincap(res, len);
ptrdiff_t cap;
char *dst = tinyhttp_response_body_buf(res, &cap);
if (dst)
memcpy(dst, src, len);
tinyhttp_response_body_ack(res, len);
}
void tinyhttp_response_send(TinyHTTPResponse res)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_send(stream);
invalidate_handles_to_stream(res.server, stream);
TinyHTTPServer *server = res.server;
int idx = stream - server->stream_state;
if (tinyhttp_stream_request(stream)) {
int ready_idx = (server->ready_head + server->ready_count) % TINYHTTP_SERVER_CONN_LIMIT;
server->ready_queue[ready_idx] = idx;
server->ready_count++;
}
#if defined(__linux__)
int state = tinyhttp_stream_state(stream);
SOCKET sock = server->stream_sockets[idx];
struct epoll_event epoll_buf;
epoll_buf.data.fd = idx;
epoll_buf.events = 0;
if (state & TINYHTTP_STREAM_RECV) epoll_buf.events |= EPOLLIN;
if (state & TINYHTTP_STREAM_SEND) epoll_buf.events |= EPOLLOUT;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_MOD, sock, &epoll_buf) < 0) {
ASSERT(0); // TODO
}
#elif defined(_WIN32)
SOCKET sock = server->stream_sockets[idx];
OVERLAPPED *recv_overlapped = &server->recv_overlapped[idx];
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
if (start_stream_operations(stream, sock, recv_overlapped, send_overlapped) < 0) {
ASSERT(0); // TODO
}
#endif
}
void tinyhttp_response_undo(TinyHTTPResponse res)
{
TinyHTTPStream *stream = response_to_stream(res);
if (stream == NULL)
return; // Invalid handle
tinyhttp_stream_response_undo(stream);
}