Serve core static files without leaving C.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4833 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2024-02-09 01:21:57 +00:00
parent e87acc6286
commit ed2d57fb4b
8 changed files with 323 additions and 48 deletions

View File

@ -11,7 +11,7 @@
<tf-auth id="auth"></tf-auth> <tf-auth id="auth"></tf-auth>
<script>window.litDisableBundleWarning = true;</script> <script>window.litDisableBundleWarning = true;</script>
<script type="module"> <script type="module">
import {LitElement, html} from '/static/lit/lit-all.min.js'; import {LitElement, html} from '/lit/lit-all.min.js';
let g_data = $AUTH_DATA; let g_data = $AUTH_DATA;
let app = document.getElementById('auth'); let app = document.getElementById('auth');
Object.assign(app, g_data); Object.assign(app, g_data);

View File

@ -1,4 +1,4 @@
import {LitElement, html, css, svg} from '/static/lit/lit-all.min.js'; import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
let cm6; let cm6;
let gSocket; let gSocket;

View File

@ -35,12 +35,6 @@ const k_magic_bytes = [
let k_static_files = [ let k_static_files = [
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
{uri: '/client.js', type: 'text/javascript; charset=UTF-8'},
{uri: '/favicon.png', type: 'image/png'},
{uri: '/jszip.min.js', type: 'text/javascript; charset=UTF-8'},
{uri: '/style.css', type: 'text/css; charset=UTF-8'},
{uri: '/tfrpc.js', type: 'text/javascript; charset=UTF-8', headers: {'Access-Control-Allow-Origin': 'null'}},
{uri: '/w3.css', type: 'text/css; charset=UTF-8'},
]; ];
const k_global_settings = { const k_global_settings = {
@ -562,37 +556,6 @@ function startsWithBytes(data, bytes) {
} }
} }
async function staticFileHandler(request, response, blobId, uri) {
for (let i in k_static_files) {
if (uri === k_static_files[i].uri) {
let path = k_static_files[i].path || uri.substring(1);
let type = k_static_files[i].type || guessTypeFromName(path);
let stat = await File.stat('core/' + path);
let id = `${stat.mtime}_${stat.size}`;
if (request.headers['if-none-match'] === '"' + id + '"') {
response.writeHead(304, {'Content-Length': '0'});
response.end();
} else {
let data = await File.readFile('core/' + path);
response.writeHead(200, Object.assign(
{
'Content-Type': type,
'Content-Length': data.byteLength,
'etag': '"' + id + '"',
},
k_static_files[i].headers || {}));
response.end(data);
}
return;
}
}
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
response.end("File not found");
}
async function staticDirectoryHandler(request, response, directory, uri) { async function staticDirectoryHandler(request, response, directory, uri) {
let filename = uri || 'index.html'; let filename = uri || 'index.html';
if (filename.indexOf('..') != -1) { if (filename.indexOf('..') != -1) {
@ -983,6 +946,7 @@ loadSettings().then(function() {
httpd.set_http_redirect(gGlobalSettings.http_redirect); httpd.set_http_redirect(gGlobalSettings.http_redirect);
} }
httpd.all("/login", auth.handler); httpd.all("/login", auth.handler);
httpd.all("/login/logout", auth.handler);
httpd.all("/app/socket", app.socket); httpd.all("/app/socket", app.socket);
httpd.all("", function default_http_handler(request, response) { httpd.all("", function default_http_handler(request, response) {
let match; let match;
@ -1004,14 +968,12 @@ loadSettings().then(function() {
return blobHandler(request, response, match[1], match[2]); return blobHandler(request, response, match[1], match[2]);
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) { } else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
return blobHandler(request, response, match[1], match[2]); return blobHandler(request, response, match[1], match[2]);
} else if (match = /^\/static\/lit\/([\.\w-/]*)$/.exec(request.uri)) { } else if (match = /^\/lit\/([\.\w-/]*)$/.exec(request.uri)) {
return staticDirectoryHandler(request, response, 'deps/lit/', match[1]); return staticDirectoryHandler(request, response, 'deps/lit/', match[1]);
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) { } else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]); return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) { } else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]); return staticDirectoryHandler(request, response, 'deps/speedscope/', match[1]);
} else if (match = /^\/static(\/.*)/.exec(request.uri)) {
return staticFileHandler(request, response, null, match[1]);
} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) { } else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
return blobHandler(request, response, match[1], match[2]); return blobHandler(request, response, match[1], match[2]);
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) { } else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {

View File

@ -1,5 +1,6 @@
#include "file.js.h" #include "file.js.h"
#include "log.h"
#include "mem.h" #include "mem.h"
#include "task.h" #include "task.h"
#include "trace.h" #include "trace.h"
@ -51,7 +52,7 @@ void tf_file_register(JSContext* context)
JS_FreeValue(context, global); JS_FreeValue(context, global);
} }
static const int k_file_read_max = 8 * 1024 * 1024; enum { k_file_read_max = 8 * 1024 * 1024 };
static void _file_async_close_callback(uv_fs_t* req) static void _file_async_close_callback(uv_fs_t* req)
{ {
@ -426,3 +427,160 @@ static void _file_on_stat_complete(uv_fs_t* request)
uv_fs_req_cleanup(request); uv_fs_req_cleanup(request);
tf_free(data); tf_free(data);
} }
typedef struct _stat_t
{
uv_fs_t request;
tf_task_t* task;
void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data);
void* user_data;
char path[];
} stat_t;
static void _file_stat_complete(uv_fs_t* request)
{
stat_t* data = request->data;
data->callback(data->task, data->path, request->result, &request->statbuf, data->user_data);
uv_fs_req_cleanup(request);
tf_free(data);
}
void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data)
{
const char* zip = tf_task_get_zip_path(task);
size_t path_length = strlen(path) + 1;
stat_t* data = tf_malloc(sizeof(stat_t) + path_length);
*data = (stat_t) { .request = { .data = data }, .task = task, .callback = callback, .user_data = user_data };
memcpy(data->path, path, path_length);
int result = uv_fs_stat(tf_task_get_loop(task), &data->request, zip ? zip : path, _file_stat_complete);
if (result)
{
callback(task, path, result, NULL, user_data);
uv_fs_req_cleanup(&data->request);
tf_free(data);
}
}
typedef struct _read_t
{
uv_work_t work;
tf_task_t* task;
void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data);
void* user_data;
int64_t result;
char buffer[k_file_read_max];
char path[];
} read_t;
static void _file_read_work(uv_work_t* work)
{
read_t* data = work->data;
const char* zip_path = tf_task_get_zip_path(data->task);
if (zip_path)
{
tf_trace_t* trace = tf_task_get_trace(data->task);
tf_trace_begin(trace, "file_read_zip_work");
unzFile zip = unzOpen(zip_path);
if (zip)
{
data->result = unzLocateFile(zip, data->path, 1);
if (data->result == UNZ_OK)
{
unz_file_info64 info = { 0 };
data->result = unzGetCurrentFileInfo64(zip, &info, NULL, 0, NULL, 0, NULL, 0);
if (data->result == UNZ_OK && info.uncompressed_size > sizeof(data->buffer))
{
data->result = -EFBIG;
}
else if (data->result == UNZ_OK)
{
data->result = unzOpenCurrentFile(zip);
if (data->result == UNZ_OK)
{
data->result = unzReadCurrentFile(zip, data->buffer, info.uncompressed_size);
if (data->result == (int64_t)info.uncompressed_size)
{
int r = unzCloseCurrentFile(zip);
if (r != UNZ_OK)
{
data->result = r;
}
}
else
{
data->result = EAGAIN;
}
}
}
}
unzClose(zip);
}
else
{
data->result = errno;
}
tf_trace_end(trace);
}
else
{
tf_trace_t* trace = tf_task_get_trace(data->task);
tf_trace_begin(trace, "file_read_zip_work");
uv_loop_t* loop = tf_task_get_loop(data->task);
uv_fs_t open_req = { 0 };
int open_result = uv_fs_open(loop, &open_req, data->path, UV_FS_O_RDONLY, 0, NULL);
if (open_result >= 0)
{
uv_buf_t buf = { .base = data->buffer, .len = sizeof(data->buffer) };
uv_fs_t read_req = { 0 };
int result = uv_fs_read(loop, &read_req, open_result, &buf, 1, 0, NULL);
if ((size_t)result >= sizeof(data->buffer))
{
data->result = -EFBIG;
}
else
{
data->result = result;
}
uv_fs_req_cleanup(&read_req);
uv_fs_t close_req = { 0 };
result = uv_fs_close(loop, &close_req, open_result, NULL);
if (result && data->result >= 0)
{
data->result = result;
}
uv_fs_req_cleanup(&close_req);
}
else
{
data->result = errno;
}
uv_fs_req_cleanup(&open_req);
tf_trace_end(trace);
}
}
static void _file_read_after_work(uv_work_t* work, int result)
{
read_t* data = work->data;
data->callback(data->task, data->path, data->result, data->buffer, data->user_data);
tf_free(data);
}
void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data)
{
size_t path_length = strlen(path) + 1;
read_t* data = tf_malloc(sizeof(read_t) + path_length);
memset(data, 0, sizeof(read_t));
data->callback = callback;
data->user_data = user_data;
data->work.data = data;
data->task = task;
memcpy(data->path, path, path_length);
int r = uv_queue_work(tf_task_get_loop(task), &data->work, _file_read_work, _file_read_after_work);
if (r)
{
_file_read_after_work(&data->work, r);
}
}

