From a84f850e910c49b2214a8361c9ff8dfc7d61079f Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 3 Nov 2024 21:09:57 -0500 Subject: [PATCH] http: Bring back handler.js support, mostly. Partly in C, this time. --- core/core.js | 67 +++++++++++++++++++++++++++ src/httpd.js.c | 123 ++++++++++++++++++++++++++++++++++++------------- src/task.c | 1 - 3 files changed, 159 insertions(+), 32 deletions(-) diff --git a/core/core.js b/core/core.js index ce4b40a9..2dbf86dc 100644 --- a/core/core.js +++ b/core/core.js @@ -860,6 +860,73 @@ function sendStats() { } } +let g_handler_index = 0; + +exports.callAppHandler = async function callAppHandler( + response, + app_blob_id, + path, + query, + headers, + package_owner, + package_name +) { + let answer; + try { + let do_resolve; + let promise = new Promise(async function (resolve, reject) { + do_resolve = resolve; + }); + let process; + try { + process = await getProcessBlob( + app_blob_id, + 'handler_' + g_handler_index++, + { + script: 'handler.js', + imports: { + request: { + path: path, + query: query, + }, + respond: do_resolve, + }, + credentials: await httpd.auth_query(headers), + packageOwner: package_owner, + packageName: package_name, + } + ); + await process.ready; + answer = await promise; + } finally { + if (process?.task) { + await process.task.kill(); + } + } + } catch (error) { + let data = utf8Encode( + `Internal Server Error\n\n${error?.message}\n${error?.stack}` + ); + response.writeHead(500, { + 'Content-Type': 'text/plain; charset=utf-8', + 'Content-Length': data.length, + }); + response.end(data); + return; + } + if (typeof answer?.data == 'string') { + answer.data = utf8Encode(answer.data); + } + response.writeHead(answer?.status_code, { + 'Content-Type': answer?.content_type, + 'Content-Length': answer?.data?.length, + 'Access-Control-Allow-Origin': '*', + 'Content-Security-Policy': + 'sandbox allow-downloads allow-top-navigation-by-user-activation', + }); + response.end(answer?.data); +}; + /** * TODOC */ diff --git a/src/httpd.js.c b/src/httpd.js.c index 64592b21..4eb52592 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -224,6 +224,17 @@ static void _httpd_message_callback(tf_http_request_t* request, int op_code, con JS_FreeValue(context, on_message); } +static JSValue _httpd_make_response_object(JSContext* context, tf_http_request_t* request) +{ + JSValue response_object = JS_NewObjectClass(context, _httpd_request_class_id); + JS_SetOpaque(response_object, request); + JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); + JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); + JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2)); + JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2)); + return response_object; +} + static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket) { http_handler_data_t* data = request->user_data; @@ -250,14 +261,9 @@ static void _httpd_callback_internal(tf_http_request_t* request, bool is_websock JS_SetPropertyStr(context, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE); JS_SetPropertyStr(context, request_object, "client", client); - JSValue response_object = JS_NewObjectClass(context, _httpd_request_class_id); + JSValue response_object = _httpd_make_response_object(context, request); /* The ref is owned by the JS object and will be released by the finalizer. */ tf_http_request_ref(request); - JS_SetOpaque(response_object, request); - JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2)); - JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1)); - JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2)); - JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2)); JSValue args[] = { request_object, response_object, @@ -1024,8 +1030,12 @@ typedef struct _app_blob_t tf_http_request_t* request; bool found; bool not_modified; + bool use_handler; void* data; size_t size; + char app_blob_id[k_blob_id_len]; + const char* file; + user_app_t* user_app; char etag[256]; } app_blob_t; @@ -1033,8 +1043,6 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) { app_blob_t* data = user_data; tf_http_request_t* request = data->request; - char app_id[256] = ""; - const char* file = NULL; if (request->path[0] == '/' && request->path[1] == '~') { const char* last_slash = strchr(request->path + 1, '/'); @@ -1042,18 +1050,17 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) { last_slash = strchr(last_slash + 1, '/'); } - user_app_t* user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL; - if (user_app) + data->user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL; + if (data->user_app) { - size_t path_length = strlen("path:") + strlen(user_app->app) + 1; + size_t path_length = strlen("path:") + strlen(data->user_app->app) + 1; char* app_path = tf_malloc(path_length); - snprintf(app_path, path_length, "path:%s", user_app->app); - const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path); - snprintf(app_id, sizeof(app_id), "%s", value); + snprintf(app_path, path_length, "path:%s", data->user_app->app); + const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path); + snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%s", value); tf_free(app_path); tf_free((void*)value); - file = last_slash + 1; - tf_free(user_app); + data->file = last_slash + 1; } } else if (request->path[0] == '/' && request->path[1] == '&') @@ -1061,14 +1068,14 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) const char* end = strstr(request->path, ".sha256/"); if (end) { - snprintf(app_id, sizeof(app_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1); - file = end + strlen(".sha256/"); + snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1); + data->file = end + strlen(".sha256/"); } } char* app_blob = NULL; size_t app_blob_size = 0; - if (*app_id && tf_ssb_db_blob_get(ssb, app_id, (uint8_t**)&app_blob, &app_blob_size)) + if (*data->app_blob_id && tf_ssb_db_blob_get(ssb, data->app_blob_id, (uint8_t**)&app_blob, &app_blob_size)) { JSMallocFunctions funcs = { 0 }; tf_get_js_malloc_functions(&funcs); @@ -1077,22 +1084,33 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) JSValue app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL); JSValue files = JS_GetPropertyStr(context, app_object, "files"); - JSValue blob_id = JS_GetPropertyStr(context, files, file); - const char* blob_id_str = JS_ToCString(context, blob_id); - if (blob_id_str) + JSValue blob_id = JS_GetPropertyStr(context, files, data->file); + if (JS_IsUndefined(blob_id)) { - snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str); - const char* match = tf_http_request_get_header(data->request, "if-none-match"); - if (match && strcmp(match, data->etag) == 0) + blob_id = JS_GetPropertyStr(context, files, "handler.js"); + if (!JS_IsUndefined(blob_id)) { - data->not_modified = true; - } - else - { - data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size); + data->use_handler = true; } } - JS_FreeCString(context, blob_id_str); + else + { + const char* blob_id_str = JS_ToCString(context, blob_id); + if (blob_id_str) + { + snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str); + const char* match = tf_http_request_get_header(data->request, "if-none-match"); + if (match && strcmp(match, data->etag) == 0) + { + data->not_modified = true; + } + else + { + data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size); + } + } + JS_FreeCString(context, blob_id_str); + } JS_FreeValue(context, blob_id); JS_FreeValue(context, files); JS_FreeValue(context, app_object); @@ -1103,6 +1121,44 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data) } } +static void _httpd_call_app_handler(tf_ssb_t* ssb, tf_http_request_t* request, const char* app_blob_id, const char* path, const char* package_owner, const char* app) +{ + JSContext* context = tf_ssb_get_context(ssb); + JSValue global = JS_GetGlobalObject(context); + JSValue exports = JS_GetPropertyStr(context, global, "exports"); + JSValue call_app_handler = JS_GetPropertyStr(context, exports, "callAppHandler"); + + JSValue response = _httpd_make_response_object(context, request); + tf_http_request_ref(request); + JSValue handler_blob_id = JS_NewString(context, app_blob_id); + JSValue path_value = JS_NewString(context, path); + JSValue package_owner_value = JS_NewString(context, package_owner); + JSValue app_value = JS_NewString(context, app); + + JSValue args[] = { + response, + handler_blob_id, + path_value, + JS_UNDEFINED, + JS_UNDEFINED, + package_owner_value, + app_value, + }; + + JSValue result = JS_Call(context, call_app_handler, JS_NULL, tf_countof(args), args); + tf_util_report_error(context, result); + JS_FreeValue(context, result); + + JS_FreeValue(context, app_value); + JS_FreeValue(context, package_owner_value); + JS_FreeValue(context, handler_blob_id); + JS_FreeValue(context, path_value); + JS_FreeValue(context, response); + JS_FreeValue(context, call_app_handler); + JS_FreeValue(context, exports); + JS_FreeValue(context, global); +} + static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data) { app_blob_t* data = user_data; @@ -1110,6 +1166,10 @@ static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* { tf_http_respond(data->request, 304, NULL, 0, NULL, 0); } + else if (data->use_handler) + { + _httpd_call_app_handler(ssb, data->request, data->app_blob_id, data->file, data->user_app->user, data->user_app->app); + } else if (data->found) { const char* mime_type = _ext_to_content_type(strrchr(data->request->path, '.'), false); @@ -1129,6 +1189,7 @@ static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* }; tf_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size); } + tf_free(data->user_app); tf_free(data->data); tf_http_request_unref(data->request); tf_free(data); diff --git a/src/task.c b/src/task.c index 515b16a8..5eccbdfa 100644 --- a/src/task.c +++ b/src/task.c @@ -1869,7 +1869,6 @@ void tf_task_destroy(tf_task_t* task) { JSValue global = JS_GetGlobalObject(task->_context); JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED); - JS_SetPropertyStr(task->_context, global, "gProcesses", JS_NewObject(task->_context)); JS_FreeValue(task->_context, global); }