WebSocket request/response header dance. Feels like the loop is getting close to closed, but I want to refactor everything.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4692 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2023-12-24 17:43:33 +00:00
parent 196ab66e14
commit 1621f1753a
5 changed files with 90 additions and 13 deletions

View File

@ -342,7 +342,6 @@ function handleWebSocketRequest(request, response, client) {
function webSocketAcceptResponse(key) {
let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return base64Encode(sha1Digest(key + kMagic));
}

View File

@ -39,6 +39,7 @@ typedef struct _tf_http_connection_t
int headers_length;
bool headers_done;
int flags;
tf_http_callback_t* callback;
void* user_data;
@ -51,6 +52,7 @@ typedef struct _tf_http_connection_t
typedef struct _tf_http_handler_t
{
const char* pattern;
int flags;
tf_http_callback_t* callback;
void* user_data;
} tf_http_handler_t;
@ -70,6 +72,7 @@ typedef struct _tf_http_t
uv_loop_t* loop;
} tf_http_t;
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name);
static void _http_connection_destroy(tf_http_connection_t* connection);
static const char* _http_status_text(int status);
@ -96,13 +99,14 @@ void _http_allocate_buffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t*
}
}
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, int flags, tf_http_callback_t** out_callback, void** out_user_data)
{
for (int i = 0; i < http->handlers_count; i++)
{
if (!http->handlers[i].pattern ||
if (http->handlers[i].flags == flags &&
(!http->handlers[i].pattern ||
strcmp(path, http->handlers[i].pattern) == 0 ||
(strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/'))
(strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern)] == '/')))
{
*out_callback = http->handlers[i].callback;
*out_user_data = http->handlers[i].user_data;
@ -182,6 +186,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
.phase = k_http_callback_phase_headers_received,
.method = connection->method,
.path = connection->path,
.flags = connection->flags,
.query = connection->query,
.body = connection->body,
.content_length = connection->content_length,
@ -189,6 +194,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
.headers_count = connection->headers_length,
.user_data = connection->user_data,
};
tf_http_request_ref(request);
connection->callback(request);
tf_http_request_release(request);
@ -260,7 +266,9 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t
connection->body = tf_malloc(connection->content_length);
}
if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback)
int flags = _http_connection_get_header(connection, "upgrade") ? k_tf_http_handler_flag_websocket : 0;
connection->flags = flags;
if (!_http_find_handler(connection->http, connection->path, flags, &connection->callback, &connection->user_data) || !connection->callback)
{
connection->callback = _http_builtin_404_handler;
}
@ -365,12 +373,13 @@ 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, int flags, 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),
.flags = flags,
.callback = callback,
.user_data = user_data,
};
@ -451,7 +460,7 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header
char content_length_buffer[32] = { 0 };
int content_length_buffer_length = 0;
if (!sent_content_length)
if (!sent_content_length && status != 101)
{
content_length_buffer_length = snprintf(content_length_buffer, sizeof(content_length_buffer), "Content-Length: %zd\r\n", content_length);
headers_length += content_length_buffer_length;
@ -513,3 +522,20 @@ void tf_http_request_release(tf_http_request_t* request)
tf_free(request);
}
}
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name)
{
for (int i = 0; i < connection->headers_length; i++)
{
if (strcasecmp(connection->headers[i].name, name) == 0)
{
return connection->headers[i].value;
}
}
return NULL;
}
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name)
{
return _http_connection_get_header(request->connection, name);
}

View File

@ -18,6 +18,7 @@ typedef struct _tf_http_request_t
tf_http_connection_t* connection;
const char* method;
const char* path;
int flags;
const char* query;
void* body;
size_t content_length;
@ -27,14 +28,21 @@ typedef struct _tf_http_request_t
int ref_count;
} tf_http_request_t;
typedef enum _tf_http_handler_flags_t
{
k_tf_http_handler_flag_none = 0,
k_tf_http_handler_flag_websocket = 1,
} tf_http_handler_flags_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_add_handler(tf_http_t* http, const char* pattern, int flags, 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);
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_request_ref(tf_http_request_t* request);
void tf_http_request_release(tf_http_request_t* request);
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name);

View File

@ -9,6 +9,9 @@
#include "picohttpparser.h"
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
#include <alloca.h>
@ -99,6 +102,43 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in
static void _httpd_callback(tf_http_request_t* request)
{
if (request->flags & k_tf_http_handler_flag_websocket)
{
const char* header_connection = tf_http_request_get_header(request, "connection");
const char* header_upgrade = tf_http_request_get_header(request, "upgrade");
const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key");
tf_printf("\n%s\n%s\n%s\n\n", header_connection, header_upgrade, header_sec_websocket_key);
if (header_connection &&
header_upgrade &&
header_sec_websocket_key &&
strstr(header_connection, "Upgrade") &&
strcasecmp(header_upgrade, "websocket") == 0)
{
static const char* k_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
size_t key_length = strlen(header_sec_websocket_key);
size_t size = key_length + 36;
uint8_t* key_magic = alloca(size);
memcpy(key_magic, header_sec_websocket_key, key_length);
memcpy(key_magic + key_length, k_magic, 36);
uint8_t digest[20];
SHA1(key_magic, size, digest);
char key[41] = { 0 };
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
tf_printf("ACCEPT %s\n", key);
enum { k_headers_count = 4 };
const char* headers[k_headers_count * 2] =
{
"Upgrade", "websocket",
"Connection", "upgrade",
"Sec-WebSocket-Accept", key,
"Sec-WebSocket-Version", "13",
};
tf_http_respond(request, 101, headers, k_headers_count, NULL, 0);
return;
}
}
http_handler_data_t* data = request->user_data;
JSContext* context = data->context;
JSValue request_object = JS_NewObject(context);
@ -146,15 +186,19 @@ static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, J
const char* pattern = JS_ToCString(context, argv[0]);
http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t));
*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) };
tf_http_add_handler(http, pattern, _httpd_callback, data);
tf_http_add_handler(http, pattern, k_tf_http_handler_flag_none, _httpd_callback, data);
JS_FreeCString(context, pattern);
return JS_UNDEFINED;
}
static JSValue _httpd_register_socket_handler(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_printf("HTTPD_REGISTER_SOCKET_HANDLER UNIMPLEMENTED\n");
tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id);
const char* pattern = JS_ToCString(context, argv[0]);
http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t));
*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) };
tf_http_add_handler(http, pattern, k_tf_http_handler_flag_websocket, _httpd_callback, data);
JS_FreeCString(context, pattern);
return JS_UNDEFINED;
}

View File

@ -737,8 +737,8 @@ 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, "/hello", _test_http_handler, NULL);
tf_http_add_handler(http, "/post", _test_http_handler_post, NULL);
tf_http_add_handler(http, "/hello", k_tf_http_handler_flag_none, _test_http_handler, NULL);
tf_http_add_handler(http, "/post", k_tf_http_handler_flag_none, _test_http_handler_post, NULL);
tf_http_listen(http, 23456);
test_http_t test = { .loop = &loop };