Work in progress HTTP server in C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4676 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
0b47207949
commit
1d214f89ed
218
src/http.c
Normal file
218
src/http.c
Normal file
@ -0,0 +1,218 @@
|
||||
#include "http.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
|
||||
#include "picohttpparser.h"
|
||||
#include "uv.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
typedef struct _tf_http_connection_t
|
||||
{
|
||||
tf_http_t* http;
|
||||
uv_tcp_t tcp;
|
||||
|
||||
char buffer[8192];
|
||||
size_t buffer_length;
|
||||
int parsed_length;
|
||||
|
||||
tf_http_callback_t* callback;
|
||||
void* user_data;
|
||||
} tf_http_connection_t;
|
||||
|
||||
typedef struct _tf_http_handler_t
|
||||
{
|
||||
const char* pattern;
|
||||
tf_http_callback_t* callback;
|
||||
void* user_data;
|
||||
} tf_http_handler_t;
|
||||
|
||||
typedef struct _tf_http_t
|
||||
{
|
||||
uv_tcp_t** listeners;
|
||||
int listeners_count;
|
||||
|
||||
tf_http_connection_t** connections;
|
||||
int connections_count;
|
||||
|
||||
tf_http_handler_t* handlers;
|
||||
int handlers_count;
|
||||
|
||||
uv_loop_t* loop;
|
||||
} tf_http_t;
|
||||
|
||||
tf_http_t* tf_http_create(uv_loop_t* loop)
|
||||
{
|
||||
tf_http_t* http = tf_malloc(sizeof(tf_http_t));
|
||||
*http = (tf_http_t)
|
||||
{
|
||||
.loop = loop,
|
||||
};
|
||||
return http;
|
||||
}
|
||||
|
||||
void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
|
||||
{
|
||||
tf_http_connection_t* connection = handle->data;
|
||||
*buf = uv_buf_init(connection->buffer + connection->buffer_length, sizeof(connection->buffer) - connection->buffer_length);
|
||||
}
|
||||
|
||||
bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, void** out_user_data)
|
||||
{
|
||||
for (int i = 0; i < http->handlers_count; i++)
|
||||
{
|
||||
if (!http->handlers[i].pattern)
|
||||
{
|
||||
*out_callback = http->handlers[i].callback;
|
||||
*out_user_data = http->handlers[i].user_data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer)
|
||||
{
|
||||
tf_http_connection_t* connection = stream->data;
|
||||
if (read_size > 0)
|
||||
{
|
||||
connection->buffer_length += read_size;
|
||||
|
||||
const char* method = NULL;
|
||||
size_t method_length = 0;
|
||||
const char* path = NULL;
|
||||
size_t path_length = 0;
|
||||
int minor_version = 0;
|
||||
struct phr_header headers[100];
|
||||
size_t header_count = sizeof(headers) / sizeof(*headers);
|
||||
|
||||
int parse_result = phr_parse_request(connection->buffer, connection->buffer_length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, connection->parsed_length);
|
||||
connection->parsed_length = connection->buffer_length;
|
||||
if (parse_result > 0)
|
||||
{
|
||||
if (_http_find_handler(connection->http, path, &connection->callback, &connection->user_data) && connection->callback)
|
||||
{
|
||||
char* method_copy = alloca(method_length + 1);
|
||||
memcpy(method_copy, method, method_length);
|
||||
method_copy[method_length] = '\0';
|
||||
|
||||
char* path_copy = alloca(path_length + 1);
|
||||
memcpy(path_copy, path, path_length);
|
||||
path_copy[path_length] = '\0';
|
||||
|
||||
tf_http_request_t request =
|
||||
{
|
||||
.connection = connection,
|
||||
.phase = k_http_callback_phase_headers_received,
|
||||
.method = method_copy,
|
||||
.path = path_copy,
|
||||
.headers = headers,
|
||||
.headers_count = (int)header_count,
|
||||
.user_data = connection->user_data,
|
||||
};
|
||||
connection->callback(&request);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("phr_parse_request: %d\n", parse_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
{
|
||||
tf_http_t* http = stream->data;
|
||||
tf_http_connection_t* connection = tf_malloc(sizeof(tf_http_connection_t));
|
||||
*connection = (tf_http_connection_t) { .http = http, .tcp = { .data = connection } };
|
||||
int r = uv_tcp_init(connection->http->loop, &connection->tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror(r));
|
||||
tf_free(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
r = uv_accept(stream, (uv_stream_t*)&connection->tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_accept: %s\n", uv_strerror(r));
|
||||
uv_close((uv_handle_t*)&connection->tcp, NULL);
|
||||
tf_free(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
r = uv_read_start((uv_stream_t*)&connection->tcp, _http_allocate_buffer, _http_on_read);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_read-start: %s\n", uv_strerror(r));
|
||||
uv_close((uv_handle_t*)&connection->tcp, NULL);
|
||||
tf_free(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
http->connections = tf_realloc(http->connections, http->connections_count + 1);
|
||||
http->connections[http->connections_count++] = connection;
|
||||
}
|
||||
|
||||
void tf_http_listen(tf_http_t* http, int port)
|
||||
{
|
||||
uv_tcp_t* tcp = tf_malloc(sizeof(uv_tcp_t));
|
||||
*tcp = (uv_tcp_t) { .data = http };
|
||||
int r = uv_tcp_init(http->loop, tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror(r));
|
||||
}
|
||||
|
||||
if (r == 0)
|
||||
{
|
||||
struct sockaddr_in addr =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr = { .s_addr = INADDR_ANY },
|
||||
.sin_port = ntohs(port),
|
||||
};
|
||||
r = uv_tcp_bind(tcp, (struct sockaddr*)&addr, 0);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_tcp_bind: %s\n", uv_strerror(r));
|
||||
}
|
||||
}
|
||||
|
||||
if (r == 0)
|
||||
{
|
||||
r = uv_listen((uv_stream_t*)tcp, 16, _http_on_connection);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_listen: %s\n", uv_strerror(r));
|
||||
}
|
||||
}
|
||||
|
||||
if (r == 0)
|
||||
{
|
||||
http->listeners = tf_realloc(http->listeners, sizeof(uv_tcp_t*) * (http->listeners_count + 1));
|
||||
http->listeners[http->listeners_count++] = tcp;
|
||||
}
|
||||
}
|
||||
|
||||
void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data)
|
||||
{
|
||||
http->handlers = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
|
||||
http->handlers[http->handlers_count++] = (tf_http_handler_t)
|
||||
{
|
||||
.pattern = tf_strdup(pattern),
|
||||
.callback = callback,
|
||||
.user_data = user_data,
|
||||
};
|
||||
}
|
||||
|
||||
void tf_http_destroy(tf_http_t* http)
|
||||
{
|
||||
tf_free(http);
|
||||
}
|
29
src/http.h
Normal file
29
src/http.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
typedef struct _tf_http_t tf_http_t;
|
||||
typedef struct _tf_http_connection_t tf_http_connection_t;
|
||||
|
||||
typedef enum _tf_http_callback_phase_t
|
||||
{
|
||||
k_http_callback_phase_headers_received,
|
||||
k_http_callback_phase_request_done,
|
||||
} tf_http_callback_phase_t;
|
||||
|
||||
typedef struct _tf_http_request_t
|
||||
{
|
||||
tf_http_callback_phase_t phase;
|
||||
tf_http_connection_t* connection;
|
||||
const char* method;
|
||||
const char* path;
|
||||
struct phr_header* headers;
|
||||
int headers_count;
|
||||
void* user_data;
|
||||
} tf_http_request_t;
|
||||
|
||||
typedef void (tf_http_callback_t)(tf_http_request_t* request);
|
||||
|
||||
tf_http_t* tf_http_create(uv_loop_t* loop);
|
||||
void tf_http_listen(tf_http_t* http, int port);
|
||||
void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data);
|
||||
void tf_http_destroy(tf_http_t* http);
|
36
src/tests.c
36
src/tests.c
@ -1,5 +1,6 @@
|
||||
#include "tests.h"
|
||||
|
||||
#include "http.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "ssb.tests.h"
|
||||
@ -11,6 +12,8 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIFEXITED(x) 1
|
||||
#define WEXITSTATUS(x) (x)
|
||||
@ -667,6 +670,38 @@ static void _test_b64(const tf_test_options_t* options)
|
||||
unlink("out/test.js");
|
||||
}
|
||||
|
||||
static void _test_http_thread(void* data)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
int r = system("curl -v http://localhost:23456/");
|
||||
tf_printf("curl returned %d\n", WEXITSTATUS(r));
|
||||
assert(WEXITSTATUS(r) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _test_http_handler(tf_http_request_t* request)
|
||||
{
|
||||
tf_printf("HANDLER %d\n", request->phase);
|
||||
}
|
||||
|
||||
static void _test_http(const tf_test_options_t* options)
|
||||
{
|
||||
uv_loop_t loop = { 0 };
|
||||
uv_loop_init(&loop);
|
||||
tf_http_t* http = tf_http_create(&loop);
|
||||
tf_http_add_handler(http, NULL, _test_http_handler, NULL);
|
||||
tf_http_listen(http, 23456);
|
||||
|
||||
uv_thread_t thread = { 0 };
|
||||
uv_thread_create(&thread, _test_http_thread, NULL);
|
||||
uv_run(&loop, UV_RUN_DEFAULT);
|
||||
|
||||
tf_http_destroy(http);
|
||||
uv_loop_close(&loop);
|
||||
|
||||
uv_thread_join(&thread);
|
||||
}
|
||||
|
||||
static void _tf_test_run(const tf_test_options_t* options, const char* name, void (*test)(const tf_test_options_t* options), bool opt_in)
|
||||
{
|
||||
bool specified = false;
|
||||
@ -706,6 +741,7 @@ static void _tf_test_run(const tf_test_options_t* options, const char* name, voi
|
||||
void tf_tests(const tf_test_options_t* options)
|
||||
{
|
||||
#if !TARGET_OS_IPHONE
|
||||
_tf_test_run(options, "http", _test_http, false);
|
||||
_tf_test_run(options, "ssb", tf_ssb_test_ssb, false);
|
||||
_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false);
|
||||
_tf_test_run(options, "ssb_following", tf_ssb_test_following, false);
|
||||
|
Loading…
Reference in New Issue
Block a user