Improved documentation and fixed bugs

This commit is contained in:
2025-04-19 16:32:45 +02:00
parent 55c543bcac
commit a4aafb1ee8
10 changed files with 804 additions and 433 deletions
+2
View File
@@ -1,2 +1,4 @@
blog
blog.exe
example
example.exe
+73 -2
View File
@@ -1,3 +1,74 @@
# Makefile for tinyhttp with cross-platform support
# Supports Windows and Linux with various build configurations
all:
gcc example.c tinyhttp.c -o blog -Wall -Wextra -ggdb -lws2_32
# Detect operating system
ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows
# On Windows, we need to link with winsock library
LIBS := -lws2_32
RM := del /Q
# Windows executable extension
EXE := .exe
else
DETECTED_OS := $(shell uname -s)
LIBS :=
RM := rm -f
EXE :=
endif
# Compiler and flags
CC := gcc
CFLAGS := -Wall -Wextra -I.
LDFLAGS :=
# Project files
SRC := tinyhttp.c example.c
HEADERS := tinyhttp.h
TARGET := example$(EXE)
# Default target
.PHONY: all clean release debug asan coverage
# Default is release build
all: release
# Release build
release: CFLAGS += -O2 -DNDEBUG
release: $(TARGET)
# Debug build
debug: CFLAGS += -ggdb -DDEBUG
debug: $(TARGET)
# Address Sanitizer build
asan: CFLAGS += -fsanitize=address -fno-omit-frame-pointer -O1
asan: LDFLAGS += -fsanitize=address
asan: $(TARGET)
# Coverage build
coverage: CFLAGS += -fprofile-arcs -ftest-coverage -O0
coverage: LDFLAGS += -fprofile-arcs -ftest-coverage
coverage: $(TARGET)
# Compile and link
$(TARGET): $(SRC) $(HEADERS)
$(CC) $(CFLAGS) $(SRC) -o $@ $(LDFLAGS) $(LIBS)
# Clean build artifacts
clean:
ifeq ($(DETECTED_OS),Windows)
$(RM) $(TARGET) *.gcda *.gcno *.gcov
else
$(RM) $(TARGET) *.o *.gcda *.gcno *.gcov
endif
# Show help
help:
@echo "Available targets:"
@echo " all - Same as 'release'"
@echo " release - Optimized build (-O2, NDEBUG)"
@echo " debug - Debug build with symbols (-ggdb)"
@echo " asan - Address Sanitizer build"
@echo " coverage - Code coverage build"
@echo " clean - Remove build artifacts"
@echo " help - Show this help"
-73
View File
@@ -1,73 +0,0 @@
#include <signal.h>
#include "tinyhttp.h"
sig_atomic_t should_exit = 0;
static void
signal_handler(int sig)
{
if (sig == SIGINT)
should_exit = 1;
}
static void *memfunc(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data)
{
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_REALLOC:
return realloc(ptr, len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
int main(void)
{
signal(SIGINT, signal_handler);
TinyHTTPServerConfig config = {
.reuse = 1,
.plain_addr = "127.0.0.1",
.plain_port = 8080,
.secure = 0,
.secure_addr = "127.0.0.1",
.secure_port = 8443,
.cert_file = "cert.pem",
.private_key_file = "privkey.pem",
};
TinyHTTPServer *server = tinyhttp_server_init(config, memfunc, NULL);
if (server == NULL)
return -1;
TinyHTTPRouter *router = tinyhttp_router_init();
if (router == NULL) {
tinyhttp_server_free(server);
return -1;
}
tinyhttp_router_dir(router, "/", "/docroot", 0);
while (!should_exit) {
TinyHTTPRequest *req;
TinyHTTPResponse res;
if (tinyhttp_server_wait(server, &req, &res, 1000))
continue; // Timeout or error
tinyhttp_router_resolve(router, server, req, res);
}
tinyhttp_router_free(router);
tinyhttp_server_free(server);
return 0;
}
+232
View File
@@ -0,0 +1,232 @@
# TinyHTTP
TinyHTTP is a C library for implementing web servers. It offers two interfaces:
1. stream interface
1. server interface
The stream interface is a lower level interface to TinyHTTP's HTTP state machine. It's completely stand-alone and performs no internal I/O, making it ideal for embedding in applications with custom constraints.
The server interface is based on the stream interface and adds to it a platform-dependant I/O system. It uses the most performant I/O model the platform it's compiled for, but is single-threaded. The design goal of the server interface is ease of use and reasonable performance.
## Table of Contents
1. Stream Interface
1. Initialize a Stream
1. Stream I/O
1. Response Builer API
1. Basic Usage
1. Special Headers
1. Zero-Copy Response Building
1. Error Management
1. The State Bitset
1. Embedding in Custom Event Loops
1. Ready-based Event Loops
1. Completion-based Event Loops
## Stream Interface
The stream interface is based on the `TinyHTTPStream` object, which abstracts the communication between the server and one client. Generally, a non-blocking web server will hold an array of stream objects.
Applications must feed input bytes from the network into the stream and flush any bytes from the stream to the network. When the stream becomes "ready", the application creates a response using the response builder interface and submits it to the stream. If at any point something goes wrong, the stream is marked as DIED.
Since the stream object doesn't perform read/write operations on the socket directly but waits for the application to provide I/O bytes, it is trivial to add HTTPS support. Before bytes are read/written into the stream, applications can perform a TLS encryption/decryption step.
### Initialize a Stream
The only resources held by the stream object are the input and output buffers, which are contiguous buffers resized as needed. Therefore the only thing the stream depends on is a general purpose allocator. To keep the core of TinyHTTP dependency-free, allocation is done through a callback:
```c
static void *memfunc(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
// ptr is null
// len contains the allocation size
return malloc(len);
case TINYHTTP_MEM_FREE:
// ptr is the previous allocation
// len contains the size of the allocation (as requested during malloc)
free(ptr);
return NULL;
}
return NULL;
}
int main(void)
{
TinyHTTPStream stream;
tinyhttp_stream_init(&stream, memfunc, NULL);
// ...
}
```
in most cases this will be a wrapper of malloc/free.
### Stream I/O
When the stream is ready to receive bytes, applications can use the `tinyhttp_stream_recv_buf` to start a receive operation. This function will return a pointer into the stream's input buffer where data can be written from the network. The function returns from an output argument the capacity of the returned region. No more bytes than the capacity must be written.
When the write input the input buffer is complete, applications must call the `tinyhttp_stream_recv_ack` to complete the operation and let the stream object know how many bytes were written into the buffer.
It's not possible to start multiple recv operations at once. In other words, once you call `tinyhttp_stream_recv_buf`, you can't call it again until `tinyhttp_stream_recv_ack` is called.
Here's an example:
```c
ptrdiff_t cap;
char *dst;
// Get the input buffer's pointer
dst = tinyhttp_stream_recv_buf(stream, &cap);
// Write to the buffer
int num = recv(socket_fd, dst, cap, 0);
if (num < 0) {
// ... error ...
}
if (num == 0) {
// ... peer disconnected ...
}
// Tell the stream there are new bytes available
tinyhttp_stream_recv_ack(stream, num);
```
Flushing data from the stream works the same exact way, except you copy data out of the returned pointer instead of writing to it:
```c
ptrdiff_t len;
char *src;
// Get the output buffer's pointer
src = tinyhttp_stream_send_buf(stream, &len);
// Write to the buffer
int num = send(socket_fd, src, len, 0);
if (num < 0) {
// ... error ...
}
// Tell the stream how many bytes were flushed
tinyhttp_stream_send_ack(stream, num);
```
Note that if an error occurred and the stream is in the DIED state (more on that later), the `tinyhttp_stream_recv_buf` and `tinyhttp_stream_send_buf` will return a null pointer and a zero length/capacity. In this scenario, it doesn't matter what you do since an unrecoverable error occurred in the stream which will be shortly freed.
### Response Builder API
#### Basic Usage
When the stream reaches the "ready" state, applications can use the response builder interface to generate a response:
```c
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
if (req == NULL) {
// Not ready yet
} else {
// Ready!
// Get the parsed request
TinyHTTPRequest *request = tinyhttp_stream_request(stream);
// .. read the request ..
tinyhttp_stream_response_status(stream, 200);
tinyhttp_stream_response_header(stream, "Some-header: %d", 100);
tinyhttp_stream_response_header(stream, "Other-header: %s", "hello");
tinyhttp_stream_response_body(stream, "Hello, world!", -1);
tinyhttp_stream_response_send(stream);
}
```
The `tinyhttp_stream_request` function returns the parsed version of the buffered request. You must always access request information through the returned pointer and not copy and pointers from it since they may be invalidated if the stream decides to move data in the input buffer.
These response functions follow a strict state machine. You must first call the status function one time, then the header functions zero or more times, then the body functions zero or more times, and finally the send function. At any point you can drop the response and start from scratch by using the undo function:
```c
tinyhttp_stream_response_status(stream, 200);
if (error_occurred) {
tinyhttp_stream_response_undo(stream);
tinyhttp_stream_response_status(stream, 500);
}
tinyhttp_stream_response_send(stream);
```
#### Special Headers
TinyHTTP adds some special headers under the hood:
* Content-Length
* Transfer-Encoding
* Connection
so you should avoid adding these manually.
#### Zero-Copy Response Building
TODO: Talk about body_buf/ack/setmincap
#### Error Management
The response builder interface also uses sticky errors to lift the burdain of error management from the application.
If an error occurs while building the response, tinyhttp either sends a code 500 response or sets the DIED state. Either way, this is totally transparent to the application.
### The State Bitset
To know which operations are allowed on a stream at any given time, applications can use the `tinyhttp_stream_state` function:
```c
int state = tinyhttp_stream_state(stream);
if (state & TINYHTTP_STREAM_RECV) {
// stream is ready for input bytes
}
if (state & TINYHTTP_STREAM_SEND) {
// stream is ready for output
}
if (state & TINYHTTP_STREAM_RECV_STARTED) {
// A recv operation is in progress
}
if (state & TINYHTTP_STREAM_SEND_STARTED) {
// A send operation is in progress
}
if (state & TINYHTTP_STREAM_READY) {
// Request was buffered so it's now possible
// to build a response
}
if (state & TINYHTTP_STREAM_DIED) {
// The stream died
}
```
The `TINYHTTP_STREAM_RECV` and `TINYHTTP_STREAM_SEND` flags tell the application when it should call `tinyhttp_stream_recv_buf` or `tinyhttp_stream_send_buf`.
The `TINYHTTP_STREAM_RECV_STARTED` and `TINYHTTP_STREAM_SEND_STARTED` tell the application when calls to `tinyhttp_stream_recv_ack` or `tinyhttp_stream_send_ack` are expected due to previous calls to `tinyhttp_stream_recv_buf` or `tinyhttp_stream_send_buf`.
The `TINYHTTP_STREAM_READY` tells the application a request was buffered and it is now possible to build a response. The parsed request can by accessed with `tinyhttp_stream_request`.
The `TINYHTTP_STREAM_DIED` flag indicates an unreacoverable error occurred and all resources associated to the stream and the stream itself should be freed. DIED connections keep their input and output buffers intact to allow any pending asynchronous operations reading or writing to the stream's buffers to complete safely. When no one is using the stream's buffers, applications should free the stream. If you are handling states with a chain of if statements, you should handle the DIED state last since the code before it may cause the stream to go into the DIED state.
### Embedding in Custom Event Loops
In this sections there are some pseudocode examples of how one would use stream objects with various event loop models.
#### Ready-based Event Loops
Ready-based event loops are those which report that an operation can be performed on a given resource in a non-blocking way. Examples of ready-based event loops are those based on epoll, poll, and select.
TODO
#### Completion-based Event Loops
Completion-based event loops are an other way of referring to asynchronous I/O. With the completion-based model, when a thread performs a read or write operation on a resource, it does not wait for its completion. Instead, it continues running by doing other stuff. When no more work can be performed until an read/write operation completes, it waits until the next completion. Examples of this model are Windows's I/O completion ports and Linux's io_uring.
TODO
+7 -15
View File
@@ -21,9 +21,6 @@ static void *memfunc(TinyHTTPMemoryFuncTag tag,
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_REALLOC:
return realloc(ptr, len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
@@ -36,20 +33,10 @@ int main(void)
signal(SIGINT, signal_handler);
TinyHTTPServerConfig config = {
.reuse = 1,
.plain_addr = "127.0.0.1",
.plain_addr = NULL,
.plain_port = 8080,
.plain_backlog = 32,
.secure = 1,
.secure_addr = "127.0.0.1",
.secure_port = 8443,
.secure_backlog = 32,
.cert_file = "cert.pem",
.private_key_file = "privkey.pem",
};
TinyHTTPServer *server = tinyhttp_server_init(config, memfunc, NULL);
@@ -57,18 +44,23 @@ int main(void)
return -1;
while (!should_exit) {
TinyHTTPRequest *req;
TinyHTTPResponse res;
int ret = tinyhttp_server_wait(server, &req, &res, 1000);
if (ret < 0) return -1; // Error
if (ret > 0) continue; // Timeout
tinyhttp_response_status(res, 200);
tinyhttp_response_body_setmincap(res, 1<<10);
ptrdiff_t cap;
char *buf = tinyhttp_response_body_buf(res, &cap);
int len = snprintf(buf, cap, "Hello, world!");
int len = buf ? snprintf(buf, cap, "Hello, world!") : 0;
if (len < 0 || len > cap) abort();
tinyhttp_response_body_ack(res, len);
tinyhttp_response_send(res);
}
+95
View File
@@ -0,0 +1,95 @@
// A program using the stream interface may look like this:
//
// void respond(TinyHTTPStream *stream)
// {
// TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// if (req->method != TINYHTTP_METHOD_GET)
// tinyhttp_stream_status(stream, 405);
// else
// tinyhttp_stream_status(stream, 200);
// tinyhttp_stream_send(stream);
// }
//
// int main(void)
// {
// int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
//
// struct sockaddr_in buf;
// buf.sin_family = AF_INET;
// buf.sin_port = htons(port);
// buf.sin_addr.s_addr = htonl(INADDR_ANY);
// bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf));
//
// listen(listen_fd, 32);
//
// int num_conns = 0;
// int fds[1000];
// TinyHTTPStream streams[1000];
//
// for (int i = 0; i < 1000; i++)
// fds[i] = -1;
//
// for (;;) {
// // TODO: timeouts
//
// fd_set readset;
// fd_set writeset;
// FD_ZERO(&readset);
// FD_ZERO(&writeset);
//
// FD_SET(&readset);
// int max_fd = listen_fd;
// for (int i = 0; i < 1000; i++) {
// if (fds[i] == -1) continue;
// int state = tinyhttp_stream_state(&streams[i]);
// if (state & TINYHTTP_STREAM_RECV)
// FD_SET(fds[i], &readset);
// if (state & TINYHTTP_STREAM_SEND)
// FD_SET(fds[i], &writeset);
// if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))
// if (max_fd < fds[i]) max_fd = fds[i];
// }
//
// int num = select(max_fd+1, &readset, &writeset, NULL, NULL);
//
// if (FD_ISSET(liste_fd, &readset)) {
// // TODO
// }
//
// int ready_queue[1000];
// int ready_head = 0;
// int ready_count = 0;
// for (int i = 0; i < 1000; i++) {
// // TODO
// }
//
// while (ready_count > 0) {
//
// int idx = ready_queue[ready_head];
// TinyHTTPStream *stream = &streams[idx];
//
// TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// assert(req);
//
// respond(stream);
//
// ready_head = (ready_head + 1) % 1000;
// ready_count--;
// if (tinyhttp_stream_request(stream)) {
// ready_queue[(ready_head + ready_count) % 1000] = idx;
// ready_count++;
// }
// }
// }
//
// close(listen_fd);
// return 0;
// }
//
// Note that this example does not keep track of timeouts.
//
// The recv_buf/recv_ack and send_buf/send_ack interface is very handy as it's
// compatible both with readyness-based event loops (epoll, poll, select) and
// completion-based event loops (iocp, io_uring). Since the stream object does
// not read from the socket directly, you can easily implement HTTPS by providing
// it with TLS-encoded data instead of data directly from the socket.
+238
View File
@@ -0,0 +1,238 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// This is a bare-bones epoll HTTP server to get a practical
// req/sec limit.
//
// Compile:
// gcc fast_epoll.c -o fast_epoll -Wall -Wextra -O2 -DNDEBUG
int main()
{
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1");
return -1;
}
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
return -1;
}
{
int flags = fcntl(listen_fd, F_GETFL, 0);
if (flags < 0) {
perror("fcntl");
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(listen_fd, F_SETFL, flags) < 0) {
perror("fcntl");
return -1;
}
}
int one = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)) < 0) {
perror("setsockopt");
close(listen_fd);
return -1;
}
struct sockaddr_in bind_buf;
bind_buf.sin_family = AF_INET;
bind_buf.sin_port = htons(3333);
bind_buf.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
perror("bind");
close(listen_fd);
return -1;
}
if (listen(listen_fd, 32) < 0) {
perror("listen");
close(listen_fd);
return -1;
}
struct epoll_event epoll_buf;
epoll_buf.data.fd = -1;
epoll_buf.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &epoll_buf) < 0) {
perror("epoll_ctl");
return -1;
}
#define CAPACITY (1<<9)
#define IBUFCAP (1<<9)
#define OBUFCAP (1<<9)
#define MAXEXCH 100
int num_fds = 0;
int fds[CAPACITY];
int free_idx[CAPACITY];
char ibufs[CAPACITY][IBUFCAP];
char obufs[CAPACITY][OBUFCAP];
int sent[CAPACITY];
int received[CAPACITY];
int exchanged[CAPACITY];
for (int i = 0; i < CAPACITY; i++)
free_idx[i] = i;
for (;;) {
struct epoll_event batch[CAPACITY];
int num = epoll_wait(epoll_fd, batch, CAPACITY, -1);
for (int i = 0; i < num; i++) {
if (batch[i].data.fd < 0) {
while (num_fds < CAPACITY) {
int accepted_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);
if (accepted_fd < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
__builtin_trap();
}
int num_free = CAPACITY - num_fds;
int idx = free_idx[num_free-1];
struct epoll_event epoll_buf;
epoll_buf.data.fd = idx;
epoll_buf.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accepted_fd, &epoll_buf)) {
perror("epoll_ctl");
__builtin_trap();
}
fds[idx] = accepted_fd;
sent[idx] = 0;
received[idx] = 0;
exchanged[idx] = 0;
num_fds++;
}
} else {
int flags = batch[i].events;
int idx = batch[i].data.fd;
int fd = fds[idx];
char *ibuf = ibufs[idx];
char *obuf = obufs[idx];
int remove = 0;
if (flags & EPOLLIN) {
for (;;) {
if (received[idx] == IBUFCAP)
__builtin_trap();
int ret = recv(fd, ibuf + received[idx], IBUFCAP - received[idx], 0);
if (ret < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
remove = 1;
break;
}
if (ret == 0) {
remove = 1;
break;
}
received[idx] += ret;
}
if (!remove) {
int found = -1;
for (int i = 0; i < received[idx]; i++) {
if (3 < received[idx] - i
&& ibuf[i+0] == '\r'
&& ibuf[i+1] == '\n'
&& ibuf[i+2] == '\r'
&& ibuf[i+3] == '\n') {
found = i;
break;
}
}
if (found > -1) {
char resp[] = "HTTP/1.1 204 OK\r\nConnection: Keep-Alive\r\n\r\n";
memcpy(obuf + sent[idx], resp, sizeof(resp)-1);
sent[idx] += sizeof(resp)-1;
int head_len = found+4;
memcpy(ibuf, ibuf + received[idx], received[idx] - head_len);
received[idx] -= head_len;
struct epoll_event epoll_buf;
epoll_buf.data.fd = idx;
epoll_buf.events = EPOLLOUT;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &epoll_buf) < 0) {
perror("epoll_ctl");
__builtin_trap();
}
}
}
}
if (!remove && (flags & EPOLLOUT)) {
int flushed = 0;
do {
int ret = send(fd, obuf + flushed, sent[idx] - flushed, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
__builtin_trap();
}
if (ret == 0)
__builtin_trap();
flushed += ret;
} while (flushed < sent[idx]);
memmove(obuf, obuf + flushed, sent[idx] - flushed);
sent[idx] -= flushed;
if (sent[idx] == 0) {
struct epoll_event epoll_buf;
epoll_buf.data.fd = idx;
epoll_buf.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &epoll_buf) < 0) {
perror("epoll_ctl");
__builtin_trap();
}
}
exchanged[idx]++;
if (exchanged[idx] == MAXEXCH)
remove = 1;
}
if (remove) {
close(fds[idx]);
fds[idx] = -1;
int num_free = CAPACITY - num_fds;
free_idx[num_free] = idx;
num_fds--;
}
}
}
}
close(epoll_fd);
}
+95 -48
View File
@@ -45,6 +45,7 @@
#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
@@ -475,12 +476,6 @@ callback_malloc(TinyHTTPByteQueue *queue, ptrdiff_t len)
return queue->memfunc(TINYHTTP_MEM_MALLOC, NULL, len, queue->memfuncdata);
}
static void*
callback_realloc(TinyHTTPByteQueue *queue, void *ptr, ptrdiff_t len)
{
return queue->memfunc(TINYHTTP_MEM_REALLOC, ptr, len, queue->memfuncdata);
}
static void
callback_free(TinyHTTPByteQueue *queue, void *ptr, ptrdiff_t len)
{
@@ -961,6 +956,9 @@ void tinyhttp_stream_init(TinyHTTPStream *stream, TinyHTTPMemoryFunc memfunc, vo
// 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);
}
@@ -968,11 +966,15 @@ void tinyhttp_stream_init(TinyHTTPStream *stream, TinyHTTPMemoryFunc memfunc, vo
// See tinyhttp.h
void tinyhttp_stream_free(TinyHTTPStream *stream)
{
if (stream->state == TINYHTTP_STREAM_FREE)
return;
byte_queue_free(&stream->out);
byte_queue_free(&stream->in);
stream->state = TINYHTTP_STREAM_FREE;
stream->state = 0;
}
// See tinyhttp.h
void tinyhttp_stream_kill(TinyHTTPStream *stream)
{
stream->state |= TINYHTTP_STREAM_DIED;
}
// See tinyhttp.h
@@ -984,23 +986,22 @@ int tinyhttp_stream_state(TinyHTTPStream *stream)
int state = stream->state;
// The TINYHTTP_STREAM_FREE state is exclusive
if (state == TINYHTTP_STREAM_FREE)
return 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 (stream->reqsize > 0)
state |= TINYHTTP_STREAM_READY;
// 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;
@@ -1015,7 +1016,7 @@ int tinyhttp_stream_state(TinyHTTPStream *stream)
char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE) {
if (stream->state & TINYHTTP_STREAM_DIED) {
*cap = 0;
return NULL;
}
@@ -1059,6 +1060,9 @@ should_keep_alive(TinyHTTPStream *stream)
if (stream->req.minor == 0)
return 0;
if (stream->numexch >= 100) // TODO: Make this a parameter
return 0;
return 1;
}
@@ -1085,7 +1089,7 @@ process_next_request(TinyHTTPStream *stream)
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)) {
tinyhttp_stream_free(stream);
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
stream->state |= TINYHTTP_STREAM_CLOSE;
@@ -1121,7 +1125,7 @@ process_next_request(TinyHTTPStream *stream)
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
byte_queue_write_ack(&stream->in, num);
@@ -1141,7 +1145,7 @@ void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num)
char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return NULL;
return byte_queue_read_buf(&stream->out, len);
@@ -1151,13 +1155,13 @@ char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len)
void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
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)) {
tinyhttp_stream_free(stream);
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
}
@@ -1166,7 +1170,7 @@ void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num)
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (value)
@@ -1205,7 +1209,7 @@ TinyHTTPRequest *tinyhttp_stream_request(TinyHTTPStream *stream)
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_STATUS) {
@@ -1232,7 +1236,7 @@ void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ..
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_HEADER) {
@@ -1270,7 +1274,7 @@ append_special_headers(TinyHTTPStream *stream)
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
@@ -1293,7 +1297,7 @@ void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t m
char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state == TINYHTTP_STREAM_DIED)
return NULL;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
@@ -1318,7 +1322,7 @@ char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state != TINYHTTP_OUTPUT_STATE_BODY) {
@@ -1364,7 +1368,7 @@ void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num)
void tinyhttp_stream_response_send(TinyHTTPStream *stream)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
if (stream->output_state == TINYHTTP_OUTPUT_STATE_NONE)
@@ -1425,7 +1429,7 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
DUMP_STATE(tinyhttp_stream_state(stream));
if (byte_queue_error(&stream->out)) {
tinyhttp_stream_free(stream);
stream->state |= TINYHTTP_STREAM_DIED;
return;
}
@@ -1451,7 +1455,7 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
void tinyhttp_stream_response_undo(TinyHTTPStream *stream)
{
// Sticky error
if (stream->state == TINYHTTP_STREAM_FREE)
if (stream->state & TINYHTTP_STREAM_DIED)
return;
byte_queue_remove_after_lock(&stream->out);
@@ -1594,6 +1598,26 @@ invalidate_handles_to_stream(TinyHTTPServer *server, TinyHTTPStream *stream)
*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--;
}
#if defined(__linux__)
static void
@@ -1657,6 +1681,12 @@ accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secu
}
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;
@@ -1670,6 +1700,10 @@ accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secu
TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata);
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;
@@ -1723,7 +1757,7 @@ process_network_events(TinyHTTPServer *server, int timeout)
TinyHTTPStream *stream = &server->stream_state[idx];
if (flags & (EPOLLERR | EPOLLHUP))
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
int state = tinyhttp_stream_state(stream);
if (flags & EPOLLIN) {
@@ -1736,11 +1770,11 @@ process_network_events(TinyHTTPServer *server, int timeout)
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
break;
}
if (ret == 0) {
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
break;
}
#if DUMP_IO
@@ -1762,11 +1796,11 @@ process_network_events(TinyHTTPServer *server, int timeout)
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
break;
}
if (ret == 0) {
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
break;
}
#if DUMP_IO
@@ -1784,22 +1818,23 @@ process_network_events(TinyHTTPServer *server, int timeout)
tmp.data.fd = idx;
tmp.events = 0;
if (new_state & TINYHTTP_STREAM_RECV) tmp.events |= EPOLLIN;
if (new_state & TINYHTTP_STREAM_RECV) tmp.events |= EPOLLOUT;
if (new_state & TINYHTTP_STREAM_SEND) tmp.events |= EPOLLOUT;
if (epoll_ctl(server->epoll_fd, EPOLL_CTL_MOD, sock, &tmp) < 0) {
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
new_state = tinyhttp_stream_state(stream);
}
}
if (state & TINYHTTP_STREAM_READY) {
if ((new_state & TINYHTTP_STREAM_READY) && !(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_FREE) {
// TODO: Remove from the ready list
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--;
@@ -1972,6 +2007,12 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
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;
@@ -1985,6 +2026,10 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata);
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1);
int state = tinyhttp_stream_state(stream);
DUMP_STATE(tinyhttp_stream_state(stream));
@@ -2107,11 +2152,13 @@ process_network_events(TinyHTTPServer *server, int timeout)
OVERLAPPED *recv_overlapped = &server->recv_overlapped[idx];
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
int old_state = tinyhttp_stream_state(stream);
DUMP_STATE(tinyhttp_stream_state(stream));
if (!result) {
// A read or write operation failed
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
} else {
if (recv_overlapped == overlapped) {
@@ -2121,7 +2168,7 @@ process_network_events(TinyHTTPServer *server, int timeout)
#endif
tinyhttp_stream_recv_ack(stream, transferred);
if (transferred == 0)
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
} else {
ASSERT(send_overlapped == overlapped);
#if DUMP_IO
@@ -2132,14 +2179,14 @@ process_network_events(TinyHTTPServer *server, int timeout)
}
if (start_stream_operations(stream, sock, recv_overlapped, send_overlapped) < 0)
tinyhttp_stream_free(stream);
tinyhttp_stream_kill(stream);
}
DUMP_STATE(tinyhttp_stream_state(stream));
int state = tinyhttp_stream_state(stream);
if (state & TINYHTTP_STREAM_READY) {
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++;
@@ -2147,8 +2194,9 @@ process_network_events(TinyHTTPServer *server, int timeout)
DUMP_STATE(tinyhttp_stream_state(stream));
if (state == TINYHTTP_STREAM_FREE) {
// TODO: Remove from the ready list
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;
@@ -2379,9 +2427,8 @@ void tinyhttp_response_send(TinyHTTPResponse res)
server->ready_count++;
}
int state = tinyhttp_stream_state(stream);
#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;
+52 -285
View File
@@ -1,6 +1,3 @@
#include <stddef.h>
#include <stdarg.h>
////////////////////////////////////////////////////////////////////////////
// LICENSE //
////////////////////////////////////////////////////////////////////////////
@@ -26,192 +23,25 @@
// IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////
// OVERVIEW //
////////////////////////////////////////////////////////////////////////////
//
// TinyHTTP is the implementation of an HTTP sever.
//
// Users can use one of two interfaces:
//
// 1) Stream interface: This is the implementation of a single HTTP connection.
// It abstracts the communication between the server and one client. The
// advantage is it does not depend on specific I/O and can easily be embedded
// in the user's I/O model.
//
// 2) Server interface: This is the full server implementation. It uses the
// streaming interface by adding platform-specific I/O primitives. It uses
// epoll on Linux and I/O completion ports on Windows. This can also serve
// as reference for how to use the streaming interface to build a custom
// server abstraction.
//
// A program using the server interface looks like this:
//
// int main(void)
// {
// HTTPServerConfig config = {
// // ... set config here ...
// };
// HTTPServer *s = http_server_init(config);
// if (s == NULL) return -1;
//
// for (;;) {
// TinyHTTPRequest *req;
// TinyHTTPResponse res;
//
// int ret = http_server_wait(s, &req, &res, 1000);
// if (ret == 1) continue; // Timeout
// if (ret < 0) continue; // Error
//
// // Respond
//
// if (req->method == TINYHTTP_METHOD_POST) {
// tinyhttp_response_status(res, 405);
// tinyhttp_response_send(res);
// continue;
// }
//
// tinyhttp_response_status(res, 200);
// tinyhttp_response_header(res, "Server: TinyHTTP version %d", 0);
// tinyhttp_response_body_setmincap(res, 1<<9);
// ptrdiff_t cap;
// char *dst = tinyhttp_response_body_buf(res, &cap);
// int len = snprintf(dst, cap, "Hello, world!");
// if (len < 0 || len > cap) {
// tinyhttp_response_undo(res);
// tinyhttp_response_status(res, 500);
// }
// tinyhttp_response_send(res);
// }
//
// http_server_free(s);
// return 0;
// }
//
// TODO: Make sure this example is up to date
//
// Note that this example does full error checking. The public API of tinyhttp
// tries very hard to handle any errors internally, keeping your routes as simple
// as possible.
//
// Once you get a response object from the wait function, you don't need to respond
// immediately. For instance if the response requires an operation to complete, you
// can store the response handle somewhere and accept new responses in the mean time.
//
// A program using the stream interface may look like this:
//
// void respond(TinyHTTPStream *stream)
// {
// TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// if (req->method != TINYHTTP_METHOD_GET)
// tinyhttp_stream_status(stream, 405);
// else
// tinyhttp_stream_status(stream, 200);
// tinyhttp_stream_send(stream);
// }
//
// int main(void)
// {
// int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
//
// struct sockaddr_in buf;
// buf.sin_family = AF_INET;
// buf.sin_port = htons(port);
// buf.sin_addr.s_addr = htonl(INADDR_ANY);
// bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf));
//
// listen(listen_fd, 32);
//
// int num_conns = 0;
// int fds[1000];
// TinyHTTPStream streams[1000];
//
// for (int i = 0; i < 1000; i++)
// fds[i] = -1;
//
// for (;;) {
// // TODO: timeouts
//
// fd_set readset;
// fd_set writeset;
// FD_ZERO(&readset);
// FD_ZERO(&writeset);
//
// FD_SET(&readset);
// int max_fd = listen_fd;
// for (int i = 0; i < 1000; i++) {
// if (fds[i] == -1) continue;
// int state = tinyhttp_stream_state(&streams[i]);
// if (state & TINYHTTP_STREAM_RECV)
// FD_SET(fds[i], &readset);
// if (state & TINYHTTP_STREAM_SEND)
// FD_SET(fds[i], &writeset);
// if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))
// if (max_fd < fds[i]) max_fd = fds[i];
// }
//
// int num = select(max_fd+1, &readset, &writeset, NULL, NULL);
//
// if (FD_ISSET(liste_fd, &readset)) {
// // TODO
// }
//
// int ready_queue[1000];
// int ready_head = 0;
// int ready_count = 0;
// for (int i = 0; i < 1000; i++) {
// // TODO
// }
//
// while (ready_count > 0) {
//
// int idx = ready_queue[ready_head];
// TinyHTTPStream *stream = &streams[idx];
//
// TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// assert(req);
//
// respond(stream);
//
// ready_head = (ready_head + 1) % 1000;
// ready_count--;
// if (tinyhttp_stream_request(stream)) {
// ready_queue[(ready_head + ready_count) % 1000] = idx;
// ready_count++;
// }
// }
// }
//
// close(listen_fd);
// return 0;
// }
//
// Note that this example does not keep track of timeouts.
//
// The recv_buf/recv_ack and send_buf/send_ack interface is very handy as it's
// compatible both with readyness-based event loops (epoll, poll, select) and
// completion-based event loops (iocp, io_uring). Since the stream object does
// not read from the socket directly, you can easily implement HTTPS by providing
// it with TLS-encoded data instead of data directly from the socket.
////////////////////////////////////////////////////////////////////////////
// CONFIGURATION //
////////////////////////////////////////////////////////////////////////////
#include <stddef.h>
#include <stdarg.h>
#ifdef TINYHTTP_CUSTOMCONFIG
#include "tinyhttp_config.h"
#else
#define TINYHTTP_SERVER_ENABLE 1
#define TINYHTTP_ROUTER_ENABLE 0
#define TINYHTTP_HTTPS_ENABLE 0
#define TINYHTTP_ROUTER_MAX_PATH_COMPONENTS 32
#define TINYHTTP_HEADER_LIMIT 32
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
#endif
////////////////////////////////////////////////////////////////////////////
// HTTP SERVER INTEFACE //
////////////////////////////////////////////////////////////////////////////
// Opaque types
typedef struct TinyHTTPServer TinyHTTPServer;
typedef struct TinyHTTPRouter TinyHTTPRouter;
typedef enum {
TINYHTTP_METHOD_GET,
@@ -263,114 +93,15 @@ typedef struct {
typedef enum {
TINYHTTP_MEM_MALLOC,
TINYHTTP_MEM_REALLOC,
TINYHTTP_MEM_FREE,
} TinyHTTPMemoryFuncTag;
typedef void*(*TinyHTTPMemoryFunc)(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data);
#ifdef TINYHTTP_SERVER_ENABLE
// TODO: Comment
TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config,
TinyHTTPMemoryFunc memfunc, void *memfuncdata);
// TODO: Comment
void tinyhttp_server_free(TinyHTTPServer *server);
// TODO: Comment
int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req,
TinyHTTPResponse *res, int timeout);
// TODO: Comment
void tinyhttp_response_status(TinyHTTPResponse res, int status);
// TODO: Comment
void tinyhttp_response_header(TinyHTTPResponse res,
const char *fmt, ...);
// TODO: Comment
void tinyhttp_response_header_fmt(TinyHTTPResponse res,
const char *fmt, va_list args);
// TODO: Comment
void tinyhttp_response_body_setmincap(TinyHTTPResponse res,
ptrdiff_t mincap);
// TODO: Comment
char *tinyhttp_response_body_buf(TinyHTTPResponse res, ptrdiff_t *cap);
// TODO: Comment
void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num);
// TODO: Comment
void tinyhttp_response_send(TinyHTTPResponse res);
// TODO: Comment
void tinyhttp_response_undo(TinyHTTPResponse res);
#endif // TINYHTTP_SERVER_ENABLE
////////////////////////////////////////////////////////////////////////////
// HTTP STREAM INTEFACE //
////////////////////////////////////////////////////////////////////////////
//
// The TinyHTTPStream object abstracts the HTTP request/response
// state machine in a platorm-agnostic way.
//
// The connection is first initialized with [tinyhttp_stream_init].
// From this point on, the connection interacts with the network
// using the [tinyhttp_stream_net_{recv|send}_{buf|ack}] functions.
//
// The state of the connection can be queried using the
// [tinyhttp_stream_state] function. If the state is [TINYHTTP_STREAM_FREE],
// the connection was terminated and the caller must release
// any resources it allocated for it. If the connection is still
// active, zero or more of these flags may be set:
//
// TINYHTTP_STREAM_READY
// An HTTP request was completely buffered and its parsed
// version can be accessed through the [tinyhttp_stream_request]
// function.
//
// TINYHTTP_STREAM_RECV
// The connection expects some bytes from the network.
//
// TINYHTTP_STREAM_SEND
// The connection needs to send bytes on the network
//
// Any call to [tinyhttp_stream_*] functions may cause this state
// to change. When the HTTP-CONN_READY flag is set, the caller
// must create a response by calling these functions in
// sequence:
//
// tinyhttp_stream_response_status
// tinyhttp_stream_response_header (optional)
// tinyhttp_stream_response_body_setmincap/buf/ack (optional)
// tinyhttp_stream_response_send
//
// The header and body functions may be called any number
// of times.
//
// When [tinyhttp_stream_response_send] is called, the buffered
// request is removed and the following buffered requests
// is parsed. If no requests are left, the TINYHTTP_STREAM_READY
// state is unset.
//
// If at any point you want to change the response, you
// can undo all the calls by calling:
//
// tinyhttp_stream_response_undo
//
// To start again from [tinyhttp_stream_response_status]
//
// Note that due to pipelining, the stream may still be reading
// after sending a response.
// See [tinyhttp_stream_state]
enum {
TINYHTTP_STREAM_FREE = 1 << 0,
TINYHTTP_STREAM_DIED = 1 << 0,
TINYHTTP_STREAM_READY = 1 << 1,
TINYHTTP_STREAM_RECV = 1 << 2,
TINYHTTP_STREAM_SEND = 1 << 3,
@@ -423,6 +154,7 @@ typedef unsigned long long TinyHTTPByteQueueOffset;
typedef struct {
int state;
int numexch;
int chunked;
int keepalive;
ptrdiff_t reqsize;
@@ -453,6 +185,9 @@ tinyhttp_stream_init(TinyHTTPStream *stream,
// calls become no-ops.
void tinyhttp_stream_free(TinyHTTPStream *stream);
// TODO: Comment
void tinyhttp_stream_kill(TinyHTTPStream *stream);
// Returns the current state of the connection
//
// TODO: List the possible states
@@ -526,6 +261,10 @@ void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ..
// TODO: Comment
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args);
// TODO: Comment
// TODO: Implement
void tinyhttp_stream_response_body(TinyHTTPStream *stream, char *src, int len);
// TODO: Comment
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
@@ -541,13 +280,43 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream);
// TODO: Comment
void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
////////////////////////////////////////////////////////////////////////////
// HTTP ROUTER //
////////////////////////////////////////////////////////////////////////////
// TODO: Comment
TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config,
TinyHTTPMemoryFunc memfunc, void *memfuncdata);
#ifdef TINYHTTP_ROUTER_ENABLE
// TODO: Comment
void tinyhttp_server_free(TinyHTTPServer *server);
typedef struct TinyHTTPRouter TinyHTTPRouter;
// TODO: Comment
int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req,
TinyHTTPResponse *res, int timeout);
// TODO: Comment
void tinyhttp_response_status(TinyHTTPResponse res, int status);
// TODO: Comment
void tinyhttp_response_header(TinyHTTPResponse res,
const char *fmt, ...);
// TODO: Comment
void tinyhttp_response_header_fmt(TinyHTTPResponse res,
const char *fmt, va_list args);
// TODO: Comment
void tinyhttp_response_body_setmincap(TinyHTTPResponse res,
ptrdiff_t mincap);
// TODO: Comment
char *tinyhttp_response_body_buf(TinyHTTPResponse res, ptrdiff_t *cap);
// TODO: Comment
void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num);
// TODO: Comment
void tinyhttp_response_send(TinyHTTPResponse res);
// TODO: Comment
void tinyhttp_response_undo(TinyHTTPResponse res);
// TODO: Comment
TinyHTTPRouter *tinyhttp_router_init(void);
@@ -560,5 +329,3 @@ void tinyhttp_router_resolve(TinyHTTPRouter *router, TinyHTTPServer *server, Tin
// TODO: Comment
void tinyhttp_router_dir(TinyHTTPRouter *router, const char *endpoint, const char *path, int dir_listing);
#endif // TINYHTTP_ROUTER_ENABLE
View File