Improved documentation and fixed bugs
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
blog
|
blog
|
||||||
blog.exe
|
blog.exe
|
||||||
|
example
|
||||||
|
example.exe
|
||||||
@@ -1,3 +1,74 @@
|
|||||||
|
# Makefile for tinyhttp with cross-platform support
|
||||||
|
# Supports Windows and Linux with various build configurations
|
||||||
|
|
||||||
all:
|
# Detect operating system
|
||||||
gcc example.c tinyhttp.c -o blog -Wall -Wextra -ggdb -lws2_32
|
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"
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -21,9 +21,6 @@ static void *memfunc(TinyHTTPMemoryFuncTag tag,
|
|||||||
case TINYHTTP_MEM_MALLOC:
|
case TINYHTTP_MEM_MALLOC:
|
||||||
return malloc(len);
|
return malloc(len);
|
||||||
|
|
||||||
case TINYHTTP_MEM_REALLOC:
|
|
||||||
return realloc(ptr, len);
|
|
||||||
|
|
||||||
case TINYHTTP_MEM_FREE:
|
case TINYHTTP_MEM_FREE:
|
||||||
free(ptr);
|
free(ptr);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -36,20 +33,10 @@ int main(void)
|
|||||||
signal(SIGINT, signal_handler);
|
signal(SIGINT, signal_handler);
|
||||||
|
|
||||||
TinyHTTPServerConfig config = {
|
TinyHTTPServerConfig config = {
|
||||||
|
|
||||||
.reuse = 1,
|
.reuse = 1,
|
||||||
|
.plain_addr = NULL,
|
||||||
.plain_addr = "127.0.0.1",
|
|
||||||
.plain_port = 8080,
|
.plain_port = 8080,
|
||||||
.plain_backlog = 32,
|
.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);
|
TinyHTTPServer *server = tinyhttp_server_init(config, memfunc, NULL);
|
||||||
@@ -57,21 +44,26 @@ int main(void)
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
while (!should_exit) {
|
while (!should_exit) {
|
||||||
|
|
||||||
TinyHTTPRequest *req;
|
TinyHTTPRequest *req;
|
||||||
TinyHTTPResponse res;
|
TinyHTTPResponse res;
|
||||||
|
|
||||||
int ret = tinyhttp_server_wait(server, &req, &res, 1000);
|
int ret = tinyhttp_server_wait(server, &req, &res, 1000);
|
||||||
if (ret < 0) return -1; // Error
|
if (ret < 0) return -1; // Error
|
||||||
if (ret > 0) continue; // Timeout
|
if (ret > 0) continue; // Timeout
|
||||||
|
|
||||||
tinyhttp_response_status(res, 200);
|
tinyhttp_response_status(res, 200);
|
||||||
|
|
||||||
tinyhttp_response_body_setmincap(res, 1<<10);
|
tinyhttp_response_body_setmincap(res, 1<<10);
|
||||||
ptrdiff_t cap;
|
ptrdiff_t cap;
|
||||||
char *buf = tinyhttp_response_body_buf(res, &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();
|
if (len < 0 || len > cap) abort();
|
||||||
tinyhttp_response_body_ack(res, len);
|
tinyhttp_response_body_ack(res, len);
|
||||||
|
|
||||||
tinyhttp_response_send(res);
|
tinyhttp_response_send(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
tinyhttp_server_free(server);
|
tinyhttp_server_free(server);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
+103
-56
@@ -45,6 +45,7 @@
|
|||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
#define SOCKET int
|
#define SOCKET int
|
||||||
#define CLOSESOCKET close
|
#define CLOSESOCKET close
|
||||||
#define INVALID_SOCKET -1
|
#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);
|
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
|
static void
|
||||||
callback_free(TinyHTTPByteQueue *queue, void *ptr, ptrdiff_t len)
|
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
|
// Set the maximum content length
|
||||||
stream->bodylimit = 1<<29; // 500MB
|
stream->bodylimit = 1<<29; // 500MB
|
||||||
|
|
||||||
|
stream->reqsize = 0;
|
||||||
|
stream->numexch = 0;
|
||||||
|
|
||||||
byte_queue_init(&stream->in, 1<<29, memfunc, memfuncdata);
|
byte_queue_init(&stream->in, 1<<29, memfunc, memfuncdata);
|
||||||
byte_queue_init(&stream->out, 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
|
// See tinyhttp.h
|
||||||
void tinyhttp_stream_free(TinyHTTPStream *stream)
|
void tinyhttp_stream_free(TinyHTTPStream *stream)
|
||||||
{
|
{
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
|
||||||
return;
|
|
||||||
byte_queue_free(&stream->out);
|
byte_queue_free(&stream->out);
|
||||||
byte_queue_free(&stream->in);
|
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
|
// See tinyhttp.h
|
||||||
@@ -984,23 +986,22 @@ int tinyhttp_stream_state(TinyHTTPStream *stream)
|
|||||||
|
|
||||||
int state = stream->state;
|
int state = stream->state;
|
||||||
|
|
||||||
// The TINYHTTP_STREAM_FREE state is exclusive
|
if ((state & TINYHTTP_STREAM_DIED) == 0) {
|
||||||
if (state == TINYHTTP_STREAM_FREE)
|
|
||||||
return state;
|
|
||||||
|
|
||||||
// If there is data to read in the output buffer,
|
if (stream->reqsize > 0)
|
||||||
// we are interested in sending data.
|
state |= TINYHTTP_STREAM_READY;
|
||||||
if (byte_queue_read_size(&stream->out))
|
|
||||||
state |= TINYHTTP_STREAM_SEND;
|
|
||||||
|
|
||||||
if (stream->reqsize > 0)
|
// If there is data to read in the output buffer,
|
||||||
state |= TINYHTTP_STREAM_READY;
|
// 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
|
// If we don't have a buffered request and the
|
||||||
// connection is not closing, we are interested
|
// connection is not closing, we are interested
|
||||||
// in receiving data.
|
// in receiving data.
|
||||||
if ((state & (TINYHTTP_STREAM_READY | TINYHTTP_STREAM_CLOSE)) == 0)
|
if ((state & (TINYHTTP_STREAM_READY | TINYHTTP_STREAM_CLOSE)) == 0)
|
||||||
state |= TINYHTTP_STREAM_RECV;
|
state |= TINYHTTP_STREAM_RECV;
|
||||||
|
}
|
||||||
|
|
||||||
if (byte_queue_write_started(&stream->in))
|
if (byte_queue_write_started(&stream->in))
|
||||||
state |= TINYHTTP_STREAM_RECV_STARTED;
|
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)
|
char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE) {
|
if (stream->state & TINYHTTP_STREAM_DIED) {
|
||||||
*cap = 0;
|
*cap = 0;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -1059,6 +1060,9 @@ should_keep_alive(TinyHTTPStream *stream)
|
|||||||
if (stream->req.minor == 0)
|
if (stream->req.minor == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (stream->numexch >= 100) // TODO: Make this a parameter
|
||||||
|
return 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1085,7 +1089,7 @@ process_next_request(TinyHTTPStream *stream)
|
|||||||
int status = -ret;
|
int status = -ret;
|
||||||
byte_queue_write_fmt(&stream->out, "HTTP/1.1 %d %s\r\n", status, get_status_text(status));
|
byte_queue_write_fmt(&stream->out, "HTTP/1.1 %d %s\r\n", status, get_status_text(status));
|
||||||
if (byte_queue_error(&stream->out)) {
|
if (byte_queue_error(&stream->out)) {
|
||||||
tinyhttp_stream_free(stream);
|
stream->state |= TINYHTTP_STREAM_DIED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stream->state |= TINYHTTP_STREAM_CLOSE;
|
stream->state |= TINYHTTP_STREAM_CLOSE;
|
||||||
@@ -1121,7 +1125,7 @@ process_next_request(TinyHTTPStream *stream)
|
|||||||
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num)
|
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
byte_queue_write_ack(&stream->in, num);
|
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)
|
char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return byte_queue_read_buf(&stream->out, len);
|
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)
|
void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
byte_queue_read_ack(&stream->out, num);
|
byte_queue_read_ack(&stream->out, num);
|
||||||
|
|
||||||
if (byte_queue_read_size(&stream->out) == 0 && (stream->state & TINYHTTP_STREAM_CLOSE)) {
|
if (byte_queue_read_size(&stream->out) == 0 && (stream->state & TINYHTTP_STREAM_CLOSE)) {
|
||||||
tinyhttp_stream_free(stream);
|
stream->state |= TINYHTTP_STREAM_DIED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1166,7 +1170,7 @@ void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num)
|
|||||||
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value)
|
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
@@ -1205,7 +1209,7 @@ TinyHTTPRequest *tinyhttp_stream_request(TinyHTTPStream *stream)
|
|||||||
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status)
|
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (stream->output_state != TINYHTTP_OUTPUT_STATE_STATUS) {
|
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)
|
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (stream->output_state != TINYHTTP_OUTPUT_STATE_HEADER) {
|
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)
|
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
|
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)
|
char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state == TINYHTTP_STREAM_DIED)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (stream->output_state == TINYHTTP_OUTPUT_STATE_HEADER) {
|
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)
|
void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (stream->output_state != TINYHTTP_OUTPUT_STATE_BODY) {
|
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)
|
void tinyhttp_stream_response_send(TinyHTTPStream *stream)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (stream->output_state == TINYHTTP_OUTPUT_STATE_NONE)
|
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));
|
DUMP_STATE(tinyhttp_stream_state(stream));
|
||||||
|
|
||||||
if (byte_queue_error(&stream->out)) {
|
if (byte_queue_error(&stream->out)) {
|
||||||
tinyhttp_stream_free(stream);
|
stream->state |= TINYHTTP_STREAM_DIED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1451,7 +1455,7 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream)
|
|||||||
void tinyhttp_stream_response_undo(TinyHTTPStream *stream)
|
void tinyhttp_stream_response_undo(TinyHTTPStream *stream)
|
||||||
{
|
{
|
||||||
// Sticky error
|
// Sticky error
|
||||||
if (stream->state == TINYHTTP_STREAM_FREE)
|
if (stream->state & TINYHTTP_STREAM_DIED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
byte_queue_remove_after_lock(&stream->out);
|
byte_queue_remove_after_lock(&stream->out);
|
||||||
@@ -1594,6 +1598,26 @@ invalidate_handles_to_stream(TinyHTTPServer *server, TinyHTTPStream *stream)
|
|||||||
*gen = 1;
|
*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__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1657,6 +1681,12 @@ accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secu
|
|||||||
}
|
}
|
||||||
errors = 0;
|
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)) {
|
if (socket_set_block(accepted_socket, 0)) {
|
||||||
CLOSESOCKET(accepted_socket);
|
CLOSESOCKET(accepted_socket);
|
||||||
continue;
|
continue;
|
||||||
@@ -1670,6 +1700,10 @@ accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secu
|
|||||||
TinyHTTPStream *stream = &server->stream_state[idx];
|
TinyHTTPStream *stream = &server->stream_state[idx];
|
||||||
|
|
||||||
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata);
|
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);
|
int state = tinyhttp_stream_state(stream);
|
||||||
|
|
||||||
struct epoll_event epoll_buf;
|
struct epoll_event epoll_buf;
|
||||||
@@ -1723,7 +1757,7 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
TinyHTTPStream *stream = &server->stream_state[idx];
|
TinyHTTPStream *stream = &server->stream_state[idx];
|
||||||
|
|
||||||
if (flags & (EPOLLERR | EPOLLHUP))
|
if (flags & (EPOLLERR | EPOLLHUP))
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
|
|
||||||
int state = tinyhttp_stream_state(stream);
|
int state = tinyhttp_stream_state(stream);
|
||||||
if (flags & EPOLLIN) {
|
if (flags & EPOLLIN) {
|
||||||
@@ -1736,11 +1770,11 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
break;
|
break;
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#if DUMP_IO
|
#if DUMP_IO
|
||||||
@@ -1762,11 +1796,11 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
break;
|
break;
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#if DUMP_IO
|
#if DUMP_IO
|
||||||
@@ -1784,22 +1818,23 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
tmp.data.fd = idx;
|
tmp.data.fd = idx;
|
||||||
tmp.events = 0;
|
tmp.events = 0;
|
||||||
if (new_state & TINYHTTP_STREAM_RECV) tmp.events |= EPOLLIN;
|
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) {
|
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);
|
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;
|
int ready_idx = (server->ready_head + server->ready_count) % TINYHTTP_SERVER_CONN_LIMIT;
|
||||||
server->ready_queue[ready_idx] = idx;
|
server->ready_queue[ready_idx] = idx;
|
||||||
server->ready_count++;
|
server->ready_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_state == TINYHTTP_STREAM_FREE) {
|
if (new_state & TINYHTTP_STREAM_DIED) {
|
||||||
// TODO: Remove from the ready list
|
remove_from_ready_queue(server, idx);
|
||||||
CLOSESOCKET(sock);
|
CLOSESOCKET(sock);
|
||||||
|
tinyhttp_stream_free(stream);
|
||||||
invalidate_handles_to_stream(server, stream);
|
invalidate_handles_to_stream(server, stream);
|
||||||
server->stream_sockets[idx] = INVALID_SOCKET;
|
server->stream_sockets[idx] = INVALID_SOCKET;
|
||||||
server->num_conns--;
|
server->num_conns--;
|
||||||
@@ -1972,6 +2007,12 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
|
|||||||
return;
|
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) {
|
if (socket_set_block(accepted_socket, 0) < 0) {
|
||||||
CLOSESOCKET(accepted_socket);
|
CLOSESOCKET(accepted_socket);
|
||||||
return;
|
return;
|
||||||
@@ -1985,6 +2026,10 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
|
|||||||
TinyHTTPStream *stream = &server->stream_state[idx];
|
TinyHTTPStream *stream = &server->stream_state[idx];
|
||||||
|
|
||||||
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata);
|
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);
|
int state = tinyhttp_stream_state(stream);
|
||||||
|
|
||||||
DUMP_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 *recv_overlapped = &server->recv_overlapped[idx];
|
||||||
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
|
OVERLAPPED *send_overlapped = &server->send_overlapped[idx];
|
||||||
|
|
||||||
|
int old_state = tinyhttp_stream_state(stream);
|
||||||
|
|
||||||
DUMP_STATE(tinyhttp_stream_state(stream));
|
DUMP_STATE(tinyhttp_stream_state(stream));
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// A read or write operation failed
|
// A read or write operation failed
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (recv_overlapped == overlapped) {
|
if (recv_overlapped == overlapped) {
|
||||||
@@ -2121,7 +2168,7 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
#endif
|
#endif
|
||||||
tinyhttp_stream_recv_ack(stream, transferred);
|
tinyhttp_stream_recv_ack(stream, transferred);
|
||||||
if (transferred == 0)
|
if (transferred == 0)
|
||||||
tinyhttp_stream_free(stream);
|
tinyhttp_stream_kill(stream);
|
||||||
} else {
|
} else {
|
||||||
ASSERT(send_overlapped == overlapped);
|
ASSERT(send_overlapped == overlapped);
|
||||||
#if DUMP_IO
|
#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)
|
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));
|
DUMP_STATE(tinyhttp_stream_state(stream));
|
||||||
|
|
||||||
int 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;
|
int ready_idx = (server->ready_head + server->ready_count) % TINYHTTP_SERVER_CONN_LIMIT;
|
||||||
server->ready_queue[ready_idx] = idx;
|
server->ready_queue[ready_idx] = idx;
|
||||||
server->ready_count++;
|
server->ready_count++;
|
||||||
@@ -2147,8 +2194,9 @@ process_network_events(TinyHTTPServer *server, int timeout)
|
|||||||
|
|
||||||
DUMP_STATE(tinyhttp_stream_state(stream));
|
DUMP_STATE(tinyhttp_stream_state(stream));
|
||||||
|
|
||||||
if (state == TINYHTTP_STREAM_FREE) {
|
if (state & TINYHTTP_STREAM_DIED) {
|
||||||
// TODO: Remove from the ready list
|
tinyhttp_stream_free(stream);
|
||||||
|
remove_from_ready_queue(server, idx);
|
||||||
CLOSESOCKET(sock);
|
CLOSESOCKET(sock);
|
||||||
invalidate_handles_to_stream(server, stream);
|
invalidate_handles_to_stream(server, stream);
|
||||||
server->stream_sockets[idx] = INVALID_SOCKET;
|
server->stream_sockets[idx] = INVALID_SOCKET;
|
||||||
@@ -2379,9 +2427,8 @@ void tinyhttp_response_send(TinyHTTPResponse res)
|
|||||||
server->ready_count++;
|
server->ready_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int state = tinyhttp_stream_state(stream);
|
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
|
int state = tinyhttp_stream_state(stream);
|
||||||
SOCKET sock = server->stream_sockets[idx];
|
SOCKET sock = server->stream_sockets[idx];
|
||||||
struct epoll_event epoll_buf;
|
struct epoll_event epoll_buf;
|
||||||
epoll_buf.data.fd = idx;
|
epoll_buf.data.fd = idx;
|
||||||
|
|||||||
+53
-286
@@ -1,6 +1,3 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// LICENSE //
|
// LICENSE //
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -26,192 +23,25 @@
|
|||||||
// IN THE SOFTWARE.
|
// 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.
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
#include <stddef.h>
|
||||||
// CONFIGURATION //
|
#include <stdarg.h>
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
#ifdef TINYHTTP_CUSTOMCONFIG
|
||||||
|
#include "tinyhttp_config.h"
|
||||||
|
#else
|
||||||
#define TINYHTTP_SERVER_ENABLE 1
|
#define TINYHTTP_SERVER_ENABLE 1
|
||||||
#define TINYHTTP_ROUTER_ENABLE 0
|
#define TINYHTTP_ROUTER_ENABLE 0
|
||||||
#define TINYHTTP_HTTPS_ENABLE 0
|
#define TINYHTTP_HTTPS_ENABLE 0
|
||||||
|
|
||||||
#define TINYHTTP_ROUTER_MAX_PATH_COMPONENTS 32
|
#define TINYHTTP_ROUTER_MAX_PATH_COMPONENTS 32
|
||||||
|
|
||||||
#define TINYHTTP_HEADER_LIMIT 32
|
#define TINYHTTP_HEADER_LIMIT 32
|
||||||
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
|
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
|
||||||
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
|
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// Opaque types
|
||||||
// HTTP SERVER INTEFACE //
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
typedef struct TinyHTTPServer TinyHTTPServer;
|
typedef struct TinyHTTPServer TinyHTTPServer;
|
||||||
|
typedef struct TinyHTTPRouter TinyHTTPRouter;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TINYHTTP_METHOD_GET,
|
TINYHTTP_METHOD_GET,
|
||||||
@@ -263,114 +93,15 @@ typedef struct {
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
TINYHTTP_MEM_MALLOC,
|
TINYHTTP_MEM_MALLOC,
|
||||||
TINYHTTP_MEM_REALLOC,
|
|
||||||
TINYHTTP_MEM_FREE,
|
TINYHTTP_MEM_FREE,
|
||||||
} TinyHTTPMemoryFuncTag;
|
} TinyHTTPMemoryFuncTag;
|
||||||
|
|
||||||
typedef void*(*TinyHTTPMemoryFunc)(TinyHTTPMemoryFuncTag tag,
|
typedef void*(*TinyHTTPMemoryFunc)(TinyHTTPMemoryFuncTag tag,
|
||||||
void *ptr, int len, void *data);
|
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]
|
// See [tinyhttp_stream_state]
|
||||||
enum {
|
enum {
|
||||||
TINYHTTP_STREAM_FREE = 1 << 0,
|
TINYHTTP_STREAM_DIED = 1 << 0,
|
||||||
TINYHTTP_STREAM_READY = 1 << 1,
|
TINYHTTP_STREAM_READY = 1 << 1,
|
||||||
TINYHTTP_STREAM_RECV = 1 << 2,
|
TINYHTTP_STREAM_RECV = 1 << 2,
|
||||||
TINYHTTP_STREAM_SEND = 1 << 3,
|
TINYHTTP_STREAM_SEND = 1 << 3,
|
||||||
@@ -423,6 +154,7 @@ typedef unsigned long long TinyHTTPByteQueueOffset;
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int state;
|
int state;
|
||||||
|
int numexch;
|
||||||
int chunked;
|
int chunked;
|
||||||
int keepalive;
|
int keepalive;
|
||||||
ptrdiff_t reqsize;
|
ptrdiff_t reqsize;
|
||||||
@@ -453,6 +185,9 @@ tinyhttp_stream_init(TinyHTTPStream *stream,
|
|||||||
// calls become no-ops.
|
// calls become no-ops.
|
||||||
void tinyhttp_stream_free(TinyHTTPStream *stream);
|
void tinyhttp_stream_free(TinyHTTPStream *stream);
|
||||||
|
|
||||||
|
// TODO: Comment
|
||||||
|
void tinyhttp_stream_kill(TinyHTTPStream *stream);
|
||||||
|
|
||||||
// Returns the current state of the connection
|
// Returns the current state of the connection
|
||||||
//
|
//
|
||||||
// TODO: List the possible states
|
// TODO: List the possible states
|
||||||
@@ -526,6 +261,10 @@ void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ..
|
|||||||
// TODO: Comment
|
// TODO: Comment
|
||||||
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args);
|
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
|
// TODO: Comment
|
||||||
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
|
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
|
||||||
|
|
||||||
@@ -541,13 +280,43 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream);
|
|||||||
// TODO: Comment
|
// TODO: Comment
|
||||||
void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
|
void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
// TODO: Comment
|
||||||
// HTTP ROUTER //
|
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
|
// TODO: Comment
|
||||||
TinyHTTPRouter *tinyhttp_router_init(void);
|
TinyHTTPRouter *tinyhttp_router_init(void);
|
||||||
@@ -559,6 +328,4 @@ void tinyhttp_router_free(TinyHTTPRouter *router);
|
|||||||
void tinyhttp_router_resolve(TinyHTTPRouter *router, TinyHTTPServer *server, TinyHTTPRequest *request, TinyHTTPResponse response);
|
void tinyhttp_router_resolve(TinyHTTPRouter *router, TinyHTTPServer *server, TinyHTTPRequest *request, TinyHTTPResponse response);
|
||||||
|
|
||||||
// TODO: Comment
|
// TODO: Comment
|
||||||
void tinyhttp_router_dir(TinyHTTPRouter *router, const char *endpoint, const char *path, int dir_listing);
|
void tinyhttp_router_dir(TinyHTTPRouter *router, const char *endpoint, const char *path, int dir_listing);
|
||||||
|
|
||||||
#endif // TINYHTTP_ROUTER_ENABLE
|
|
||||||
Reference in New Issue
Block a user