View File

@ -1,5 +1,11 @@
#pragma once #pragma once
#include <uv.h>
typedef struct JSContext JSContext; typedef struct JSContext JSContext;
typedef struct _tf_task_t tf_task_t;
void tf_file_register(JSContext* context); void tf_file_register(JSContext* context);
void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data);
void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data);

View File

@ -102,7 +102,6 @@ typedef struct _tf_http_t
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name); 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, const char* reason); static void _http_connection_destroy(tf_http_connection_t* connection, const char* reason);
static const char* _http_status_text(int status);
static void _http_timer_reset(tf_http_connection_t* connection); static void _http_timer_reset(tf_http_connection_t* connection);
static void _http_tls_update(tf_http_connection_t* connection); static void _http_tls_update(tf_http_connection_t* connection);
@ -132,8 +131,9 @@ bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t**
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 ||
!*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)] == '/')) (*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/'))
{ {
*out_callback = http->handlers[i].callback; *out_callback = http->handlers[i].callback;
*out_trace_name = http->handlers[i].pattern; *out_trace_name = http->handlers[i].pattern;
@ -213,7 +213,7 @@ static void _http_connection_destroy(tf_http_connection_t* connection, const cha
static void _http_builtin_404_handler(tf_http_request_t* request) static void _http_builtin_404_handler(tf_http_request_t* request)
{ {
const char* k_payload = _http_status_text(404); const char* k_payload = tf_http_status_text(404);
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload)); tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
} }
@ -731,7 +731,7 @@ void tf_http_destroy(tf_http_t* http)
tf_free(http); tf_free(http);
} }
static const char* _http_status_text(int status) const char* tf_http_status_text(int status)
{ {
switch (status) switch (status)
{ {
@ -848,7 +848,7 @@ void tf_http_respond(tf_http_request_t* request, int status, const char** header
} }
request->connection->is_response_sent = true; request->connection->is_response_sent = true;
const char* status_text = _http_status_text(status); const char* status_text = tf_http_status_text(status);
/* HTTP/1.x 200 OK\r\n */ /* HTTP/1.x 200 OK\r\n */
bool sent_content_length = false; bool sent_content_length = false;
int headers_length = 8 + 1 + 3 + 1 + strlen(status_text) + 2; int headers_length = 8 + 1 + 3 + 1 + strlen(status_text) + 2;

View File

@ -51,3 +51,5 @@ void tf_http_request_unref(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); void tf_http_request_websocket_upgrade(tf_http_request_t* request);
const char* tf_http_status_text(int status);

View File

@ -1,5 +1,6 @@
#include "httpd.js.h" #include "httpd.js.h"
#include "file.js.h"
#include "http.h" #include "http.h"
#include "log.h" #include "log.h"
#include "mem.h" #include "mem.h"
@ -10,6 +11,7 @@
#include "picohttpparser.h" #include "picohttpparser.h"
#include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -508,6 +510,150 @@ static void _httpd_endpoint_hitches(tf_http_request_t* request)
tf_free(response); tf_free(response);
} }
static const char* _after(const char* text, const char* prefix)
{
size_t prefix_length = strlen(prefix);
if (strncmp(text, prefix, prefix_length) == 0)
{
return text + prefix_length;
}
return text;
}
static double _time_spec_to_double(const uv_timespec_t* time_spec)
{
return (double)time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
}
typedef struct _http_file_t
{
tf_http_request_t* request;
char etag[512];
} http_file_t;
static const char* _ext_to_content_type(const char* ext)
{
if (ext)
{
if (strcmp(ext, ".js") == 0)
{
return "text/javascript; charset=UTF-8";
}
if (strcmp(ext, ".css") == 0)
{
return "text/css; charset=UTF-8";
}
else if (strcmp(ext, ".png") == 0)
{
return "image/png";
}
}
return "application/binary";
}
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{
http_file_t* file = user_data;
tf_http_request_t* request = file->request;
if (result >= 0)
{
if (strcmp(path, "core/tfrpc.js") == 0)
{
const char* content_type = _ext_to_content_type(strrchr(path, '.'));
const char* headers[] =
{
"Content-Type", content_type,
"etag", file->etag,
"Access-Control-Allow-Origin", "null",
};
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
}
else
{
const char* content_type = _ext_to_content_type(strrchr(path, '.'));
const char* headers[] =
{
"Content-Type", content_type,
"etag", file->etag,
};
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
}
}
else
{
tf_printf("404 %s\n", path);
const char* k_payload = tf_http_status_text(404);
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
}
tf_http_request_unref(request);
tf_free(file);
}
static void _httpd_endpoint_static_stat(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data)
{
tf_http_request_t* request = user_data;
const char* match = tf_http_request_get_header(request, "if-none-match");
char etag[512];
snprintf(etag, sizeof(etag), "\"%f_%zd\"", _time_spec_to_double(&stat->st_mtim), (size_t)stat->st_size);
if (match && strcmp(match, etag) == 0)
{
tf_http_respond(request, 304, NULL, 0, NULL, 0);
tf_http_request_unref(request);
}
else
{
http_file_t* file = tf_malloc(sizeof(http_file_t));
*file = (http_file_t) { .request = request };
static_assert(sizeof(file->etag) == sizeof(etag), "Size mismatch");
memcpy(file->etag, etag, sizeof(etag));
tf_file_read(task, path, _httpd_endpoint_static_read, file);
}
}
static void _httpd_endpoint_static(tf_http_request_t* request)
{
if (_httpd_redirect(request))
{
return;
}
const char* k_static_files[] =
{
"index.html",
"client.js",
"favicon.png",
"jszip.min.js",
"style.css",
"tfrpc.js",
"w3.css",
};
tf_task_t* task = request->user_data;
const char* after = _after(request->path, "/static/");
bool found = false;
for (int i = 0; i < tf_countof(k_static_files); i++)
{
if (strcmp(after, k_static_files[i]) == 0)
{
found = true;
break;
}
}
if (!found)
{
const char* k_payload = tf_http_status_text(404);
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
return;
}
size_t size = strlen("core/") + strlen(after) + 1;
char* path = alloca(size);
snprintf(path, size, "core/%s", after);
tf_http_request_ref(request);
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
}
static void _httpd_endpoint_robots_txt(tf_http_request_t* request) static void _httpd_endpoint_robots_txt(tf_http_request_t* request)
{ {
if (_httpd_redirect(request)) if (_httpd_redirect(request))
@ -571,6 +717,7 @@ void tf_httpd_register(JSContext* context)
tf_http_set_trace(http, tf_task_get_trace(task)); tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http); JS_SetOpaque(httpd, http);
tf_http_add_handler(http, "/static/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);
tf_http_add_handler(http, "/disconnections", _httpd_endpoint_disconnections, NULL, task); tf_http_add_handler(http, "/disconnections", _httpd_endpoint_disconnections, NULL, task);