Improve testing program
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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(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));
|
||||
}
|
||||
TEST(match_request(req, &expreq));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
+6
-1
@@ -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);
|
||||
|
||||
@@ -39,4 +43,5 @@ int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString
|
||||
#define TEST_END
|
||||
|
||||
void test_reuse(void);
|
||||
void test_chunking(void);
|
||||
void test_chunking(void);
|
||||
void test_parse_request(void);
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user