Improve testing program

This commit is contained in:
2025-04-24 22:00:30 +02:00
parent 579697f138
commit 82ef7ee170
9 changed files with 437 additions and 26 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ SUFFIX_DEBUG = _debug
# ------------------------------------------------------ #
TEST_CFILES = tinyhttp.c tests/picohttpparser.c tests/test.c tests/test_reuse.c tests/test_chunking.c
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
+2 -2
View File
@@ -4,9 +4,9 @@ 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 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 the platform it's compiled for, but is single-threaded. The design goal of the server interface is ease of use and reasonable performance.
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
+271
View File
@@ -0,0 +1,271 @@
#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;
}
+73
View File
@@ -0,0 +1,73 @@
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
"""
+38 -18
View File
@@ -144,6 +144,7 @@ int main(void)
test_basic_exchange();
test_reuse();
test_chunking();
test_parse_request();
printf("OK\n");
return 0;
}
@@ -227,6 +228,41 @@ stream_into_buffer(TinyHTTPStream *stream, char *dst, int cap)
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)
{
@@ -235,26 +271,10 @@ expect_request(TinyHTTPStream *stream, TinyHTTPRequest expreq)
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
TEST(req);
TEST(req->method == expreq.method);
TEST(req->minor == expreq.minor);
TEST(tinyhttp_streq(req->path, expreq.path));
TEST(req->num_headers == expreq.num_headers);
for (int i = 0; i < expreq.num_headers; i++) {
TEST(tinyhttp_streq(req->headers[i].name, expreq.headers[i].name));
TEST(tinyhttp_streq(req->headers[i].value, expreq.headers[i].value));
TEST(match_request(req, &expreq));
}
TEST(req->body_len == expreq.body_len);
if (expreq.body_len == 0) {
TEST(req->body == NULL);
} else {
TEST(!memcmp(req->body, expreq.body, expreq.body_len));
}
}
static int
parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max)
int parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max)
{
const char *method;
size_t method_len;
+5
View File
@@ -29,6 +29,10 @@ void send_request(TinyHTTPStream *stream, const char *str);
// "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);
@@ -40,3 +44,4 @@ int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString
void test_reuse(void);
void test_chunking(void);
void test_parse_request(void);
+40
View File
@@ -0,0 +1,40 @@
#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);
}
}
+3 -4
View File
@@ -471,8 +471,7 @@ parse_content_length(const char *src, ptrdiff_t len, unsigned long long *out)
return 0;
}
static int
parse_request(char *src, ptrdiff_t len,
int tinyhttp_parserequest(char *src, ptrdiff_t len,
unsigned long long body_limit, TinyHTTPRequest *req)
{
ptrdiff_t ret = parse_request_head(src, len, req);
@@ -1174,7 +1173,7 @@ char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap)
// Parse again. We assume everything will go
// well as it did the first time.
parse_request(src, len, stream->bodylimit, &stream->req);
tinyhttp_parserequest(src, len, stream->bodylimit, &stream->req);
}
// Forward the write region from the input buffer
@@ -1217,7 +1216,7 @@ process_next_request(TinyHTTPStream *stream)
ptrdiff_t len;
char *src = byte_queue_read_buf(&stream->in, &len); // TODO: What if this returns NULL?
int ret = parse_request(src, len, stream->bodylimit, &stream->req);
int ret = tinyhttp_parserequest(src, len, stream->bodylimit, &stream->req);
// Request is incomplete
if (ret == 0) {
+3
View File
@@ -190,6 +190,9 @@ void tinyhttp_printstate_(int state, const char *file, const char *line);
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