forked from cory/tildefriends
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:
parent
e87acc6286
commit
ed2d57fb4b
@ -11,7 +11,7 @@
|
||||
<tf-auth id="auth"></tf-auth>
|
||||
<script>window.litDisableBundleWarning = true;</script>
|
||||
<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 app = document.getElementById('auth');
|
||||
Object.assign(app, g_data);
|
||||
|
@ -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 gSocket;
|
||||
|
42
core/core.js
42
core/core.js
@ -35,12 +35,6 @@ const k_magic_bytes = [
|
||||
|
||||
let k_static_files = [
|
||||
{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 = {
|
||||
@ -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) {
|
||||
let filename = uri || 'index.html';
|
||||
if (filename.indexOf('..') != -1) {
|
||||
@ -983,6 +946,7 @@ loadSettings().then(function() {
|
||||
httpd.set_http_redirect(gGlobalSettings.http_redirect);
|
||||
}
|
||||
httpd.all("/login", auth.handler);
|
||||
httpd.all("/login/logout", auth.handler);
|
||||
httpd.all("/app/socket", app.socket);
|
||||
httpd.all("", function default_http_handler(request, response) {
|
||||
let match;
|
||||
@ -1004,14 +968,12 @@ loadSettings().then(function() {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) {
|
||||
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]);
|
||||
} else if (match = /^\/codemirror\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
return staticDirectoryHandler(request, response, 'deps/codemirror/', match[1]);
|
||||
} else if (match = /^\/speedscope\/([\.\w-/]*)$/.exec(request.uri)) {
|
||||
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)) {
|
||||
return blobHandler(request, response, match[1], match[2]);
|
||||
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
|
||||
|
160
src/file.js.c
160
src/file.js.c
@ -1,5 +1,6 @@
|
||||
#include "file.js.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "task.h"
|
||||
#include "trace.h"
|
||||
@ -51,7 +52,7 @@ void tf_file_register(JSContext* context)
|
||||
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)
|
||||
{
|
||||
@ -426,3 +427,160 @@ static void _file_on_stat_complete(uv_fs_t* request)
|
||||
uv_fs_req_cleanup(request);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
typedef struct JSContext JSContext;
|
||||
typedef struct _tf_task_t tf_task_t;
|
||||
|
||||
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);
|
||||
|
10
src/http.c
10
src/http.c
@ -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 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_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++)
|
||||
{
|
||||
if (!http->handlers[i].pattern ||
|
||||
!*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)] == '/'))
|
||||
(*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_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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@ -731,7 +731,7 @@ void tf_http_destroy(tf_http_t* http)
|
||||
tf_free(http);
|
||||
}
|
||||
|
||||
static const char* _http_status_text(int status)
|
||||
const char* tf_http_status_text(int 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;
|
||||
|
||||
const char* status_text = _http_status_text(status);
|
||||
const char* status_text = tf_http_status_text(status);
|
||||
/* HTTP/1.x 200 OK\r\n */
|
||||
bool sent_content_length = false;
|
||||
int headers_length = 8 + 1 + 3 + 1 + strlen(status_text) + 2;
|
||||
|
@ -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);
|
||||
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);
|
||||
|
||||
const char* tf_http_status_text(int status);
|
||||
|
147
src/httpd.js.c
147
src/httpd.js.c
@ -1,5 +1,6 @@
|
||||
#include "httpd.js.h"
|
||||
|
||||
#include "file.js.h"
|
||||
#include "http.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
@ -10,6 +11,7 @@
|
||||
|
||||
#include "picohttpparser.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -508,6 +510,150 @@ static void _httpd_endpoint_hitches(tf_http_request_t* request)
|
||||
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)
|
||||
{
|
||||
if (_httpd_redirect(request))
|
||||
@ -571,6 +717,7 @@ void tf_httpd_register(JSContext* context)
|
||||
tf_http_set_trace(http, tf_task_get_trace(task));
|
||||
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, "/debug", _httpd_endpoint_debug, NULL, task);
|
||||
tf_http_add_handler(http, "/disconnections", _httpd_endpoint_disconnections, NULL, task);
|
||||
|
Loading…
Reference in New Issue
Block a user