forked from cory/tildefriends
Some quick http refactors to make websockets less magic.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4705 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
d556cbc835
commit
b6a3923b27
@ -954,6 +954,7 @@ function stringResponse(response, data) {
|
|||||||
loadSettings().then(function() {
|
loadSettings().then(function() {
|
||||||
let httpd_impl = (tildefriends.args.httpdc ? httpdc : httpd);
|
let httpd_impl = (tildefriends.args.httpdc ? httpdc : httpd);
|
||||||
httpd_impl.all("/login", auth.handler);
|
httpd_impl.all("/login", auth.handler);
|
||||||
|
httpd_impl.registerSocketHandler("/app/socket", app.socket);
|
||||||
httpd_impl.all("", function(request, response) {
|
httpd_impl.all("", function(request, response) {
|
||||||
let match;
|
let match;
|
||||||
if (request.uri === "/" || request.uri === "") {
|
if (request.uri === "/" || request.uri === "") {
|
||||||
@ -1004,7 +1005,6 @@ loadSettings().then(function() {
|
|||||||
return response.end(data);
|
return response.end(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
httpd_impl.registerSocketHandler("/app/socket", app.socket);
|
|
||||||
httpd_impl.start(tildefriends.http_port);
|
httpd_impl.start(tildefriends.http_port);
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
print('Failed to load settings.');
|
print('Failed to load settings.');
|
||||||
|
26
src/http.c
26
src/http.c
@ -40,7 +40,6 @@ typedef struct _tf_http_connection_t
|
|||||||
int headers_length;
|
int headers_length;
|
||||||
bool headers_done;
|
bool headers_done;
|
||||||
|
|
||||||
int flags;
|
|
||||||
tf_http_callback_t* callback;
|
tf_http_callback_t* callback;
|
||||||
tf_http_request_t* request;
|
tf_http_request_t* request;
|
||||||
void* user_data;
|
void* user_data;
|
||||||
@ -56,7 +55,6 @@ typedef struct _tf_http_connection_t
|
|||||||
typedef struct _tf_http_handler_t
|
typedef struct _tf_http_handler_t
|
||||||
{
|
{
|
||||||
const char* pattern;
|
const char* pattern;
|
||||||
int flags;
|
|
||||||
tf_http_callback_t* callback;
|
tf_http_callback_t* callback;
|
||||||
void* user_data;
|
void* user_data;
|
||||||
} tf_http_handler_t;
|
} tf_http_handler_t;
|
||||||
@ -103,14 +101,13 @@ 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, int flags, 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].flags == flags &&
|
if (!http->handlers[i].pattern ||
|
||||||
(!http->handlers[i].pattern ||
|
|
||||||
strcmp(path, http->handlers[i].pattern) == 0 ||
|
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_callback = http->handlers[i].callback;
|
||||||
*out_user_data = http->handlers[i].user_data;
|
*out_user_data = http->handlers[i].user_data;
|
||||||
@ -271,10 +268,6 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
|
|||||||
|
|
||||||
if (connection->body_length == connection->content_length)
|
if (connection->body_length == connection->content_length)
|
||||||
{
|
{
|
||||||
if (connection->flags & k_tf_http_handler_flag_websocket)
|
|
||||||
{
|
|
||||||
connection->is_websocket = true;
|
|
||||||
}
|
|
||||||
tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t));
|
tf_http_request_t* request = tf_malloc(sizeof(tf_http_request_t));
|
||||||
*request = (tf_http_request_t)
|
*request = (tf_http_request_t)
|
||||||
{
|
{
|
||||||
@ -282,7 +275,6 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
|
|||||||
.phase = k_http_callback_phase_headers_received,
|
.phase = k_http_callback_phase_headers_received,
|
||||||
.method = connection->method,
|
.method = connection->method,
|
||||||
.path = connection->path,
|
.path = connection->path,
|
||||||
.flags = connection->flags,
|
|
||||||
.query = connection->query,
|
.query = connection->query,
|
||||||
.body = connection->body,
|
.body = connection->body,
|
||||||
.content_length = connection->content_length,
|
.content_length = connection->content_length,
|
||||||
@ -367,9 +359,7 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t
|
|||||||
connection->body = tf_malloc(connection->content_length);
|
connection->body = tf_malloc(connection->content_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags = _http_connection_get_header(connection, "upgrade") ? k_tf_http_handler_flag_websocket : 0;
|
if (!_http_find_handler(connection->http, connection->path, &connection->callback, &connection->user_data) || !connection->callback)
|
||||||
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;
|
connection->callback = _http_builtin_404_handler;
|
||||||
}
|
}
|
||||||
@ -475,13 +465,12 @@ void tf_http_listen(tf_http_t* http, int port)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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 = tf_realloc(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
|
||||||
http->handlers[http->handlers_count++] = (tf_http_handler_t)
|
http->handlers[http->handlers_count++] = (tf_http_handler_t)
|
||||||
{
|
{
|
||||||
.pattern = tf_strdup(pattern),
|
.pattern = tf_strdup(pattern),
|
||||||
.flags = flags,
|
|
||||||
.callback = callback,
|
.callback = callback,
|
||||||
.user_data = user_data,
|
.user_data = user_data,
|
||||||
};
|
};
|
||||||
@ -664,3 +653,8 @@ const char* tf_http_request_get_header(tf_http_request_t* request, const char* n
|
|||||||
{
|
{
|
||||||
return _http_connection_get_header(request->connection, name);
|
return _http_connection_get_header(request->connection, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tf_http_request_websocket_upgrade(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
request->connection->is_websocket = true;
|
||||||
|
}
|
||||||
|
10
src/http.h
10
src/http.h
@ -21,7 +21,6 @@ typedef struct _tf_http_request_t
|
|||||||
tf_http_connection_t* connection;
|
tf_http_connection_t* connection;
|
||||||
const char* method;
|
const char* method;
|
||||||
const char* path;
|
const char* path;
|
||||||
int flags;
|
|
||||||
const char* query;
|
const char* query;
|
||||||
void* body;
|
void* body;
|
||||||
size_t content_length;
|
size_t content_length;
|
||||||
@ -33,17 +32,11 @@ typedef struct _tf_http_request_t
|
|||||||
int ref_count;
|
int ref_count;
|
||||||
} tf_http_request_t;
|
} 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);
|
typedef void (tf_http_callback_t)(tf_http_request_t* request);
|
||||||
|
|
||||||
tf_http_t* tf_http_create(uv_loop_t* loop);
|
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, int flags, 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);
|
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);
|
||||||
@ -52,3 +45,4 @@ void tf_http_request_ref(tf_http_request_t* request);
|
|||||||
void tf_http_request_release(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);
|
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name);
|
||||||
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
|
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
|
||||||
|
void tf_http_request_websocket_upgrade(tf_http_request_t* request);
|
||||||
|
@ -162,44 +162,8 @@ static void _httpd_message_callback(tf_http_request_t* request, int op_code, con
|
|||||||
JS_FreeValue(context, on_message);
|
JS_FreeValue(context, on_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _httpd_callback(tf_http_request_t* request)
|
static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket)
|
||||||
{
|
{
|
||||||
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");
|
|
||||||
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));
|
|
||||||
enum { k_headers_count = 4 };
|
|
||||||
const char* headers[k_headers_count * 2] =
|
|
||||||
{
|
|
||||||
"Upgrade", "websocket",
|
|
||||||
"Connection", "Upgrade",
|
|
||||||
"Sec-WebSocket-Accept", key,
|
|
||||||
"Sec-WebSocket-Version", "13",
|
|
||||||
};
|
|
||||||
bool send_version =
|
|
||||||
!tf_http_request_get_header(request, "sec-websocket-version") ||
|
|
||||||
strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0;
|
|
||||||
tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http_handler_data_t* data = request->user_data;
|
http_handler_data_t* data = request->user_data;
|
||||||
JSContext* context = data->context;
|
JSContext* context = data->context;
|
||||||
JSValue request_object = JS_NewObject(context);
|
JSValue request_object = JS_NewObject(context);
|
||||||
@ -238,12 +202,67 @@ static void _httpd_callback(tf_http_request_t* request)
|
|||||||
JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args);
|
JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args);
|
||||||
tf_util_report_error(context, response);
|
tf_util_report_error(context, response);
|
||||||
JS_FreeValue(context, request_object);
|
JS_FreeValue(context, request_object);
|
||||||
//JS_FreeValue(context, response_object);
|
|
||||||
JS_FreeValue(context, response);
|
JS_FreeValue(context, response);
|
||||||
|
|
||||||
|
if (is_websocket)
|
||||||
|
{
|
||||||
request->on_message = _httpd_message_callback;
|
request->on_message = _httpd_message_callback;
|
||||||
request->context = context;
|
request->context = context;
|
||||||
request->user_data = JS_VALUE_GET_PTR(response_object);
|
request->user_data = JS_VALUE_GET_PTR(response_object);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JS_FreeValue(context, response_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_callback(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
_httpd_callback_internal(request, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_websocket_callback(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
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));
|
||||||
|
enum { k_headers_count = 4 };
|
||||||
|
const char* headers[k_headers_count * 2] =
|
||||||
|
{
|
||||||
|
"Upgrade", "websocket",
|
||||||
|
"Connection", "Upgrade",
|
||||||
|
"Sec-WebSocket-Accept", key,
|
||||||
|
"Sec-WebSocket-Version", "13",
|
||||||
|
};
|
||||||
|
bool send_version =
|
||||||
|
!tf_http_request_get_header(request, "sec-websocket-version") ||
|
||||||
|
strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0;
|
||||||
|
tf_http_request_websocket_upgrade(request);
|
||||||
|
tf_http_respond(request, 101, headers, send_version ? k_headers_count : (k_headers_count - 1), NULL, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_http_respond(request, 400, NULL, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpd_callback_internal(request, true);
|
||||||
|
}
|
||||||
|
|
||||||
static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
@ -251,7 +270,7 @@ static JSValue _httpd_all(JSContext* context, JSValueConst this_val, int argc, J
|
|||||||
const char* pattern = JS_ToCString(context, argv[0]);
|
const char* pattern = JS_ToCString(context, argv[0]);
|
||||||
http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t));
|
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]) };
|
*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) };
|
||||||
tf_http_add_handler(http, pattern, k_tf_http_handler_flag_none, _httpd_callback, data);
|
tf_http_add_handler(http, pattern, _httpd_callback, data);
|
||||||
JS_FreeCString(context, pattern);
|
JS_FreeCString(context, pattern);
|
||||||
return JS_UNDEFINED;
|
return JS_UNDEFINED;
|
||||||
}
|
}
|
||||||
@ -262,7 +281,7 @@ static JSValue _httpd_register_socket_handler(JSContext* context, JSValueConst t
|
|||||||
const char* pattern = JS_ToCString(context, argv[0]);
|
const char* pattern = JS_ToCString(context, argv[0]);
|
||||||
http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t));
|
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]) };
|
*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);
|
tf_http_add_handler(http, pattern, _httpd_websocket_callback, data);
|
||||||
JS_FreeCString(context, pattern);
|
JS_FreeCString(context, pattern);
|
||||||
return JS_UNDEFINED;
|
return JS_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
@ -737,8 +737,8 @@ static void _test_http(const tf_test_options_t* options)
|
|||||||
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, "/hello", k_tf_http_handler_flag_none, _test_http_handler, NULL);
|
tf_http_add_handler(http, "/hello", _test_http_handler, NULL);
|
||||||
tf_http_add_handler(http, "/post", k_tf_http_handler_flag_none, _test_http_handler_post, 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