http: Bring back handler.js support, mostly. Partly in C, this time.

This commit is contained in:
Cory McWilliams 2024-11-03 21:09:57 -05:00
parent 5a765e6f07
commit a84f850e91
3 changed files with 159 additions and 32 deletions

View File

@ -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 * TODOC
*/ */

View File

@ -224,6 +224,17 @@ 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 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) static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket)
{ {
http_handler_data_t* data = request->user_data; 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, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE);
JS_SetPropertyStr(context, request_object, "client", client); 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. */ /* The ref is owned by the JS object and will be released by the finalizer. */
tf_http_request_ref(request); 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[] = { JSValue args[] = {
request_object, request_object,
response_object, response_object,
@ -1024,8 +1030,12 @@ typedef struct _app_blob_t
tf_http_request_t* request; tf_http_request_t* request;
bool found; bool found;
bool not_modified; bool not_modified;
bool use_handler;
void* data; void* data;
size_t size; size_t size;
char app_blob_id[k_blob_id_len];
const char* file;
user_app_t* user_app;
char etag[256]; char etag[256];
} app_blob_t; } 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; app_blob_t* data = user_data;
tf_http_request_t* request = data->request; tf_http_request_t* request = data->request;
char app_id[256] = "";
const char* file = NULL;
if (request->path[0] == '/' && request->path[1] == '~') if (request->path[0] == '/' && request->path[1] == '~')
{ {
const char* last_slash = strchr(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, '/'); last_slash = strchr(last_slash + 1, '/');
} }
user_app_t* user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL; data->user_app = last_slash ? _parse_user_app_from_path(request->path, last_slash) : NULL;
if (user_app) 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); char* app_path = tf_malloc(path_length);
snprintf(app_path, path_length, "path:%s", user_app->app); snprintf(app_path, path_length, "path:%s", data->user_app->app);
const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path); const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path);
snprintf(app_id, sizeof(app_id), "%s", value); snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%s", value);
tf_free(app_path); tf_free(app_path);
tf_free((void*)value); tf_free((void*)value);
file = last_slash + 1; data->file = last_slash + 1;
tf_free(user_app);
} }
} }
else if (request->path[0] == '/' && request->path[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/"); const char* end = strstr(request->path, ".sha256/");
if (end) if (end)
{ {
snprintf(app_id, sizeof(app_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1); snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1);
file = end + strlen(".sha256/"); data->file = end + strlen(".sha256/");
} }
} }
char* app_blob = NULL; char* app_blob = NULL;
size_t app_blob_size = 0; 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 }; JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs); 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 app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL);
JSValue files = JS_GetPropertyStr(context, app_object, "files"); JSValue files = JS_GetPropertyStr(context, app_object, "files");
JSValue blob_id = JS_GetPropertyStr(context, files, file); JSValue blob_id = JS_GetPropertyStr(context, files, data->file);
const char* blob_id_str = JS_ToCString(context, blob_id); if (JS_IsUndefined(blob_id))
if (blob_id_str)
{ {
snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str); blob_id = JS_GetPropertyStr(context, files, "handler.js");
const char* match = tf_http_request_get_header(data->request, "if-none-match"); if (!JS_IsUndefined(blob_id))
if (match && strcmp(match, data->etag) == 0)
{ {
data->not_modified = true; data->use_handler = 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); 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, blob_id);
JS_FreeValue(context, files); JS_FreeValue(context, files);
JS_FreeValue(context, app_object); 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) static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data)
{ {
app_blob_t* data = 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); 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) else if (data->found)
{ {
const char* mime_type = _ext_to_content_type(strrchr(data->request->path, '.'), false); 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_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size);
} }
tf_free(data->user_app);
tf_free(data->data); tf_free(data->data);
tf_http_request_unref(data->request); tf_http_request_unref(data->request);
tf_free(data); tf_free(data);

View File

@ -1869,7 +1869,6 @@ void tf_task_destroy(tf_task_t* task)
{ {
JSValue global = JS_GetGlobalObject(task->_context); JSValue global = JS_GetGlobalObject(task->_context);
JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED); JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED);
JS_SetPropertyStr(task->_context, global, "gProcesses", JS_NewObject(task->_context));
JS_FreeValue(task->_context, global); JS_FreeValue(task->_context, global);
} }