forked from cory/tildefriends
Now we're uploading some data.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4682 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
f4b6812675
commit
c2f62cd8e0
139
src/http.c
139
src/http.c
@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
#include "picohttpparser.h"
|
#include "picohttpparser.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
@ -18,12 +20,26 @@ typedef struct _tf_http_connection_t
|
|||||||
tf_http_t* http;
|
tf_http_t* http;
|
||||||
uv_tcp_t tcp;
|
uv_tcp_t tcp;
|
||||||
|
|
||||||
|
const char* method;
|
||||||
|
const char* path;
|
||||||
|
|
||||||
|
char headers_buffer[8192];
|
||||||
|
size_t headers_buffer_length;
|
||||||
|
int parsed_length;
|
||||||
|
|
||||||
char buffer[8192];
|
char buffer[8192];
|
||||||
size_t buffer_length;
|
size_t buffer_length;
|
||||||
int parsed_length;
|
|
||||||
|
struct phr_header headers[32];
|
||||||
|
int headers_length;
|
||||||
|
bool headers_done;
|
||||||
|
|
||||||
tf_http_callback_t* callback;
|
tf_http_callback_t* callback;
|
||||||
void* user_data;
|
void* user_data;
|
||||||
|
|
||||||
|
void* body;
|
||||||
|
size_t body_length;
|
||||||
|
size_t content_length;
|
||||||
} tf_http_connection_t;
|
} tf_http_connection_t;
|
||||||
|
|
||||||
typedef struct _tf_http_handler_t
|
typedef struct _tf_http_handler_t
|
||||||
@ -49,6 +65,7 @@ typedef struct _tf_http_t
|
|||||||
} tf_http_t;
|
} tf_http_t;
|
||||||
|
|
||||||
static void _http_connection_destroy(tf_http_connection_t* connection);
|
static void _http_connection_destroy(tf_http_connection_t* connection);
|
||||||
|
static const char* _http_status_text(int status);
|
||||||
|
|
||||||
tf_http_t* tf_http_create(uv_loop_t* loop)
|
tf_http_t* tf_http_create(uv_loop_t* loop)
|
||||||
{
|
{
|
||||||
@ -63,14 +80,21 @@ tf_http_t* tf_http_create(uv_loop_t* loop)
|
|||||||
void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
|
void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
|
||||||
{
|
{
|
||||||
tf_http_connection_t* connection = handle->data;
|
tf_http_connection_t* connection = handle->data;
|
||||||
|
if (!connection->headers_done)
|
||||||
|
{
|
||||||
|
*buf = uv_buf_init(connection->headers_buffer + connection->headers_buffer_length, sizeof(connection->headers_buffer) - connection->headers_buffer_length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
*buf = uv_buf_init(connection->buffer + connection->buffer_length, sizeof(connection->buffer) - connection->buffer_length);
|
*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)
|
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++)
|
for (int i = 0; i < http->handlers_count; i++)
|
||||||
{
|
{
|
||||||
if (!http->handlers[i].pattern)
|
if (!http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0)
|
||||||
{
|
{
|
||||||
*out_callback = http->handlers[i].callback;
|
*out_callback = http->handlers[i].callback;
|
||||||
*out_user_data = http->handlers[i].user_data;
|
*out_user_data = http->handlers[i].user_data;
|
||||||
@ -108,58 +132,105 @@ static void _http_connection_destroy(tf_http_connection_t* connection)
|
|||||||
http->connections[i] = http->connections[--http->connections_count];
|
http->connections[i] = http->connections[--http->connections_count];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (connection->body)
|
||||||
|
{
|
||||||
|
tf_free(connection->body);
|
||||||
|
connection->body = NULL;
|
||||||
|
}
|
||||||
tf_free(connection);
|
tf_free(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer)
|
static void _http_builtin_404_handler(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
const char* k_payload = _http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _http_add_body_bytes(tf_http_connection_t* connection, const void* data, size_t size)
|
||||||
|
{
|
||||||
|
size_t fit = tf_min(connection->content_length - connection->body_length, size);
|
||||||
|
if (fit > 0)
|
||||||
|
{
|
||||||
|
memcpy((char*)connection->body + connection->body_length, data, fit);
|
||||||
|
connection->body_length += fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection->body_length == connection->content_length)
|
||||||
|
{
|
||||||
|
tf_http_request_t request =
|
||||||
|
{
|
||||||
|
.connection = connection,
|
||||||
|
.phase = k_http_callback_phase_headers_received,
|
||||||
|
.method = connection->method,
|
||||||
|
.path = connection->path,
|
||||||
|
.headers = connection->headers,
|
||||||
|
.headers_count = connection->headers_length,
|
||||||
|
.user_data = connection->user_data,
|
||||||
|
};
|
||||||
|
connection->callback(&request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t* buffer)
|
||||||
{
|
{
|
||||||
tf_http_connection_t* connection = stream->data;
|
tf_http_connection_t* connection = stream->data;
|
||||||
if (read_size > 0)
|
if (read_size > 0)
|
||||||
{
|
{
|
||||||
connection->buffer_length += read_size;
|
if (!connection->headers_done)
|
||||||
|
{
|
||||||
|
connection->headers_buffer_length += read_size;
|
||||||
|
|
||||||
const char* method = NULL;
|
const char* method = NULL;
|
||||||
size_t method_length = 0;
|
size_t method_length = 0;
|
||||||
const char* path = NULL;
|
const char* path = NULL;
|
||||||
size_t path_length = 0;
|
size_t path_length = 0;
|
||||||
int minor_version = 0;
|
int minor_version = 0;
|
||||||
struct phr_header headers[100];
|
size_t header_count = sizeof(connection->headers) / sizeof(*connection->headers);
|
||||||
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);
|
int parse_result = phr_parse_request(connection->headers_buffer, connection->headers_buffer_length, &method, &method_length, &path, &path_length, &minor_version, connection->headers, &header_count, connection->parsed_length);
|
||||||
connection->parsed_length = connection->buffer_length;
|
connection->parsed_length = connection->headers_buffer_length;
|
||||||
if (parse_result > 0)
|
if (parse_result > 0)
|
||||||
{
|
{
|
||||||
if (_http_find_handler(connection->http, path, &connection->callback, &connection->user_data) && connection->callback)
|
connection->headers_done = true;
|
||||||
{
|
connection->method = method;
|
||||||
char* method_copy = alloca(method_length + 1);
|
((char*)connection->method)[method_length] = '\0';
|
||||||
memcpy(method_copy, method, method_length);
|
connection->path = path;
|
||||||
method_copy[method_length] = '\0';
|
((char*)connection->path)[path_length] = '\0';
|
||||||
|
|
||||||
char* path_copy = alloca(path_length + 1);
|
for (int i = 0; i < (int)header_count; i++)
|
||||||
memcpy(path_copy, path, path_length);
|
|
||||||
path_copy[path_length] = '\0';
|
|
||||||
|
|
||||||
tf_http_request_t request =
|
|
||||||
{
|
{
|
||||||
.connection = connection,
|
if (connection->headers[i].name_len == strlen("content-length") &&
|
||||||
.phase = k_http_callback_phase_headers_received,
|
strncasecmp(connection->headers[i].name, "content-length", connection->headers[i].name_len) == 0)
|
||||||
.method = method_copy,
|
{
|
||||||
.path = path_copy,
|
connection->content_length = strtoull(connection->headers[i].value, NULL, 10);
|
||||||
.headers = headers,
|
|
||||||
.headers_count = (int)header_count,
|
|
||||||
.user_data = connection->user_data,
|
|
||||||
};
|
|
||||||
connection->callback(&request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connection->content_length)
|
||||||
|
{
|
||||||
|
connection->body = tf_malloc(connection->content_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback)
|
||||||
|
{
|
||||||
|
connection->callback = _http_builtin_404_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
_http_add_body_bytes(connection, connection->headers_buffer + parse_result, connection->headers_buffer_length - parse_result);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tf_printf("phr_parse_request: %d\n", parse_result);
|
tf_printf("phr_parse_request: %d\n", parse_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
connection->buffer_length += read_size;
|
||||||
|
_http_add_body_bytes(connection, connection->buffer, connection->buffer_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_http_connection_destroy(connection);
|
_http_connection_destroy(connection);
|
||||||
}
|
}
|
||||||
@ -268,6 +339,14 @@ void tf_http_destroy(tf_http_t* http)
|
|||||||
http->listeners = NULL;
|
http->listeners = NULL;
|
||||||
http->listeners_count = 0;
|
http->listeners_count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < http->handlers_count; i++)
|
||||||
|
{
|
||||||
|
if (http->handlers[i].pattern)
|
||||||
|
{
|
||||||
|
tf_free((void*)http->handlers[i].pattern);
|
||||||
|
http->handlers[i].pattern = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
tf_free(http->handlers);
|
tf_free(http->handlers);
|
||||||
http->handlers_count = 0;
|
http->handlers_count = 0;
|
||||||
|
|
||||||
@ -347,3 +426,9 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header
|
|||||||
*shutdown_request = (uv_shutdown_t) { .data = request };
|
*shutdown_request = (uv_shutdown_t) { .data = request };
|
||||||
uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown);
|
uv_shutdown(shutdown_request, (uv_stream_t*)&request->connection->tcp, _http_on_shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data)
|
||||||
|
{
|
||||||
|
*out_data = request->connection->body;
|
||||||
|
return request->connection->content_length;
|
||||||
|
}
|
||||||
|
@ -29,4 +29,5 @@ tf_http_t* tf_http_create(uv_loop_t* loop);
|
|||||||
void tf_http_listen(tf_http_t* http, int port);
|
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_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, void* user_data);
|
||||||
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length);
|
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length);
|
||||||
|
size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data);
|
||||||
void tf_http_destroy(tf_http_t* http);
|
void tf_http_destroy(tf_http_t* http);
|
||||||
|
27
src/tests.c
27
src/tests.c
@ -685,7 +685,15 @@ static void _test_http_thread(void* data)
|
|||||||
{
|
{
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
test_http_t* test = data;
|
test_http_t* test = data;
|
||||||
int r = system("curl -v http://localhost:23456/");
|
int r = system("curl -v http://localhost:23456/404");
|
||||||
|
assert(WEXITSTATUS(r) == 0);
|
||||||
|
tf_printf("curl returned %d\n", WEXITSTATUS(r));
|
||||||
|
|
||||||
|
r = system("curl -v http://localhost:23456/hello");
|
||||||
|
assert(WEXITSTATUS(r) == 0);
|
||||||
|
tf_printf("curl returned %d\n", WEXITSTATUS(r));
|
||||||
|
|
||||||
|
r = system("curl -v --data 'hello world' http://localhost:23456/post");
|
||||||
assert(WEXITSTATUS(r) == 0);
|
assert(WEXITSTATUS(r) == 0);
|
||||||
tf_printf("curl returned %d\n", WEXITSTATUS(r));
|
tf_printf("curl returned %d\n", WEXITSTATUS(r));
|
||||||
|
|
||||||
@ -706,12 +714,27 @@ static void _test_http_handler(tf_http_request_t* request)
|
|||||||
tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload));
|
tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _test_http_handler_post(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
const void* body = NULL;
|
||||||
|
size_t size = tf_http_get_body(request, &body);
|
||||||
|
tf_printf("size = %zd body=%.*s\n", size, (int)size, (const char*)body);
|
||||||
|
const char* headers[] =
|
||||||
|
{
|
||||||
|
"Connection", "close",
|
||||||
|
};
|
||||||
|
const char* k_payload = "Hello, world!\n";
|
||||||
|
tf_http_respond(request, 200, headers, 1, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
|
||||||
static void _test_http(const tf_test_options_t* options)
|
static void _test_http(const tf_test_options_t* options)
|
||||||
{
|
{
|
||||||
|
tf_printf("Starting http.\n");
|
||||||
uv_loop_t loop = { 0 };
|
uv_loop_t loop = { 0 };
|
||||||
uv_loop_init(&loop);
|
uv_loop_init(&loop);
|
||||||
tf_http_t* http = tf_http_create(&loop);
|
tf_http_t* http = tf_http_create(&loop);
|
||||||
tf_http_add_handler(http, NULL, _test_http_handler, NULL);
|
tf_http_add_handler(http, "/hello", _test_http_handler, NULL);
|
||||||
|
tf_http_add_handler(http, "/post", _test_http_handler_post, NULL);
|
||||||
tf_http_listen(http, 23456);
|
tf_http_listen(http, 23456);
|
||||||
|
|
||||||
test_http_t test = { .loop = &loop };
|
test_http_t test = { .loop = &loop };
|
||||||
|
Loading…
Reference in New Issue
Block a user