Full rewrite

This commit is contained in:
2025-05-07 23:57:07 +02:00
parent 82ef7ee170
commit 24736bc780
23 changed files with 4737 additions and 5791 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
*.out
*.exe
misc
coverage_report
+9 -114
View File
@@ -1,119 +1,14 @@
CC = gcc
RM = rm
MKDIR = mkdir
.PHONY: all report
EXT_WINDOWS = exe
EXT_LINUX = out
#LFLAGS = -lws2_32
LFLAGS = -lssl -lcrypto -ggdb
# Can be either RELEASE or DEBUG
BUILD = RELEASE
all:
#gcc -o test tests/test.c tests/test_branch_coverage.c http.c -fprofile-arcs -ftest-coverage
SUFFIX_RELEASE =
SUFFIX_DEBUG = _debug
# ------------------------------------------------------ #
TEST_CFILES = tinyhttp.c tests/picohttpparser.c tests/test.c tests/test_reuse.c tests/test_chunking.c tests/test_parse_request.c
TEST_HFILES = tinyhttp.h tests/picohttpparser.h
TEST_FLAGS = -Wall -Wextra
TEST_FLAGS_DEBUG = -ggdb
TEST_FLAGS_RELEASE = -O2 -DNDEBUG
TEST_FLAGS_WINDOWS = -lws2_32
TEST_FLAGS_WINDOWS_DEBUG =
TEST_FLAGS_WINDOWS_RELEASE =
TEST_FLAGS_LINUX =
TEST_FLAGS_LINUX_DEBUG =
TEST_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
DEMO0_CFILES = tinyhttp.c examples/server_api.c
DEMO0_HFILES = tinyhttp.h
DEMO0_FLAGS =
DEMO0_FLAGS_DEBUG = -ggdb
DEMO0_FLAGS_RELEASE = -O2 -DNDEBUG
DEMO0_FLAGS_WINDOWS = -lws2_32
DEMO0_FLAGS_WINDOWS_DEBUG =
DEMO0_FLAGS_WINDOWS_RELEASE =
DEMO0_FLAGS_LINUX =
DEMO0_FLAGS_LINUX_DEBUG =
DEMO0_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
DEMO1_CFILES = tinyhttp.c examples/stream_api_with_select.c
DEMO1_HFILES = tinyhttp.h
DEMO1_FLAGS =
DEMO1_FLAGS =
DEMO1_FLAGS_DEBUG = -ggdb
DEMO1_FLAGS_RELEASE = -O2 -DNDEBUG
DEMO1_FLAGS_WINDOWS = -lws2_32
DEMO1_FLAGS_WINDOWS_DEBUG =
DEMO1_FLAGS_WINDOWS_RELEASE =
DEMO1_FLAGS_LINUX =
DEMO1_FLAGS_LINUX_DEBUG =
DEMO1_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
# ------------------------------------------------------ #
# ------------------------------------------------------ #
ifeq ($(OS),Windows_NT)
OSTAG = WINDOWS
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
OSTAG = LINUX
endif
ifeq ($(UNAME_S),Darwin)
OSTAG = OSX
endif
endif
EXT = ${EXT_$(OSTAG)}
TEST_FLAGS += ${TEST_FLAGS_$(BUILD)}
TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)}
TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)_$(BUILD)}
DEMO0_FLAGS += ${DEMO0_FLAGS_$(BUILD)}
DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)}
DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)_$(BUILD)}
DEMO1_FLAGS += ${DEMO1_FLAGS_$(BUILD)}
DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)}
DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)_$(BUILD)}
SUFFIX = ${SUFFIX_$(BUILD)}
# ------------------------------------------------------ #
# ------------------------------------------------------ #
# ------------------------------------------------------ #
.PHONY: all clean
all: out/test$(SUFFIX).$(EXT) out/demo0$(SUFFIX).$(EXT) out/demo1$(SUFFIX).$(EXT)
out:
$(MKDIR) out
out/test$(SUFFIX).$(EXT): out $(TEST_CFILES) $(TEST_HFILES)
$(CC) -o $@ $(TEST_CFILES) $(TEST_FLAGS)
out/demo0$(SUFFIX).$(EXT): out $(DEMO0_CFILES) $(DEMO0_HFILES)
$(CC) -o $@ $(DEMO0_CFILES) $(DEMO0_FLAGS)
out/demo1$(SUFFIX).$(EXT): out $(DEMO1_CFILES) $(DEMO1_HFILES)
$(CC) -o $@ $(DEMO1_CFILES) $(DEMO1_FLAGS)
report:
lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1
genhtml coverage.info --output-directory coverage_report --rc lcov_branch_coverage=1 --rc genhtml_branch_coverage=1
clean:
$(RM) -fr out
rm *.gcda *.gcno coverage.info test test.exe
+169 -162
View File
@@ -1,199 +1,206 @@
# TinyHTTP
TinyHTTP is an HTTP server library. It's small, robust, and fast.
TinyHTTP is a library for building cross-platform and fully non-blocking HTTP 1.1 clients and servers in C.
**NOTE**: This is still a prototype! I got the basic version working and am spending some time making it more robust. After that, I will add HTTPS support, and work on compliancy to RFC 9112.
## Roadmap and status
## Features
* Self-contained
* Cross-Platform (Windows, Linux)
* HTTP/1.1 fully compliant to RFC 9112 with pipelining, chunked encoding, connection reuse
* Fully non-blocking (epoll on Linux, iocp on Windows)
* HTTPS (OpenSSL on Linux, Schannel on Windows) (in progress)
* Zero-copy interface
The project is still in the prototyping phase. I'm working on testing for robustness and compliance to RFC 9110, 9111, 9112, and 3986. The server is missing timers and HTTPS. The client is just a proof of concept at this point.
## Limitations
* Single-threaded
* IPv4 only
## Overview
## How it works / Getting Started
The architecture looks like this:
There are two ways to use TinyHTTP: the server interface and the stream interface.
The server interface is a complete server implementation designed to easily set up a new server.
The stream interface is more involved but completely stand-alone, doesn't performs I/O directly, and is easily embeddable in custom event loops. You can think of this as the core of the library where HTTP is implemented. It's designed to work well with readiness-based (select, poll, epoll) and completion-based event loops (iocp, io_uring).
To use TinyHTTP, be sure to enable the modules you are interested and tweak the configurations in from `tinyhttp.h`
```c
#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)
```
+--------+
| ROUTER |
+--------+--------+
| CLIENT | SERVER |
+--------+--------+
| ENGINE |
+-----------------+
| PARSER |
+-----------------+
```
then, drop the `tinyhttp.c` and `tinyhttp.h` in your project as they were your files.
At the lowest level there are HTTP request, HTTP respons, and URI parser. Then comes the HTTP "engine", which contains the HTTP 1.1 state machine. These two layers don't depend on basically anything. Probably only freestanding libc headers. The engine is designed in such a way that it does not perform I/O. Instead, applications feed it bytes from the network and eventually get a request or response object from it. When data needs to be output, the engine lets that know to the application. An HTTP engine represents the communication between one server and one client, so a non-blocking server would typically use an array of engines.
## Example
To give you the general idea, a simple blocking server using the engine would look somewhat like this:
```c
int main(void)
{
SOCKET listen_fd = start_server("127.0.0.1", 8080);
This is the code needed to create an HTTP/HTTPS server using the server API:
for (;;) {
SOCKET client_fd = accept(listen_fd, NULL, NULL);
int is_client = 0; // If this were true, the engine would behave as the client instead
HTTP_Engine eng;
http_engine_init(&eng, is_client, memfunc, NULL);
// Loop until the engine is in the CLOSED state
for (int closed = 0; !closed; ) {
// The engine may be in one of 4 states:
// RECV_BUF -> Bytes are expected from the network
// SEND_BUF -> Bytes need to be sent on the network
// PREP_STATUS -> A request is available
// CLOSED -> The connection was shut down at the HTTP level
switch (http_engine_state(&eng)) {
case HTTP_ENGINE_STATE_SERVER_CLOSED:
closed = 1;
break;
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
{
int cap;
char *dst = http_engine_recvbuf(&eng, &cap);
int ret = recv(client_fd, dst, cap, 0);
if (ret <= 0) {
http_engine_close(&eng);
break;
}
http_engine_recvack(&eng, ret);
}
break;
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
{
int len;
char *src = http_engine_sendbuf(&eng, &len);
int ret = send(client_fd, src, len, 0);
if (ret <= 0) {
http_engine_close(&eng);
break;
}
http_engine_sendack(&eng, ret);
}
break;
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
{
HTTP_Request *req = http_engine_getreq(&eng);
http_engine_status(&eng, 200);
http_engine_header(&eng, "Server: tinyhttp", 16);
http_engine_body(&eng, "Hello, world!", 13);
http_engine_done(&eng);
}
break;
}
}
http_engine_free(&eng);
close(client_fd);
}
close(listen_fd);
return 0;
}
```
This interface allows TinyHTTP to decouple the HTTP logic from the I/O. This has many advantages such as simplifying testing and decoupling TLS from the HTTP logic.
On top of the engine, TinyHTTP implements a fully functional and easy to use client and server. Both use `poll()` to handle non-blocking operations. The server API looks like this:
```c
#include <stdio.h>
#include <stdlib.h>
#include "tinyhttp.h"
static void *memfunc(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data)
{
(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;
}
#include "http.h"
int main(void)
{
TinyHTTPServerConfig config = {
int ret;
HTTP_Server server;
.reuse = 1,
.plain_addr = "127.0.0.1",
.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);
if (server == NULL)
return -1;
ret = http_server_init(&server, "127.0.0.1", 8080);
if (ret < 0) return -1;
for (;;) {
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_send(res);
HTTP_Request *req;
HTTP_ResponseHandle res;
ret = http_server_wait(&server, &req, &res, -1);
if (ret < 0)
return -1;
if (ret == 0)
continue;
http_response_status(res, 200);
http_response_header(res, "Server: tinyhttp");
http_response_body(res, "Hello, world!", 13);
http_response_done(res);
}
tinyhttp_server_free(server);
http_server_free(&server);
return 0;
}
```
And this is an example of a server using the stream API. It's more verbose but offers a high degree of control.
(NOTE: This code needs to be updated)
while the client interface looks like this (note that I omitted error checking for making easier to digest)
```c
void respond(TinyHTTPStream *stream)
#include <stdio.h>
#include "http.h"
int main(void)
{
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);
HTTP_Client client;
HTTP_TLSContext tls;
// Initialize the TLS stuff
http_tls_global_init();
http_tls_init(&tls);
// Initialize the client context
http_client_init(&clients[0]);
// Start the request
http_client_startreq(&clients[0], HTTP_METHOD_GET, "https://coz.is/hello.html", NULL, 0, NULL, 0, &tls);
// Wait for the request to complete
// (you could wait for more multiple request at once)
HTTP_Client *wait_list[] = { &client };
http_client_waitall(waitlist, 1, -1);
// Read the response
HTTP_Response *res;
http_client_result(&clients[0], &res);
fwrite(res->body.ptr, 1, res->body.len, stdout);
// Free the client context
http_client_free(&clients);
// Free the TLS stuff
http_tls_free(&tls);
http_tls_global_free();
return 0;
}
```
The client supports HTTPS by using OpenSSL, but the implementation is incomplete. You can get basic requests going but most things are not working yet. The server only supports plain text HTTP, but the interface is more mature. I think it's a great choice for small to medium websites (up to 500 concurrent users I'd say).
The last layer is the router, which sits on top of the HTTP server. This simplifies the work of serving files from disk or setting up routes with dynamic content in an easy and safe way. Here's an example:
```c
#include "http.h"
void endpoint_login(HTTP_Request *req, HTTP_ResponseHandle res, void *ctx)
{
http_response_status(res, 200);
http_response_body(res, "Hello!", -1);
http_response_done(res);
}
int main(void)
{
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
HTTP_Router *router = http_router_init();
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));
// Make /say_hello a dynamic resource
http_router_func(router, HTTP_METHOD_POST, HTTP_STR("/say_hello"), callback, NULL);
listen(listen_fd, 32);
// Requests to resources in the root folder are served from the examples folder
http_router_dir(router, HTTP_STR("/"), HTTP_STR("/examples"));
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;
return http_serve("127.0.0.1", 8080, router);
}
```
-232
View File
@@ -1,232 +0,0 @@
# 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 only dependency are freestanding libc headers.
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 available on the platform it's compiled for, but is single-threaded. The design goal of the server interface is ease of use and reasonable performance. It does also depend on libc.
## 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
-51
View File
@@ -1,51 +0,0 @@
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include "../tinyhttp.h"
sig_atomic_t should_exit = 0;
static void
signal_handler(int sig)
{
if (sig == SIGINT)
should_exit = 1;
}
int main(void)
{
signal(SIGINT, signal_handler);
TinyHTTPServerConfig config = {
.reuse = 1,
.plain_addr = "127.0.0.1",
.plain_port = 8080,
.plain_backlog = 32,
};
TinyHTTPServer *server = tinyhttp_server_init(config);
if (server == NULL)
return -1;
while (!should_exit) {
int ret;
TinyHTTPRequest *req;
TinyHTTPResponse res;
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(res, "Hello, world!", -1);
//tinyhttp_response_undo(res);
//tinyhttp_response_status(res, 500);
tinyhttp_response_send(res);
}
tinyhttp_server_free(server);
return 0;
}
-271
View File
@@ -1,271 +0,0 @@
#include <stdio.h>
#include <signal.h>
#include "../tinyhttp.h"
/////////////////////////////////////////////////////////////////
// CONFIGURATION
/////////////////////////////////////////////////////////////////
// Network stuff
#define ADDR "127.0.0.1"
#define PORT 8080
#define REUSE 1
#define BACKLOG 32
// Social stuff
#define MAX_USERS 32
#define MAX_POSTS 32
#define MAX_USERNAME 128
#define MAX_PASSWORD 256
#define MAX_BIO 512
#define MAX_CONTENT 512
/////////////////////////////////////////////////////////////////
// UTILITIES
/////////////////////////////////////////////////////////////////
typedef struct {
char *ptr;
ptrdiff_t len;
} string;
#define S(X) ((string) {(X), sizeof(X)-1})
static string trim(string s)
{
int i = 0;
while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t'))
i++;
if (i == s.len) {
s.ptr = NULL;
s.len = 0;
} else {
s.ptr += i;
s.len -= i;
while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\n')
s.len--;
}
return s;
}
static int copy(string src, string *dst, char *mem, int cap)
{
if (src.len > cap)
return -1;
memcpy(mem, src.ptr, src.len);
dst->ptr = mem;
dst->len = src.len;
return 0;
}
/////////////////////////////////////////////////////////////////
// BUSINESS LOGIC
/////////////////////////////////////////////////////////////////
typedef struct {
string name;
string pass;
string bio;
char name_mem[MAX_USERNAME];
char pass_mem[MAX_PASSWORD];
char bio_mem[MAX_BIO];
} User;
typedef struct {
int id;
string author;
string content;
char author_mem[MAX_USERNAME];
char content_mem[MAX_CONTENT];
} Post;
static User users[MAX_USERS];
static int user_count = 0;
static Post posts[MAX_POSTS];
static int posts_head = 0;
static int posts_count = 0;
static void init_users_and_posts(void)
{
// TODO
}
static int find_user(string name)
{
for (int i = 0; i < user_count; i++)
if (streq(name, users[i].name))
return i;
return -1;
}
static int create_user(string name, string pass)
{
name = trim(name);
pass = trim(pass);
if (name.len == 0 || pass.len == 0)
return -1;
if (find_user(name) != -1)
return -1;
if (user_count == MAX_USERS)
return -1;
User *user = &users[user_count];
if (copy(name, &user->name, user->name_mem, sizeof(user->name_mem)) < 0 &&
copy(pass, &user->pass, user->pass_mem, sizeof(user->pass_mem)) < 0)
return -1;
user_count++;
return 0;
}
static int delete_user(string name)
{
int i = find_user(name);
if (i == -1)
return -1;
// TODO
}
static int create_post(string author, string content)
{
// TODO
}
static int delete_post(int id)
{
// TODO
}
static int modify_post(int id, string new_content)
{
// TODO
}
/////////////////////////////////////////////////////////////////
// ENDPOINTS
/////////////////////////////////////////////////////////////////
#define MAX_SESSIONS 32
typedef struct {
int id;
string name;
char name_mem[MAX_USERNAME];
} Session;
static Session sessions[MAX_SESSIONS];
static int session_count = 0;
static void init_sessions(void)
{
for (int i = 0; i < MAX_SESSIONS; i++)
sessions[i].id = -1;
}
static int find_session(int id)
{
for (int i = 0; i < MAX_SESSIONS; i++)
if (sessions[i].id == id)
return i;
return -1;
}
static int create_session(string name)
{
// TODO
}
static int delete_session(string name)
{
// TODO
}
// TODO
/////////////////////////////////////////////////////////////////
// ENTRY POINT
/////////////////////////////////////////////////////////////////
static sig_atomic_t should_exit = 0;
static void
signal_handler(int sig)
{
if (sig == SIGINT)
should_exit = 1;
}
int main(void)
{
signal(SIGINT, signal_handler);
init_users_and_posts();
init_sessions();
TinyHTTPServerConfig config = {
.reuse = REUSE,
.plain_addr = ADDR,
.plain_port = PORT,
.plain_backlog = BACKLOG,
};
TinyHTTPServer *server = tinyhttp_server_init(config);
if (server == NULL)
return -1;
while (!should_exit) {
int ret;
TinyHTTPRequest *req;
TinyHTTPResponse res;
ret = tinyhttp_server_wait(server, &req, &res, 1000);
if (ret < 0) return -1; // Error
if (ret > 0) continue; // Timeout
char path_mem[1<<10];
TinyHTTPString path;
if (tinyhttp_normalizepath(req->path, &path, path_mem, sizeof(path_mem)) < 0) {
tinyhttp_response_status(res, 200);
tinyhttp_response_send(res);
continue;
}
TinyHTTPString sessid = tinyhttp_getcookie(req, TINYHTTP_STRING("sessid"));
if (tinyhttp_streq(path, TINYHTTP_STRING("/users"))) {
tinyhttp_response_status(res, 200);
tinyhttp_response_body(res, "<html><head><title>Users</title></head><body><ul>", -1);
for (int i = 0; i < user_count; i++)
tinyhttp_response_body(res, "<li><a href=\"/users/???\">???</a></li>", -1);
tinyhttp_response_body(res, "</ul></body></html>", -1);
tinyhttp_response_send(res);
continue;
}
if (tinyhttp_streq(path, TINYHTTP_STRING("/posts"))) {
tinyhttp_response_status(res, 200);
tinyhttp_response_body(res, "<html><head><title>Posts</title></head><body><ul>", -1);
for (int i = 0; i < user_count; i++)
tinyhttp_response_body(res, "<li><a href=\"/posts/???\">???</a></li>", -1);
tinyhttp_response_body(res, "</ul></body></html>", -1);
tinyhttp_response_send(res);
continue;
}
tinyhttp_response_status(res, 404);
tinyhttp_response_body(res, "<html><head><title>Not Found</title></head><body>Nothing here!</body></html>", -1);
tinyhttp_response_send(res);
}
tinyhttp_server_free(server);
return 0;
}
-67
View File
@@ -1,67 +0,0 @@
#include "../tinyhttp.h"
// TODO: Complete this example
int main(void)
{
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == INVALID_SOCKET)
return -1;
for (;;) {
SOCKET client_fd = accept(listen_fd, NULL, NULL);
if (client_fd == INVALID_SOCKET)
continue;
TinyHTTPStream stream;
tinyhttp_stream_init(&stream, memfunc, NULL);
int state = tinyhttp_stream_state(&stream);
while (!(state & TINYHTTP_STREAM_READY)) {
ASSERT(state & TINYHTTP_STREAM_RECV);
char *dst;
ptrdiff_t cap;
dst = tinyhttp_stream_recv_buf(&stream, &cap);
int ret = recv(client_fd, dst, cap, 0);
if (ret < 0) {
// TODO
}
tinyhttp_stream_recv_ack(&stream, ret);
state = tinyhttp_stream_state(&stream);
}
TinyHTTPRequest *request = tinyhttp_stream_request(&stream);
tinyhttp_stream_response_status(&stream, 200);
tinyhttp_stream_response_body(&stream, "Hello, world!", -1);
tinyhttp_stream_response_send(&stream);
while (state & TINYHTTP_STREAM_SEND) {
char *src;
ptrdiff_t len;
src = tinyhttp_stream_send_buf(&stream, &len);
int ret = send(client_fd, src, len, 0);
if (ret < 0) {
// TODO
}
tinyhttp_stream_send_ack(&stream, ret);
state = tinyhttp_stream_state(&stream);
}
tinyhttp_stream_free(&stream);
CLOSESOCKET(client_fd);
}
CLOSESOCKET(listen_fd);
return 0;
}
-107
View File
@@ -1,107 +0,0 @@
#include <assert.h>
#if defined(_WIN32)
#include <winsock2.h>
#define CLOSESOCKET closesocket
#elif defined(__linux__)
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#define CLOSESOCKET close
#else
#error "Only Windows and Linux are supported"
#endif
#include "../tinyhttp.h"
#define PORT 8080
#define ADDR "127.0.0.1"
// TODO: Complete this
void respond(TinyHTTPStream *stream)
{
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
if (req->method != TINYHTTP_METHOD_GET)
tinyhttp_stream_response_status(stream, 405);
else
tinyhttp_stream_response_status(stream, 200);
tinyhttp_stream_response_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 = inet_addr(ADDR);
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(listen_fd, &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(listen_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++;
}
}
}
CLOSESOCKET(listen_fd);
return 0;
}
+3620
View File
File diff suppressed because it is too large Load Diff
+405
View File
@@ -0,0 +1,405 @@
#ifndef HTTP_INCLUDED
#define HTTP_INCLUDED
#ifndef HTTP_PARSE
#define HTTP_PARSE 1
#endif
#ifndef HTTP_ENGINE
#define HTTP_ENGINE 1
#endif
#ifndef HTTP_CLIENT
#define HTTP_CLIENT 1
#endif
#ifndef HTTP_SERVER
#define HTTP_SERVER 1
#endif
#ifndef HTTP_ROUTER
#define HTTP_ROUTER 1
#endif
/////////////////////////////////////////////////////////////////////
// UTILITIES
/////////////////////////////////////////////////////////////////////
#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1})
#define HTTP_CEIL(X, Y) (((X) + (Y) - 1) / (Y))
typedef struct {
char *ptr;
long len;
} HTTP_String;
int http_streq (HTTP_String s1, HTTP_String s2);
int http_streqcase (HTTP_String s1, HTTP_String s2);
HTTP_String http_trim (HTTP_String s);
/////////////////////////////////////////////////////////////////////
// HTTP PARSER
/////////////////////////////////////////////////////////////////////
#if HTTP_PARSE
#define HTTP_MAX_HEADERS 32
typedef struct {
unsigned int data;
} HTTP_IPv4;
typedef struct {
unsigned short data[8];
} HTTP_IPv6;
typedef enum {
HTTP_HOST_MODE_VOID = 0,
HTTP_HOST_MODE_NAME,
HTTP_HOST_MODE_IPV4,
HTTP_HOST_MODE_IPV6,
} HTTP_HostMode;
typedef struct {
HTTP_HostMode mode;
HTTP_String text;
union {
HTTP_String name;
HTTP_IPv4 ipv4;
HTTP_IPv6 ipv6;
};
} HTTP_Host;
typedef struct {
HTTP_String userinfo;
HTTP_Host host;
int port;
} HTTP_Authority;
// ZII
typedef struct {
HTTP_String scheme;
HTTP_Authority authority;
HTTP_String path;
HTTP_String query;
HTTP_String fragment;
} HTTP_URL;
typedef enum {
HTTP_METHOD_GET,
HTTP_METHOD_HEAD,
HTTP_METHOD_POST,
HTTP_METHOD_PUT,
HTTP_METHOD_DELETE,
HTTP_METHOD_CONNECT,
HTTP_METHOD_OPTIONS,
HTTP_METHOD_TRACE,
HTTP_METHOD_PATCH,
} HTTP_Method;
typedef struct {
HTTP_String name;
HTTP_String value;
} HTTP_Header;
typedef struct {
HTTP_Method method;
HTTP_URL url;
int minor;
int num_headers;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Request;
typedef struct {
int minor;
int status;
HTTP_String reason;
int num_headers;
HTTP_Header headers[HTTP_MAX_HEADERS];
HTTP_String body;
} HTTP_Response;
int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4);
int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6);
int http_parse_url (char *src, int len, HTTP_URL *url);
int http_parse_request (char *src, int len, HTTP_Request *req);
int http_parse_response (char *src, int len, HTTP_Response *res);
HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name);
HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name);
#endif // HTTP_PARSE
/////////////////////////////////////////////////////////////////////
// HTTP BYTE QUEUE
/////////////////////////////////////////////////////////////////////
#if HTTP_ENGINE
typedef enum {
HTTP_MEMFUNC_MALLOC,
HTTP_MEMFUNC_FREE,
} HTTP_MemoryFuncTag;
typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag,
void *ptr, int len, void *data);
typedef struct {
HTTP_MemoryFunc memfunc;
void *memfuncdata;
unsigned long long curs;
char* data;
unsigned int head;
unsigned int size;
unsigned int used;
unsigned int limit;
char* read_target;
unsigned int read_target_size;
int flags;
} HTTP_ByteQueue;
typedef unsigned long long HTTP_ByteQueueOffset;
#endif // HTTP_ENGINE
/////////////////////////////////////////////////////////////////////
// HTTP ENGINE
/////////////////////////////////////////////////////////////////////
#if HTTP_ENGINE
#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0)
#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1)
#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2)
#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3)
#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4)
#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5)
#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6)
#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7)
#define HTTP_ENGINE_STATEBIT_PREP (1 << 8)
#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9)
#define HTTP_ENGINE_STATEBIT_PREP_BODY (1 << 10)
#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 11)
#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 12)
#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 13)
#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 14)
typedef enum {
HTTP_ENGINE_STATE_NONE = 0,
HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL,
HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
HTTP_ENGINE_STATE_CLIENT_PREP_BODY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY,
HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF,
HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK,
HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF,
HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK,
HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE,
HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED,
HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF,
HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK,
HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS,
HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
HTTP_ENGINE_STATE_SERVER_PREP_BODY = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY,
HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF,
HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK,
HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT,
} HTTP_EngineState;
typedef struct {
HTTP_EngineState state;
HTTP_ByteQueue input;
HTTP_ByteQueue output;
int numexch;
int reqsize;
int closing;
int keepalive;
HTTP_ByteQueueOffset response_offset;
HTTP_ByteQueueOffset content_length_offset;
HTTP_ByteQueueOffset content_length_value_offset;
union {
HTTP_Request req;
HTTP_Response res;
} result;
} HTTP_Engine;
void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata);
void http_engine_free (HTTP_Engine *eng);
void http_engine_close (HTTP_Engine *eng);
HTTP_EngineState http_engine_state (HTTP_Engine *eng);
char* http_engine_recvbuf (HTTP_Engine *eng, int *cap);
void http_engine_recvack (HTTP_Engine *eng, int num);
char* http_engine_sendbuf (HTTP_Engine *eng, int *len);
void http_engine_sendack (HTTP_Engine *eng, int num);
HTTP_Request* http_engine_getreq (HTTP_Engine *eng);
HTTP_Response* http_engine_getres (HTTP_Engine *eng);
void http_engine_url (HTTP_Engine *eng, HTTP_Method method, char *url, int minor);
void http_engine_status (HTTP_Engine *eng, int status);
void http_engine_header (HTTP_Engine *eng, const char *src, int len);
void http_engine_body (HTTP_Engine *eng, void *src, int len);
void http_engine_bodycap (HTTP_Engine *eng, int mincap);
char* http_engine_bodybuf (HTTP_Engine *eng, int *cap);
void http_engine_bodyack (HTTP_Engine *eng, int num);
void http_engine_done (HTTP_Engine *eng);
void http_engine_undo (HTTP_Engine *eng);
#endif // HTTP_ENGINE
/////////////////////////////////////////////////////////////////////
// HTTP CLIENT AND SERVER
/////////////////////////////////////////////////////////////////////
#if HTTP_CLIENT || HTTP_SERVER
typedef unsigned long long HTTP_Socket;
#endif // HTTP_CLIENT || HTTP_SERVER
/////////////////////////////////////////////////////////////////////
// HTTP CLIENT
/////////////////////////////////////////////////////////////////////
#if HTTP_CLIENT
#define HTTP_CLIENT_TLS 0
#define HTTP_CLIENT_WAIT_LIMIT (1<<9)
typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSContext;
typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSClientContext;
typedef enum {
HTTP_STATE_CLIENT_IDLE,
HTTP_STATE_CLIENT_CONNECT,
HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV,
HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND,
HTTP_STATE_CLIENT_RECV,
HTTP_STATE_CLIENT_SEND,
HTTP_STATE_CLIENT_CLOSED,
HTTP_STATE_CLIENT_READY,
} HTTP_ClientState;
enum {
HTTP_CLIENT_OK = 0,
HTTP_CLIENT_ERROR_INVURL = -1,
HTTP_CLIENT_ERROR_NOSYS = -2,
HTTP_CLIENT_ERROR_INVPROTO = -3,
HTTP_CLIENT_ERROR_FSOCK = -4,
HTTP_CLIENT_ERROR_FCONNECT = -5,
HTTP_CLIENT_ERROR_DNS = -6,
HTTP_CLIENT_ERROR_FSSLNEW = -7,
HTTP_CLIENT_ERROR_FRECV = -8,
HTTP_CLIENT_ERROR_FSSLREAD = -9,
HTTP_CLIENT_ERROR_FSEND = -10,
HTTP_CLIENT_ERROR_FSSLWRITE = -11,
HTTP_CLIENT_ERROR_FGETSOCKOPT = -12,
HTTP_CLIENT_ERROR_FSSLCONNECT = -13,
};
typedef struct {
int secure;
int code;
HTTP_Socket fd;
HTTP_Engine eng;
HTTP_ClientState state;
HTTP_TLSClientContext tls;
} HTTP_Client;
void http_tls_global_init(void);
void http_tls_global_free(void);
int http_tls_init(HTTP_TLSContext *tls);
void http_tls_free(HTTP_TLSContext *tls);
void http_client_init(HTTP_Client *client);
void http_client_free(HTTP_Client *client);
void http_client_startreq(
HTTP_Client *client, HTTP_Method method,
const char *url, HTTP_String *headers,
int num_headers, char *body, int body_len,
HTTP_TLSContext *tls);
int http_client_waitany(HTTP_Client **clients, int num_clients, int timeout);
int http_client_waitall(HTTP_Client **clients, int num_clients, int timeout);
int http_client_result(HTTP_Client *client, HTTP_Response **res);
const char *http_client_strerror(int code);
#endif // HTTP_CLIENT
/////////////////////////////////////////////////////////////////////
// HTTP SERVER
/////////////////////////////////////////////////////////////////////
#if HTTP_SERVER
#define HTTP_MAX_CLIENTS_PER_SERVER (1<<9)
typedef unsigned long long HTTP_BitsetWord;
typedef struct {
HTTP_BitsetWord data[HTTP_CEIL(HTTP_MAX_CLIENTS_PER_SERVER,
sizeof(HTTP_BitsetWord))];
} HTTP_Bitset;
typedef struct {
int head;
int count;
int items[HTTP_MAX_CLIENTS_PER_SERVER];
HTTP_Bitset set;
} HTTP_IntQueue;
typedef struct {
void *ptr;
int idx;
int gen;
} HTTP_ResponseHandle;
typedef struct {
HTTP_Socket fd;
HTTP_Engine eng;
unsigned short gen;
} HTTP_ServerConnection;
typedef struct {
HTTP_Socket listen_fd;
HTTP_IntQueue ready;
int num_conns;
HTTP_ServerConnection conns[HTTP_MAX_CLIENTS_PER_SERVER];
} HTTP_Server;
int http_server_init(HTTP_Server *server,
const char *addr, int port);
void http_server_free(HTTP_Server *server);
int http_server_wait(HTTP_Server *server, HTTP_Request **req,
HTTP_ResponseHandle *res, int timeout);
void http_response_status(HTTP_ResponseHandle res, int status);
void http_response_header(HTTP_ResponseHandle res, const char *fmt, ...);
void http_response_body(HTTP_ResponseHandle res, char *src, int len);
void http_response_done(HTTP_ResponseHandle res);
void http_response_undo(HTTP_ResponseHandle res);
#endif // HTTP_SERVER
/////////////////////////////////////////////////////////////////////
// HTTP ROUTER
/////////////////////////////////////////////////////////////////////
#if HTTP_ROUTER
typedef struct HTTP_Router HTTP_Router;
typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);;
HTTP_Router* http_router_init (void);
void http_router_free (HTTP_Router *router);
void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res);
void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path);
void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*);
int http_serve (const char *addr, int port, HTTP_Router *router);
#endif // HTTP_ROUTER
/////////////////////////////////////////////////////////////////////
// THE END
/////////////////////////////////////////////////////////////////////
#endif // HTTP_INCLUDED
-238
View File
@@ -1,238 +0,0 @@
#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);
}
-73
View File
@@ -1,73 +0,0 @@
table = [
(1<<6, "DIED"),
(1<<5, "RECV"),
(1<<4, "SEND"),
(1<<3, "RECV_STARTED"),
(1<<2, "SEND_STARTED"),
(1<<1, "READY"),
(1<<0, "CLOSE"),
]
outstates = [
"STATUS",
"HEADER",
"BODY",
]
i = 0
k = 0
while i < 2**len(table):
tags = []
for entry in table:
if i & entry[0]:
tags.append(entry[1])
statestr = "|".join(tags)
if ("DIED" in tags) and ("READY" in tags):
i += 1
continue
if ("READY" not in tags) and ("DIED" not in tags) and ("SEND" not in tags) and ("RECV" not in tags):
i += 1
continue
if ("READY" in tags) and ("CLOSE" in tags):
i += 1
continue
if ("CLOSE" in tags) and ("SEND" not in tags):
i += 1
continue
if (("CLOSE" in tags) or ("READY" in tags)) and (("RECV" in tags)):
i += 1
continue
if ("DIED" in tags) and (("RECV" in tags) or ("SEND" in tags)):
i += 1
continue
if ("SEND_STARTED" in tags) and ("SEND" not in tags) and ("DIED" not in tags):
i += 1
continue
if ("RECV_STARTED" in tags) and ("RECV" not in tags) and ("DIED" not in tags):
i += 1
continue
if "READY" in tags:
for outstate in outstates:
print(k, statestr + "|" + outstate)
k += 1
else:
print(k, statestr)
k += 1
i += 1
"""
Constraints:
- STATUS/HEADER/BODY only when READY
"""
-685
View File
@@ -1,685 +0,0 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <stddef.h>
#include <string.h>
#ifdef __SSE4_2__
#ifdef _MSC_VER
#include <nmmintrin.h>
#else
#include <x86intrin.h>
#endif
#endif
#include "picohttpparser.h"
#if __GNUC__ >= 3
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
#ifdef _MSC_VER
#define ALIGNED(n) _declspec(align(n))
#else
#define ALIGNED(n) __attribute__((aligned(n)))
#endif
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
#define CHECK_EOF() \
if (buf == buf_end) { \
*ret = -2; \
return NULL; \
}
#define EXPECT_CHAR_NO_CHECK(ch) \
if (*buf++ != ch) { \
*ret = -1; \
return NULL; \
}
#define EXPECT_CHAR(ch) \
CHECK_EOF(); \
EXPECT_CHAR_NO_CHECK(ch);
#define ADVANCE_TOKEN(tok, toklen) \
do { \
const char *tok_start = buf; \
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
int found2; \
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
if (!found2) { \
CHECK_EOF(); \
} \
while (1) { \
if (*buf == ' ') { \
break; \
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
*ret = -1; \
return NULL; \
} \
} \
++buf; \
CHECK_EOF(); \
} \
tok = tok_start; \
toklen = buf - tok_start; \
} while (0)
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
{
*found = 0;
#if __SSE4_2__
if (likely(buf_end - buf >= 16)) {
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
size_t left = (buf_end - buf) & ~15;
do {
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
if (unlikely(r != 16)) {
buf += r;
*found = 1;
break;
}
buf += 16;
left -= 16;
} while (likely(left != 0));
}
#else
/* suppress unused parameter warning */
(void)buf_end;
(void)ranges;
(void)ranges_size;
#endif
return buf;
}
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
{
const char *token_start = buf;
#ifdef __SSE4_2__
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
"\012\037" /* allow SP and up to but not including DEL */
"\177\177"; /* allow chars w. MSB set */
int found;
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
if (found)
goto FOUND_CTL;
#else
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
while (likely(buf_end - buf >= 8)) {
#define DOIT() \
do { \
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
goto NonPrintable; \
++buf; \
} while (0)
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
DOIT();
#undef DOIT
continue;
NonPrintable:
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
++buf;
}
#endif
for (;; ++buf) {
CHECK_EOF();
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
goto FOUND_CTL;
}
}
}
FOUND_CTL:
if (likely(*buf == '\015')) {
++buf;
EXPECT_CHAR('\012');
*token_len = buf - 2 - token_start;
} else if (*buf == '\012') {
*token_len = buf - token_start;
++buf;
} else {
*ret = -1;
return NULL;
}
*token = token_start;
return buf;
}
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
{
int ret_cnt = 0;
buf = last_len < 3 ? buf : buf + last_len - 3;
while (1) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
CHECK_EOF();
EXPECT_CHAR('\012');
++ret_cnt;
} else if (*buf == '\012') {
++buf;
++ret_cnt;
} else {
++buf;
ret_cnt = 0;
}
if (ret_cnt == 2) {
return buf;
}
}
*ret = -2;
return NULL;
}
#define PARSE_INT(valp_, mul_) \
if (*buf < '0' || '9' < *buf) { \
buf++; \
*ret = -1; \
return NULL; \
} \
*(valp_) = (mul_) * (*buf++ - '0');
#define PARSE_INT_3(valp_) \
do { \
int res_ = 0; \
PARSE_INT(&res_, 100) \
*valp_ = res_; \
PARSE_INT(&res_, 10) \
*valp_ += res_; \
PARSE_INT(&res_, 1) \
*valp_ += res_; \
} while (0)
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
int *ret)
{
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
"\"\"" /* 0x22 */
"()" /* 0x28,0x29 */
",," /* 0x2c */
"//" /* 0x2f */
":@" /* 0x3a-0x40 */
"[]" /* 0x5b-0x5d */
"{\xff"; /* 0x7b-0xff */
const char *buf_start = buf;
int found;
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
if (!found) {
CHECK_EOF();
}
while (1) {
if (*buf == next_char) {
break;
} else if (!token_char_map[(unsigned char)*buf]) {
*ret = -1;
return NULL;
}
++buf;
CHECK_EOF();
}
*token = buf_start;
*token_len = buf - buf_start;
return buf;
}
/* returned pointer is always within [buf, buf_end), or null */
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
{
/* we want at least [HTTP/1.<two chars>] to try to parse */
if (buf_end - buf < 9) {
*ret = -2;
return NULL;
}
EXPECT_CHAR_NO_CHECK('H');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('T');
EXPECT_CHAR_NO_CHECK('P');
EXPECT_CHAR_NO_CHECK('/');
EXPECT_CHAR_NO_CHECK('1');
EXPECT_CHAR_NO_CHECK('.');
PARSE_INT(minor_version, 1);
return buf;
}
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
for (;; ++*num_headers) {
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
break;
} else if (*buf == '\012') {
++buf;
break;
}
if (*num_headers == max_headers) {
*ret = -1;
return NULL;
}
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
/* parsing name, but do not discard SP before colon, see
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
return NULL;
}
if (headers[*num_headers].name_len == 0) {
*ret = -1;
return NULL;
}
++buf;
for (;; ++buf) {
CHECK_EOF();
if (!(*buf == ' ' || *buf == '\t')) {
break;
}
}
} else {
headers[*num_headers].name = NULL;
headers[*num_headers].name_len = 0;
}
const char *value;
size_t value_len;
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
return NULL;
}
/* remove trailing SPs and HTABs */
const char *value_end = value + value_len;
for (; value_end != value; --value_end) {
const char c = *(value_end - 1);
if (!(c == ' ' || c == '\t')) {
break;
}
}
headers[*num_headers].value = value;
headers[*num_headers].value_len = value_end - value;
}
return buf;
}
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
size_t max_headers, int *ret)
{
/* skip first empty line (some clients add CRLF after POST content) */
CHECK_EOF();
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
}
/* parse request line */
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
ADVANCE_TOKEN(*path, *path_len);
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
if (*method_len == 0 || *path_len == 0) {
*ret = -1;
return NULL;
}
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
if (*buf == '\015') {
++buf;
EXPECT_CHAR('\012');
} else if (*buf == '\012') {
++buf;
} else {
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf_start + len;
size_t max_headers = *num_headers;
int r;
*method = NULL;
*method_len = 0;
*path = NULL;
*path_len = 0;
*minor_version = -1;
*num_headers = 0;
/* if last_len != 0, check if the request is complete (a fast countermeasure
againt slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
&r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
{
/* parse "HTTP/1.x" */
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL;
}
/* skip space */
if (*buf != ' ') {
*ret = -1;
return NULL;
}
do {
++buf;
CHECK_EOF();
} while (*buf == ' ');
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
if (buf_end - buf < 4) {
*ret = -2;
return NULL;
}
PARSE_INT_3(status);
/* get message including preceding space */
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
return NULL;
}
if (*msg_len == 0) {
/* ok */
} else if (**msg == ' ') {
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
* before running past the end of the given buffer. */
do {
++*msg;
--*msg_len;
} while (**msg == ' ');
} else {
/* garbage found after status code */
*ret = -1;
return NULL;
}
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
}
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*minor_version = -1;
*status = 0;
*msg = NULL;
*msg_len = 0;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
{
const char *buf = buf_start, *buf_end = buf + len;
size_t max_headers = *num_headers;
int r;
*num_headers = 0;
/* if last_len != 0, check if the response is complete (a fast countermeasure
against slowloris */
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
return r;
}
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
return r;
}
return (int)(buf - buf_start);
}
enum {
CHUNKED_IN_CHUNK_SIZE,
CHUNKED_IN_CHUNK_EXT,
CHUNKED_IN_CHUNK_DATA,
CHUNKED_IN_CHUNK_CRLF,
CHUNKED_IN_TRAILERS_LINE_HEAD,
CHUNKED_IN_TRAILERS_LINE_MIDDLE
};
static int decode_hex(int ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
} else if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 0xa;
} else if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 0xa;
} else {
return -1;
}
}
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
{
size_t dst = 0, src = 0, bufsz = *_bufsz;
ssize_t ret = -2; /* incomplete */
decoder->_total_read += bufsz;
while (1) {
switch (decoder->_state) {
case CHUNKED_IN_CHUNK_SIZE:
for (;; ++src) {
int v;
if (src == bufsz)
goto Exit;
if ((v = decode_hex(buf[src])) == -1) {
if (decoder->_hex_count == 0) {
ret = -1;
goto Exit;
}
/* the only characters that may appear after the chunk size are BWS, semicolon, or CRLF */
switch (buf[src]) {
case ' ':
case '\011':
case ';':
case '\012':
case '\015':
break;
default:
ret = -1;
goto Exit;
}
break;
}
if (decoder->_hex_count == sizeof(size_t) * 2) {
ret = -1;
goto Exit;
}
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
++decoder->_hex_count;
}
decoder->_hex_count = 0;
decoder->_state = CHUNKED_IN_CHUNK_EXT;
/* fallthru */
case CHUNKED_IN_CHUNK_EXT:
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
if (decoder->bytes_left_in_chunk == 0) {
if (decoder->consume_trailer) {
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
} else {
goto Complete;
}
}
decoder->_state = CHUNKED_IN_CHUNK_DATA;
/* fallthru */
case CHUNKED_IN_CHUNK_DATA: {
size_t avail = bufsz - src;
if (avail < decoder->bytes_left_in_chunk) {
if (dst != src)
memmove(buf + dst, buf + src, avail);
src += avail;
dst += avail;
decoder->bytes_left_in_chunk -= avail;
goto Exit;
}
if (dst != src)
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
src += decoder->bytes_left_in_chunk;
dst += decoder->bytes_left_in_chunk;
decoder->bytes_left_in_chunk = 0;
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
}
/* fallthru */
case CHUNKED_IN_CHUNK_CRLF:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src] != '\012') {
ret = -1;
goto Exit;
}
++src;
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
break;
case CHUNKED_IN_TRAILERS_LINE_HEAD:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] != '\015')
break;
}
if (buf[src++] == '\012')
goto Complete;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
/* fallthru */
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
for (;; ++src) {
if (src == bufsz)
goto Exit;
if (buf[src] == '\012')
break;
}
++src;
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
break;
default:
assert(!"decoder is corrupt");
}
}
Complete:
ret = bufsz - src;
Exit:
if (dst != src)
memmove(buf + dst, buf + src, bufsz - src);
*_bufsz = dst;
/* if incomplete but the overhead of the chunked encoding is >=100KB and >80%, signal an error */
if (ret == -2) {
decoder->_total_overhead += bufsz - dst;
if (decoder->_total_overhead >= 100 * 1024 && decoder->_total_read - decoder->_total_overhead < decoder->_total_read / 4)
ret = -1;
}
return ret;
}
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
{
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
}
#undef CHECK_EOF
#undef EXPECT_CHAR
#undef ADVANCE_TOKEN
-90
View File
@@ -1,90 +0,0 @@
/*
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
* Shigeo Mitsunari
*
* The software is licensed under either the MIT License (below) or the Perl
* license.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef picohttpparser_h
#define picohttpparser_h
#include <stdint.h>
#include <sys/types.h>
#ifdef _MSC_VER
#define ssize_t intptr_t
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* contains name and value of a header (name == NULL if is a continuing line
* of a multiline header */
struct phr_header {
const char *name;
size_t name_len;
const char *value;
size_t value_len;
};
/* returns number of bytes consumed if successful, -2 if request is partial,
* -1 if failed */
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
struct phr_header *headers, size_t *num_headers, size_t last_len);
/* ditto */
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
/* should be zero-filled before start */
struct phr_chunked_decoder {
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
char consume_trailer; /* if trailing headers should be consumed */
char _hex_count;
char _state;
uint64_t _total_read;
uint64_t _total_overhead;
};
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
* encoding headers. When the function returns without an error, bufsz is
* updated to the length of the decoded data available. Applications should
* repeatedly call the function while it returns -2 (incomplete) every time
* supplying newly arrived data. If the end of the chunked-encoded data is
* found, the function returns a non-negative number indicating the number of
* octets left undecoded, that starts from the offset returned by `*bufsz`.
* Returns -1 on error.
*/
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
/* returns if the chunked decoder is in middle of chunked data */
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
#ifdef __cplusplus
}
#endif
#endif
+14 -430
View File
@@ -1,438 +1,22 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../http.h"
#include "test.h"
#include "picohttpparser.h"
#define TEST(X) {if (!(X)) __builtin_trap(); }
//////////////////////////////////////////////////////////////////////////////////////
// TEST CASES
//////////////////////////////////////////////////////////////////////////////////////
#define BASIC_REQUEST_STRING \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"User-Agent: curl/7.81.0\r\n" \
"Accept: */*\r\n" \
"\r\n"
static void test_init(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
int state = tinyhttp_stream_state(&stream);
// These flags must be set on init
TEST(state & TINYHTTP_STREAM_RECV);
// These must be unset
TEST(!(state & TINYHTTP_STREAM_DIED));
TEST(!(state & TINYHTTP_STREAM_READY));
TEST(!(state & TINYHTTP_STREAM_REUSE));
TEST(!(state & TINYHTTP_STREAM_RECV_STARTED));
TEST(!(state & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void test_kill(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED));
tinyhttp_stream_kill(&stream);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED);
tinyhttp_stream_free(&stream);
TEST_END
}
static void
test_recv_started_flag(void)
{
ptrdiff_t cap;
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
tinyhttp_stream_recv_buf(&stream, &cap);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED);
tinyhttp_stream_recv_ack(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void
test_send_started_flag(void)
{
ptrdiff_t cap;
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_send_buf(&stream, &cap);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED);
tinyhttp_stream_send_ack(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
tinyhttp_stream_free(&stream);
TEST_END
}
static void test_basic_exchange()
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
// Send request
send_request(&stream, BASIC_REQUEST_STRING);
// Build response
tinyhttp_stream_response_status(&stream, 200);
tinyhttp_stream_response_send(&stream);
// Receive response
char buf[1<<12];
Response res;
recv_response(&stream, &res, buf, sizeof(buf));
// We expect the status line:
// HTTP/1.1 200 OK
TEST(res.minor == 1);
TEST(res.status_code == 200);
TEST(tinyhttp_streq(res.status_text, TINYHTTP_STRING("OK")));
tinyhttp_stream_free(&stream);
TEST_END
}
//////////////////////////////////////////////////////////////////////////////////////
// ENTRY POINT
//////////////////////////////////////////////////////////////////////////////////////
void test_branch_coverage(void);
int main(void)
{
test_init();
test_kill();
test_recv_started_flag();
test_send_started_flag();
test_basic_exchange();
test_reuse();
test_chunking();
test_parse_request();
printf("OK\n");
char *tests[] = {
};
for (int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
HTTP_Request req;
int ret = http_parse_request(tests[i], strlen(tests[i]), &req);
TEST(ret == 0);
}
test_branch_coverage();
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// Helper Functions
//////////////////////////////////////////////////////////////////////////////////////
void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
static int
buffer_into_stream(TinyHTTPStream *stream, const char *src, int len)
{
int state = tinyhttp_stream_state(stream);
ptrdiff_t copied = 0;
while (copied < len && (state & TINYHTTP_STREAM_RECV)) {
char *dst;
ptrdiff_t cap;
ptrdiff_t cpy;
dst = tinyhttp_stream_recv_buf(stream, &cap);
cpy = len - copied;
if (cpy > cap) cpy = cap;
memcpy(dst, src + copied, cpy);
tinyhttp_printbytes(" >> ", src + copied, cpy);
copied += cpy;
tinyhttp_stream_recv_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
printf("\n");
return copied;
}
static int
stream_into_buffer(TinyHTTPStream *stream, char *dst, int cap)
{
int state = tinyhttp_stream_state(stream);
int copied = 0;
while (copied < cap && (state & TINYHTTP_STREAM_SEND)) {
char *src;
ptrdiff_t len;
ptrdiff_t cpy;
src = tinyhttp_stream_send_buf(stream, &len);
cpy = cap - copied;
if (cpy > len) cpy = len;
memcpy(dst + copied, src, cpy);
tinyhttp_printbytes(" << ", src, cpy);
copied += cpy;
tinyhttp_stream_send_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
printf("\n");
return copied;
}
int match_request(TinyHTTPRequest *r1, TinyHTTPRequest *r2)
{
if (r1->method != r2->method)
return 0;
if (r1->minor != r2->minor)
return 0;
if (!tinyhttp_streq(r1->path, r2->path))
return 0;
if (r1->num_headers != r2->num_headers)
return 0;
for (int i = 0; i < r2->num_headers; i++) {
if (!tinyhttp_streq(r1->headers[i].name, r2->headers[i].name))
return 0;
if (!tinyhttp_streq(r1->headers[i].value, r2->headers[i].value))
return 0;
}
if (r1->body_len != r2->body_len)
return 0;
if (r2->body_len == 0) {
if (r1->body != NULL)
return 0;
} else {
if (memcmp(r1->body, r2->body, r2->body_len))
return 0;
}
return 1;
}
static void
expect_request(TinyHTTPStream *stream, TinyHTTPRequest expreq)
{
int state = tinyhttp_stream_state(stream);
TEST(state & TINYHTTP_STREAM_READY);
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
TEST(req);
TEST(match_request(req, &expreq));
}
int parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max)
{
const char *method;
size_t method_len;
const char *path;
size_t path_len;
int minor;
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
size_t num_headers = TINYHTTP_HEADER_LIMIT;
int ret = phr_parse_request(
txt.ptr, txt.len,
&method, &method_len,
&path, &path_len,
&minor,
headers, &num_headers,
0);
ptrdiff_t head_len = ret;
if (method_len == 3 && !memcmp("GET", method, 3)) {
req->method = TINYHTTP_METHOD_GET;
} else if (method_len == 4 && !memcmp("POST", method, 4)) {
req->method = TINYHTTP_METHOD_POST;
} else {
return -1;
}
req->minor = minor;
req->path = (TinyHTTPString) { path, path_len };
req->num_headers = num_headers;
for (int i = 0; i < (int) num_headers; i++) {
req->headers[i].name = (TinyHTTPString) {
headers[i].name,
headers[i].name_len,
};
req->headers[i].value = (TinyHTTPString) {
headers[i].value,
headers[i].value_len,
};
}
int transfer_encoding_index = tinyhttp_findheader(req, TINYHTTP_STRING("Transfer-Encoding"));
if (transfer_encoding_index != -1) {
// TODO: For now, we consider request as chunked just for having the Transfer-Encoding header
if (txt.len - head_len > max)
return -1;
memcpy(buf, txt.ptr + head_len, txt.len - head_len);
struct phr_chunked_decoder decoder;
memset(&decoder, 0, sizeof(decoder));
decoder.consume_trailer = 1; // Process any trailing headers
// Decode the chunked body
size_t decoded_size = txt.len - head_len;
ssize_t ret = phr_decode_chunked(&decoder, buf, &decoded_size);
if (ret < 0)
return -1;
req->body = buf;
req->body_len = decoded_size;
return 0;
}
int content_length_index = tinyhttp_findheader(req, TINYHTTP_STRING("Content-Length"));
if (content_length_index != -1) {
__builtin_trap(); // TODO
return 0;
}
req->body = NULL;
req->body_len = 0;
return 0;
}
static void
parse_response(TinyHTTPString txt, Response *res)
{
int minor;
int status_code;
const char *status_text;
size_t status_text_len;
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
size_t num_headers = TINYHTTP_HEADER_LIMIT;
int ret = phr_parse_response(
txt.ptr, txt.len,
&minor,
&status_code, &status_text, &status_text_len,
headers, &num_headers,
0);
TEST(ret == txt.len);
res->minor = minor;
res->status_code = status_code;
res->status_text = (TinyHTTPString) { status_text, status_text_len };
res->num_headers = num_headers;
for (int i = 0; i < (int) num_headers; i++) {
res->headers[i].name = (TinyHTTPString) {
headers[i].name,
headers[i].name_len
};
res->headers[i].value = (TinyHTTPString) {
headers[i].value,
headers[i].value_len
};
}
res->body = NULL; // TODO
res->body_len = 0; // TODO
}
void send_request(TinyHTTPStream *stream, const char *str)
{
int received = buffer_into_stream(stream, str, strlen(str));
TEST(received == (int) strlen(str));
TinyHTTPRequest req;
char buf[1<<12];
TEST(parse_request((TinyHTTPString) {str, strlen(str)}, &req, buf, sizeof(buf)) == 0);
expect_request(stream, req);
}
void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap)
{
int len = stream_into_buffer(stream, dst, cap);
int state = tinyhttp_stream_state(stream);
TEST((state & TINYHTTP_STREAM_SEND) == 0);
parse_response((TinyHTTPString) { dst, len }, res);
}
int header_exists(Response *res, TinyHTTPString name)
{
for (int i = 0; i < res->num_headers; i++)
if (tinyhttp_streqcase(res->headers[i].name, name))
return 1;
return 0;
}
int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value)
{
for (int i = 0; i < res->num_headers; i++)
if (tinyhttp_streqcase(res->headers[i].name, name))
return tinyhttp_streqcase(res->headers[i].value, value);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// THE END
//////////////////////////////////////////////////////////////////////////////////////
-47
View File
@@ -1,47 +0,0 @@
#include <stdio.h>
#include "../tinyhttp.h"
typedef struct {
int minor;
int status_code;
TinyHTTPString status_text;
int num_headers;
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
char *body;
int body_len;
} Response;
// Memory function used to initialize TinyHTTPStream
// TODO: Maybe choose a better name for this
void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data);
// Moves the request "str" into the stream, checks that the stream
// became ready and that it parsed the request correctly. When this
// functions returns the stream is ready for a response.
void send_request(TinyHTTPStream *stream, const char *str);
// Copies into the "dst" buffer the output bytes from the stream
// (up to "cap" bytes) and parses them as an HTTP response into
// "res".
void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap);
int parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max);
int match_request(TinyHTTPRequest *r1, TinyHTTPRequest *r2);
int header_exists(Response *res, TinyHTTPString name);
int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value);
#define TEST(X) {if (!(X)) { printf("Test failed at %s:%d\n", __FILE__, __LINE__); fflush(stdout); __builtin_trap(); }}
#define TEST_START printf("Test %s:%d\n", __FILE__, __LINE__);
#define TEST_START2(file, line) printf("Test %s:%d\n", (file), (line));
#define TEST_END
void test_reuse(void);
void test_chunking(void);
void test_parse_request(void);
+518
View File
@@ -0,0 +1,518 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../http.h"
#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0]))
#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }}
#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__)
static void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
{
if (l != r) {
printf("Test failed at %s:%d\n", file, line);
printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n",
(int) uneval_l.len, uneval_l.ptr,
(int) uneval_r.len, uneval_r.ptr,
l, r);
abort();
}
}
static void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
{
if (!http_streq(l, r)) {
printf("Test failed at %s:%d\n", file, line);
printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n",
(int) uneval_l.len, uneval_l.ptr,
(int) uneval_r.len, uneval_r.ptr,
(int) l.len, l.ptr,
(int) r.len, r.ptr);
abort();
}
}
static void test_branch_coverage_parse_request(void)
{
struct {
int line;
int ret;
char *str;
} error_reqs[] = {
{ __LINE__, -1, "G * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "G@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "GE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "GE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "GET@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "POS * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "POS@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 19, "POST * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "POST@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PU * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PU@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 18, "PUT * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PUT@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "H * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "H@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HEA * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HEA@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 19, "HEAD * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HEAD@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "D * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "D@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DEL * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DEL@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DELE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DELE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DELET * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DELET@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 21, "DELETE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "DELETE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "C * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "C@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CO * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CO@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CON * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CON@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONN * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONN@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONNE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONNE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONNEC * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONNEC@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 22, "CONNECT * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "CONNECT@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "O * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "O@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPT * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPT@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTI * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTI@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTIO * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTIO@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTION * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTION@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 22, "OPTIONS * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "OPTIONS@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "T * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "T@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TR * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TR@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TRA * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TRA@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TRAC * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TRAC@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 20, "TRACE * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "TRACE@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PA * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PA@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PAT * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PAT@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PATC * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PATC@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, 20, "PATCH * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "PATCH@ * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "GET *@\r\n\r\n" },
{ __LINE__, -1, "GET * @\r\n\r\n" },
{ __LINE__, -1, "GET * H\r\n\r\n" },
{ __LINE__, -1, "GET * H@\r\n\r\n" },
{ __LINE__, -1, "GET * HT\r\n\r\n" },
{ __LINE__, -1, "GET * HT@\r\n\r\n" },
{ __LINE__, -1, "GET * HTT\r\n\r\n" },
{ __LINE__, -1, "GET * HTT@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/@\r\n\r\n" },
{ __LINE__, 16, "GET * HTTP/1\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.@\r\n\r\n" },
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1@\nname:\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r@name:\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\n@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\nn@\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\nname\r\n\r\n" },
{ __LINE__, 25, "GET * HTTP/1.1\r\nname:\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:\x1B\r\n\r\n" },
{ __LINE__, 30, "GET * HTTP/1.1\r\nname:value\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\nname :value\r\n\r\n" },
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:val\rue\r\n\r\n" },
{ __LINE__, 0, NULL },
};
for (int i = 0; error_reqs[i].str; i++) {
HTTP_Request req;
int ret = http_parse_request(error_reqs[i].str, strlen(error_reqs[i].str), &req);
if (ret != error_reqs[i].ret) {
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_reqs[i].line, ret, error_reqs[i].ret);
}
}
{
char str[] = "GET * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 18);
TEST(req.method == HTTP_METHOD_GET);
}
{
char str[] = "POST * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 19);
TEST(req.method == HTTP_METHOD_POST);
}
{
char str[] = "PUT * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 18);
TEST(req.method == HTTP_METHOD_PUT);
}
{
char str[] = "HEAD * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 19);
TEST(req.method == HTTP_METHOD_HEAD);
}
{
char str[] = "DELETE * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 21);
TEST(req.method == HTTP_METHOD_DELETE);
}
{
char str[] = "CONNECT * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 22);
TEST(req.method == HTTP_METHOD_CONNECT);
}
{
char str[] = "OPTIONS * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 22);
TEST(req.method == HTTP_METHOD_OPTIONS);
}
{
char str[] = "TRACE * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 20);
TEST(req.method == HTTP_METHOD_TRACE);
}
{
char str[] = "PATCH * HTTP/1.1\r\n\r\n";
HTTP_Request req;
int ret = http_parse_request(str, sizeof(str)-1, &req);
TEST(ret == 20);
TEST(req.method == HTTP_METHOD_PATCH);
}
}
static void test_branch_coverage_parse_response(void)
{
struct {
int line;
int ret;
char *str;
} error_ress[] = {
{ __LINE__, -1, "@\r\n\r\n" },
{ __LINE__, -1, "H\r\n\r\n" },
{ __LINE__, -1, "H@\r\n\r\n" },
{ __LINE__, -1, "HT\r\n\r\n" },
{ __LINE__, -1, "HT@\r\n\r\n" },
{ __LINE__, -1, "HTT\r\n\r\n" },
{ __LINE__, -1, "HTT@\r\n\r\n" },
{ __LINE__, -1, "HTTP\r\n\r\n" },
{ __LINE__, -1, "HTTP@\r\n\r\n" },
{ __LINE__, -1, "HTTP/\r\n\r\n" },
{ __LINE__, -1, "HTTP/@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1\r\n\r\n" },
{ __LINE__, -1, "HTTP/1@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 \r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 @\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 4\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 4@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 40\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 40@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404@\r\n\r\n" },
{ __LINE__, 17, "HTTP/1.1 404 \r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 \x1B\r\n\r\n" },
{ __LINE__, 26, "HTTP/1.1 404 Not Found\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\nname:\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\x1Bname:\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\n@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nn@\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname\r\n\r\n" },
{ __LINE__, 33, "HTTP/1.1 404 Not Found\r\nname:\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:\x1B\r\n\r\n" },
{ __LINE__, 38, "HTTP/1.1 404 Not Found\r\nname:value\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname :value\r\n\r\n" },
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:val\rue\r\n\r\n" },
{ __LINE__, 0, NULL },
};
for (int i = 0; error_ress[i].str; i++) {
HTTP_Response res;
int ret = http_parse_response(error_ress[i].str, strlen(error_ress[i].str), &res);
if (ret != error_ress[i].ret) {
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_ress[i].line, ret, error_ress[i].ret);
}
}
}
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#define S HTTP_STR
typedef struct {
char *buf;
int cap;
int num;
} Buffer;
static void appendc(Buffer *b, char c)
{
if (b->num < b->cap)
b->buf[b->num] = c;
b->num++;
}
static void appends(Buffer *b, HTTP_String s)
{
if (b->num < b->cap) {
int cpy = s.len;
if (cpy > b->cap - b->num)
cpy = b->cap - b->num;
for (int i = 0; i < cpy; i++)
b->buf[b->num + i] = s.ptr[i];
}
b->num += s.len;
}
static void appendi(Buffer *buf, unsigned int num)
{
char tmp[10];
tmp[0] = num / 1000000000; num %= 1000000000;
tmp[1] = num / 100000000; num %= 100000000;
tmp[2] = num / 10000000; num %= 10000000;
tmp[3] = num / 1000000; num %= 1000000;
tmp[4] = num / 100000; num %= 100000;
tmp[5] = num / 10000; num %= 10000;
tmp[6] = num / 1000; num %= 1000;
tmp[7] = num / 100; num %= 100;
tmp[8] = num / 10; num %= 10;
tmp[9] = num;
int leading_zeros = 0;
while (leading_zeros < 9 && tmp[leading_zeros] == 0)
leading_zeros++;
for (int i = leading_zeros; i < 10; i++)
tmp[i] += '0';
appends(buf, (HTTP_String) {
tmp + leading_zeros,
10 - leading_zeros
});
}
static int build_url(
HTTP_String scheme,
HTTP_String userinfo,
HTTP_String host,
HTTP_HostMode mode,
int port,
HTTP_String path,
HTTP_String query,
HTTP_String fragment,
char* dst,
int cap)
{
Buffer buf = {dst, cap, 0};
appends(&buf, scheme);
appendc(&buf, ':');
if (mode != HTTP_HOST_MODE_VOID) {
appendc(&buf, '/');
appendc(&buf, '/');
if (userinfo.len) {
appends(&buf, userinfo);
appendc(&buf, '@');
}
if (mode == HTTP_HOST_MODE_IPV6)
appendc(&buf, '[');
appends(&buf, host);
if (mode == HTTP_HOST_MODE_IPV6)
appendc(&buf, ']');
if (port == -2)
appendc(&buf, ':');
else if (port != -1) {
appendc(&buf, ':');
appendi(&buf, port);
}
}
appends(&buf, path);
appends(&buf, query);
appends(&buf, fragment);
return buf.num;
}
static void test_url(HTTP_String scheme, HTTP_String userinfo,
HTTP_String host, HTTP_HostMode mode, int port,
HTTP_String path, HTTP_String query, HTTP_String fragment)
{
char mem[1<<12];
int num = build_url(scheme, userinfo, host, mode, port, path, query, fragment, mem, sizeof(mem));
TEST(num < sizeof(mem));
printf("Testing %.*s\n", num, mem);
HTTP_URL url;
int ret = http_parse_url(mem, num, &url);
TEST_EQ(ret, num);
TEST_EQ(url.scheme, scheme);
TEST_EQ(url.authority.userinfo, userinfo);
if (port < 0)
TEST_EQ(url.authority.port, 0);
else
TEST_EQ(url.authority.port, port);
TEST_EQ((int) url.authority.host.mode, (int) mode);
if (mode == HTTP_HOST_MODE_IPV4) {
char tmp[1<<12];
TEST(sizeof(tmp) > host.len);
memcpy(tmp, host.ptr, host.len);
tmp[host.len] = '\0';
HTTP_IPv4 buf;
TEST_EQ(inet_pton(AF_INET, tmp, &buf), 1);
TEST_EQ((int) buf.data, (int) url.authority.host.ipv4.data);
} else if (mode == HTTP_HOST_MODE_IPV6) {
char tmp[1<<12];
TEST(sizeof(tmp) > host.len);
memcpy(tmp, host.ptr, host.len);
tmp[host.len] = '\0';
HTTP_IPv6 buf;
TEST_EQ(inet_pton(AF_INET6, tmp, &buf), 1);
TEST(!memcmp(&buf, &url.authority.host.ipv6, sizeof(HTTP_IPv6)));
} else if (mode == HTTP_HOST_MODE_NAME) {
TEST_EQ(host, url.authority.host.name);
}
TEST_EQ(url.path, path);
TEST_EQ(url.query, query);
TEST_EQ(url.fragment, fragment);
}
static void test_branch_coverage_parse_url(void)
{
HTTP_String scheme_values[] = { S("http") };
HTTP_String userinfo_values[] = { S(""), S("xxx:yyy") };
struct host_values {
HTTP_HostMode mode;
HTTP_String text;
} host_values[] = {
{ HTTP_HOST_MODE_VOID, S("") },
{ HTTP_HOST_MODE_IPV4, S("1.2.3.4") },
{ HTTP_HOST_MODE_IPV6, S("::") },
{ HTTP_HOST_MODE_IPV6, S("::1") },
{ HTTP_HOST_MODE_IPV6, S("1:2:3:4:A:B:C:D") },
{ HTTP_HOST_MODE_NAME, S("example.com") },
{ HTTP_HOST_MODE_NAME, S("1.2.3.256") },
};
int port_values[] = { -1, 1, 8080 };
HTTP_String path_values[] = { S(""), S("/"), S("/some/path.html") };
HTTP_String query_values[] = { S(""), S("?"), S("?param1=hello&param2=sup") };
HTTP_String fragment_values[] = { S(""), S("#"), S("#section0") };
for (int i = 0; i < COUNT(scheme_values); i++)
for (int j = 0; j < COUNT(userinfo_values); j++)
for (int k = 0; k < COUNT(host_values); k++)
for (int g = 0; g < COUNT(port_values); g++)
for (int t = 0; t < COUNT(path_values); t++)
for (int p = 0; p < COUNT(query_values); p++)
for (int q = 0; q < COUNT(fragment_values); q++) {
// Don't test URLs where the host isn't specified but the port or userinfo is
if (host_values[k].mode == HTTP_HOST_MODE_VOID && (port_values[g] > -1 || userinfo_values[j].len))
continue;
// If the authority is missing, the path must not be empty
if (host_values[k].mode == HTTP_HOST_MODE_VOID && path_values[t].len == 0)
continue;
test_url(
scheme_values[i],
userinfo_values[j],
host_values[k].text,
host_values[k].mode,
port_values[g],
path_values[t],
query_values[p],
fragment_values[q]
);
}
}
void test_branch_coverage(void)
{
test_branch_coverage_parse_request();
test_branch_coverage_parse_response();
test_branch_coverage_parse_url();
}
-44
View File
@@ -1,44 +0,0 @@
#include "test.h"
void test_chunking(void)
{
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
// Send request
send_request(&stream,
"POST / HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.81.0\r\n"
"Accept: */*\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"\r\n"
"d\r\n"
"Hello, world!\r\n"
"0\r\n"
"\r\n"
);
// Build response
tinyhttp_stream_response_status(&stream, 200);
tinyhttp_stream_response_send(&stream);
// Receive response
char buf[1<<12];
Response res;
recv_response(&stream, &res, buf, sizeof(buf));
// We expect the status line:
// HTTP/1.1 200 OK
TEST(res.minor == 1);
TEST(res.status_code == 200);
TEST(tinyhttp_streq(res.status_text, TINYHTTP_STRING("OK")));
tinyhttp_stream_free(&stream);
TEST_END
}
}
-40
View File
@@ -1,40 +0,0 @@
#include <string.h>
#include "test.h"
static void test_helper(char *src, int len, int expret, TinyHTTPRequest *expreq)
{
if (len < 0) len = strlen(src);
TinyHTTPRequest req;
int ret = tinyhttp_parserequest(src, len, -1ULL, &req);
TEST(ret == expret);
if (expret > 0) {
TEST(match_request(&req, expreq));
}
}
void test_parse_request(void)
{
{
char src[] =
"GET / HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"Connection: Keep-Alive\r\n"
"\r\n";
TinyHTTPRequest req;
req.method = TINYHTTP_METHOD_GET;
req.minor = 1;
req.path = TINYHTTP_STRING("/");
req.num_headers = 2;
req.headers[0].name = TINYHTTP_STRING("Host");
req.headers[0].value = TINYHTTP_STRING("127.0.0.1:8080");
req.headers[1].name = TINYHTTP_STRING("Connection");
req.headers[1].value = TINYHTTP_STRING("Keep-Alive");
req.body = NULL;
req.body_len = 0;
for (int i = 0; i < strlen(src)-1; i++)
test_helper(src, i, 0, &req);
test_helper(src, strlen(src), strlen(src), &req);
}
}
-185
View File
@@ -1,185 +0,0 @@
#include "test.h"
// This file tests the behavior of the "Connection" header
// HTTP/1.1, No "Connection" header
#define REQUEST_HTTP1_1_NO_CONNECTION_HEADER \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"\r\n"
// HTTP/1.1, "Connection" header with invalid value
#define REQUEST_HTTP1_1_CONNECTION_INVALID \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"Connection: zzz\r\n" \
"\r\n"
// HTTP/1.1, "Connection: Keep-Alive" header
#define REQUEST_HTTP1_1_CONNECTION_KEEPALIVE \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"Connection: Keep-Alive\r\n" \
"\r\n"
// HTTP/1.1, "Connection: Close" header
#define REQUEST_HTTP1_1_CONNECTION_CLOSE \
"GET / HTTP/1.1\r\n" \
"Host: 127.0.0.1:8080\r\n" \
"Connection: Close\r\n" \
"\r\n"
// HTTP/1.0, No "Connection" header
#define REQUEST_HTTP1_0_NO_CONNECTION_HEADER \
"GET / HTTP/1.0\r\n" \
"\r\n"
// HTTP/1.0, "Connection" header with invalid value
#define REQUEST_HTTP1_0_CONNECTION_INVALID \
"GET / HTTP/1.0\r\n" \
"Connection: zzz\r\n" \
"\r\n"
// HTTP/1.0, "Connection: Keep-Alive" header
#define REQUEST_HTTP1_0_CONNECTION_KEEPALIVE \
"GET / HTTP/1.0\r\n" \
"Connection: Keep-Alive\r\n" \
"\r\n"
// HTTP/1.0, "Connection: Close" header
#define REQUEST_HTTP1_0_CONNECTION_CLOSE \
"GET / HTTP/1.0\r\n" \
"Connection: Close\r\n" \
"\r\n"
static void test_setreuse(void)
{
TinyHTTPStream stream;
TEST_START
tinyhttp_stream_init(&stream, memfunc, NULL);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
tinyhttp_stream_setreuse(&stream, 1);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
tinyhttp_stream_setreuse(&stream, 0);
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
tinyhttp_stream_setreuse(&stream, 5847295);
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
tinyhttp_stream_free(&stream);
TEST_END
}
typedef enum {
DONT_REUSE,
ALLOW_REUSE,
} ServerReuse;
typedef enum {
INCONNHDR_NONE,
INCONNHDR_KEEPALIVE,
INCONNHDR_CLOSE,
INCONNHDR_INVALID,
} InputConnectionHeader;
typedef enum {
OUTCONNHDR_MISSING_OR_KEEPALIVE,
OUTCONNHDR_CLOSE,
} OutputConnectionHeader;
static void test_reuse_helper(
ServerReuse server_reuse,
InputConnectionHeader input_conn_header,
int input_http_minor_version,
OutputConnectionHeader expect_output_conn_header,
int expect_output_http_minor_version,
const char *file, int line)
{
TinyHTTPStream stream;
TEST_START2(file, line)
tinyhttp_stream_init(&stream, memfunc, NULL);
tinyhttp_stream_setreuse(&stream, server_reuse);
if (input_http_minor_version == 1) {
switch (input_conn_header) {
case INCONNHDR_NONE : send_request(&stream, REQUEST_HTTP1_1_NO_CONNECTION_HEADER); break;
case INCONNHDR_KEEPALIVE: send_request(&stream, REQUEST_HTTP1_1_CONNECTION_KEEPALIVE); break;
case INCONNHDR_CLOSE : send_request(&stream, REQUEST_HTTP1_1_CONNECTION_CLOSE); break;
case INCONNHDR_INVALID : send_request(&stream, REQUEST_HTTP1_1_CONNECTION_INVALID); break;
}
} else {
switch (input_conn_header) {
case INCONNHDR_NONE : send_request(&stream, REQUEST_HTTP1_0_NO_CONNECTION_HEADER); break;
case INCONNHDR_KEEPALIVE: send_request(&stream, REQUEST_HTTP1_0_CONNECTION_KEEPALIVE); break;
case INCONNHDR_CLOSE : send_request(&stream, REQUEST_HTTP1_0_CONNECTION_CLOSE); break;
case INCONNHDR_INVALID : send_request(&stream, REQUEST_HTTP1_0_CONNECTION_INVALID); break;
}
}
// Build a dummy response
tinyhttp_stream_response_status(&stream, 200);
tinyhttp_stream_response_send(&stream);
Response res;
char buf[1<<10];
recv_response(&stream, &res, buf, sizeof(buf));
TEST(res.minor == expect_output_http_minor_version);
int state = tinyhttp_stream_state(&stream);
switch (expect_output_conn_header) {
case OUTCONNHDR_MISSING_OR_KEEPALIVE:
{
TEST(!header_exists(&res, TINYHTTP_STRING("Connection"))
|| header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Keep-Alive")));
TEST((state & TINYHTTP_STREAM_DIED) == 0);
}
break;
case OUTCONNHDR_CLOSE:
{
TEST(header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Close")));
TEST(state & TINYHTTP_STREAM_DIED);
}
break;
}
tinyhttp_stream_free(&stream);
TEST_END
}
void test_reuse(void)
{
// Relevant specs:
// RFC 9112, Section 9.3. (Persistence)
// RFC 9112, Section 9.6. (Tear-down)
// RFC 9110, Section 7.6.1. (Connection)
test_setreuse();
test_reuse_helper(DONT_REUSE, INCONNHDR_NONE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_KEEPALIVE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_CLOSE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_INVALID, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_NONE, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_KEEPALIVE, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_CLOSE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_INVALID, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_NONE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_KEEPALIVE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_CLOSE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(DONT_REUSE, INCONNHDR_INVALID, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_NONE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_KEEPALIVE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_CLOSE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
test_reuse_helper(ALLOW_REUSE, INCONNHDR_INVALID, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
}
-2605
View File
File diff suppressed because it is too large Load Diff
-348
View File
@@ -1,348 +0,0 @@
////////////////////////////////////////////////////////////////////////////
// LICENSE //
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2025 Francesco Cozzuto
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////
#include <stddef.h>
#include <stdarg.h>
#ifdef TINYHTTP_CUSTOMCONFIG
#include "tinyhttp_config.h"
#else
#define TINYHTTP_SERVER_ENABLE 1
#define TINYHTTP_HTTPS_ENABLE 0
#define TINYHTTP_HEADER_LIMIT 32
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
#define TINYHTTP_CHUNK_LIMIT (1<<10)
#endif
#define TINYHTTP_LINESTR_HELPER1(X) #X
#define TINYHTTP_LINESTR_HELPER2(X) TINYHTTP_LINESTR_HELPER1(X)
#define TINYHTTP_LINESTR TINYHTTP_LINESTR_HELPER2(__LINE__)
// Opaque types
typedef struct TinyHTTPServer TinyHTTPServer;
typedef struct {
const char *ptr;
ptrdiff_t len;
} TinyHTTPString;
#define TINYHTTP_STRING(X) ((TinyHTTPString) {(X), sizeof(X)-1})
typedef enum {
TINYHTTP_METHOD_GET,
TINYHTTP_METHOD_POST,
} TinyHTTPMethod;
typedef struct {
TinyHTTPString name;
TinyHTTPString value;
} TinyHTTPHeader;
typedef struct {
TinyHTTPMethod method;
int minor;
TinyHTTPString path;
int num_headers;
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
char* body;
ptrdiff_t body_len;
} TinyHTTPRequest;
typedef struct {
TinyHTTPServer *server;
unsigned short idx;
unsigned short gen;
} TinyHTTPResponse;
typedef struct {
int reuse;
// HTTP
const char *plain_addr;
int plain_port;
int plain_backlog;
// HTTPS
int secure;
const char *secure_addr;
int secure_port;
int secure_backlog;
const char *cert_file;
const char *private_key_file;
} TinyHTTPServerConfig;
typedef enum {
TINYHTTP_MEM_MALLOC,
TINYHTTP_MEM_FREE,
} TinyHTTPMemoryFuncTag;
typedef void*(*TinyHTTPMemoryFunc)(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data);
// See [tinyhttp_stream_state]
enum {
TINYHTTP_STREAM_DIED = 1 << 0,
TINYHTTP_STREAM_READY = 1 << 1,
TINYHTTP_STREAM_RECV = 1 << 2,
TINYHTTP_STREAM_SEND = 1 << 3,
TINYHTTP_STREAM_CLOSE = 1 << 4,
TINYHTTP_STREAM_REUSE = 1 << 5,
TINYHTTP_STREAM_SEND_STARTED = 1 << 6,
TINYHTTP_STREAM_RECV_STARTED = 1 << 7,
};
// Internal use
typedef enum {
TINYHTTP_OUTPUT_STATE_NONE,
TINYHTTP_OUTPUT_STATE_STATUS,
TINYHTTP_OUTPUT_STATE_HEADER,
TINYHTTP_OUTPUT_STATE_BODY,
TINYHTTP_OUTPUT_STATE_ERROR,
} TinyHTTPConnectionOutputState;
// Internal use
enum {
BYTE_QUEUE_ERROR = 1 << 0,
BYTE_QUEUE_LOCK = 1 << 1,
BYTE_QUEUE_READ = 1 << 2,
BYTE_QUEUE_WRITE = 1 << 3,
};
// Internal use
typedef struct {
TinyHTTPMemoryFunc memfunc;
void *memfuncdata;
unsigned long long curs;
unsigned int lock; // TODO: Should this be u64?
char* data;
unsigned int head;
unsigned int size;
unsigned int used;
unsigned int limit;
char* read_target;
unsigned int read_target_size;
int flags;
} TinyHTTPByteQueue;
// Internal use
typedef unsigned long long TinyHTTPByteQueueOffset;
typedef struct {
int state;
int numexch;
int chunked;
int keepalive;
ptrdiff_t reqsize;
unsigned long long bodylimit;
TinyHTTPConnectionOutputState output_state;
TinyHTTPByteQueueOffset content_length_value_offset;
TinyHTTPByteQueueOffset content_length_offset;
TinyHTTPByteQueue in;
TinyHTTPByteQueue out;
TinyHTTPRequest req;
} TinyHTTPStream;
// TODO: Comment
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2);
// TODO: Comment
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2);
// TODO: Comment
void tinyhttp_printbytes(char *prefix, const char *src, int len);
// TODO: Comment
void tinyhttp_printstate_(int state, const char *file, const char *line);
// TODO: Comment
#define tinyhttp_printstate(state) tinyhttp_printstate_(state, __FILE__, TINYHTTP_LINESTR)
int tinyhttp_findheader(TinyHTTPRequest *req, TinyHTTPString name);
int tinyhttp_parserequest(char *src, ptrdiff_t len,
unsigned long long body_limit, TinyHTTPRequest *req);
// Initializes an HTTP stream
//
// TODO: Comment on memfunc
void
tinyhttp_stream_init(TinyHTTPStream *stream,
TinyHTTPMemoryFunc memfunc, void *memfuncdata);
// Deinitializes an HTTP stream
//
// This function may be called at any time on a memory
// region where [tinyhttp_stream_init] was called at
// least once.
//
// From the moment this function is called until the
// next call to [tinyhttp_stream_init], all [tinyhttp_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
//
// Note:
// - Calling this function is always valid on a
// memory region where [tinyhttp_stream_init] was
// called at least once.
int tinyhttp_stream_state(TinyHTTPStream *stream);
// Returns the pointer and capacity of a memory region
// where data should be read from the network.
//
// You can write at least [*cap] bytes at the returned
// pointer, and need to call [tinyhttp_stream_net_recv_ack]
// when the write is complete.
//
// Note:
// - Due to sticky errors, this function may return
// the null pointer and a capacity of 0. The caller
// must not write to the pointer but act as if
// everything went okay.
char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap);
// Acknowledge the bytes written from the network
//
// The write must have been initiated by calling
// [tinyhttp_stream_net_recv_buf].
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num);
// Returns the pointer and capacity of a memory region
// where data should be written to the network.
//
// When the write is complete, [tinyhttp_stream_net_send_ack]
// should be called with the number of consumed bytes.
char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len);
// See [tinyhttp_stream_net_send_buf]
void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num);
// Enable/disable connection reuse for this HTTP
// connection. This change takes effect at the start
// of the next request.
//
// Note that even if the reuse option is set, the
// connection may still be closed due to errors or
// the client deciding otherwise.
//
// On the other hand, if this option is set the next
// request will definitely be the last.
//
// NOTE: This is turned off by default
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value);
// TODO: Comment
void tinyhttp_stream_setbodylimit(TinyHTTPStream *stream, unsigned long long value);
// TODO: Comment
void tinyhttp_stream_setinbuflimit(TinyHTTPStream *stream, unsigned int value);
// TODO: Comment
void tinyhttp_stream_setoutbuflimit(TinyHTTPStream *stream, unsigned int value);
// TODO: Comment
TinyHTTPRequest *tinyhttp_stream_request(TinyHTTPStream *stream);
// TODO: Comment
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status);
// TODO: Comment
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
void tinyhttp_stream_response_body(TinyHTTPStream *stream, const char *src, int len);
// TODO: Comment
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
// TODO: Comment
char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap);
// TODO: Comment
void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num);
// TODO: Comment
void tinyhttp_stream_response_send(TinyHTTPStream *stream);
// TODO: Comment
void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
// TODO: Comment
TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config);
// 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_body(TinyHTTPResponse res, char *src, int len);
// TODO: Comment
void tinyhttp_response_send(TinyHTTPResponse res);
// TODO: Comment
void tinyhttp_response_undo(TinyHTTPResponse res);
View File