diff --git a/tools/server/server-http.cpp b/tools/server/server-http.cpp index c0a9af8f0..00290b078 100644 --- a/tools/server/server-http.cpp +++ b/tools/server/server-http.cpp @@ -318,12 +318,19 @@ bool server_http_context::init(const common_params & params) { } else { #if defined(LLAMA_UI_HAS_ASSETS) auto serve_asset = [](const std::string & name, const char * mime, bool with_isolation_headers) { - return [name, mime, with_isolation_headers](const httplib::Request & /*req*/, httplib::Response & res) { + return [name, mime, with_isolation_headers](const httplib::Request & req, httplib::Response & res) { const llama_ui_asset * a = llama_ui_find_asset(name.c_str()); if (!a) { res.status = 404; return false; } + res.set_header("ETag", a->etag); + // Check If-None-Match for conditional GET (304 Not Modified) + if (const std::string & inm = req.get_header_value("If-None-Match"); + !inm.empty() && inm == a->etag) { + res.status = 304; + return false; + } if (with_isolation_headers) { // COEP and COOP headers, required by pyodide (python interpreter) res.set_header("Cross-Origin-Embedder-Policy", "require-corp"); diff --git a/tools/ui/embed.cpp b/tools/ui/embed.cpp index 41227868e..c88faefa1 100644 --- a/tools/ui/embed.cpp +++ b/tools/ui/embed.cpp @@ -9,6 +9,19 @@ #include #include #include +#include + +// Computes FNV-1a hash of the data +static uint64_t fnv_hash(const uint8_t * data, size_t len) { + const uint64_t fnv_prime = 0x100000001b3ULL; + uint64_t hash = 0xcbf29ce484222325ULL; + + for (size_t i = 0; i < len; ++i) { + hash ^= data[i]; + hash *= fnv_prime; + } + return hash; +} static bool read_file(const std::string & path, std::vector & out) { std::ifstream f(path, std::ios::binary | std::ios::ate); @@ -95,6 +108,7 @@ int main(int argc, char ** argv) { " const char * name;\n" " const unsigned char * data;\n" " size_t size;\n" + " const char * etag;\n" "};\n\n" "const llama_ui_asset * llama_ui_find_asset(const char * name);\n"; @@ -110,14 +124,18 @@ int main(int argc, char ** argv) { } cpp += fmt("static const unsigned char asset_%d_data[] = {", i); append_bytes_hex(cpp, bytes); - cpp += fmt("};\nstatic const size_t asset_%d_size = %lu;\n\n", + const auto hash = fnv_hash(bytes.data(), bytes.size()); + + cpp += fmt("};\nstatic const size_t asset_%d_size = %lu;\n", i, static_cast(bytes.size())); + cpp += fmt("static const char asset_%d_etag[] = \"\\\"0x%016lx\\\"\";\n\n", + i, static_cast(hash)); } cpp += "static const llama_ui_asset g_assets[] = {\n"; for (int i = 0; i < n_assets; i++) { - const char * name = argv[3 + i * 2]; - cpp += fmt(" { \"%s\", asset_%d_data, asset_%d_size },\n", name, i, i); + cpp += fmt(" { \"%s\", asset_%d_data, asset_%d_size, asset_%d_etag },\n", + argv[3 + i * 2], i, i, i); } cpp += "};\n\n";