From b277d0274c82e1607029c82ee939ce1ddc4b638f Mon Sep 17 00:00:00 2001 From: blankie Date: Fri, 12 May 2023 16:56:22 +0700 Subject: [PATCH] Implement ETags --- routes/css.cpp | 24 ++++++++++--- routes/routes.h | 2 ++ servehelper.cpp | 30 +++++++++++++--- servehelper.h | 1 + thirdparty/FastHash.h | 80 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 thirdparty/FastHash.h diff --git a/routes/css.cpp b/routes/css.cpp index cd019b7..01bd272 100644 --- a/routes/css.cpp +++ b/routes/css.cpp @@ -1,8 +1,10 @@ +#include +#include + #include "routes.h" +#include "../servehelper.h" -void css_route(const httplib::Request& req, httplib::Response& res) { - res.set_content(R"EOF( - +#define CSS R"EOF( /* GENERAL */ :root { --background-color: black; @@ -127,6 +129,20 @@ void css_route(const httplib::Request& req, httplib::Response& res) { border-style: solid; border-color: var(--error-border-color); } +)EOF" +#define CSS_LEN sizeof(CSS) / sizeof(CSS[0]) - 1 - )EOF", "text/css"); +const uint64_t css_hash = FastHashConstEval(CSS, CSS_LEN, 0); + +void css_route(const httplib::Request& req, httplib::Response& res) { + res.set_header("ETag", std::string(1, '"') + std::to_string(css_hash) + '"'); + res.set_header("Cache-Control", "max-age=31536000, immutable"); + + if (should_send_304(req, css_hash)) { + res.status = 304; + res.set_header("Content-Length", std::to_string(CSS_LEN)); + res.set_header("Content-Type", "text/css"); + } else { + res.set_content(CSS, CSS_LEN, "text/css"); + } } diff --git a/routes/routes.h b/routes/routes.h index d10a45e..10f3499 100644 --- a/routes/routes.h +++ b/routes/routes.h @@ -5,6 +5,8 @@ struct Config; // forward declaration from ../config.h class PixivClient; // forward declaration from ../pixivclient.h +extern const uint64_t css_hash; + void home_route(const httplib::Request& req, httplib::Response& res, const Config& config); void css_route(const httplib::Request& req, httplib::Response& res); void user_illustrations_route(const httplib::Request& req, httplib::Response& res, const Config& config, PixivClient& pixiv_client); diff --git a/servehelper.cpp b/servehelper.cpp index af308da..f305ced 100644 --- a/servehelper.cpp +++ b/servehelper.cpp @@ -1,8 +1,10 @@ #include +#include #include "config.h" #include "pixivmodels.h" #include "servehelper.h" +#include "routes/routes.h" static Element generate_pager(const Illusts& illusts, size_t page, const char* id); static inline Element generate_illusts_grid(const httplib::Request& req, const Config& config, const Illusts& illusts); @@ -16,16 +18,26 @@ void serve(const httplib::Request& req, httplib::Response& res, const Config& co res.set_header("Content-Security-Policy", "default-src 'none'; style-src "s + css_url + "; img-src https://s.pximg.net " + config.image_proxy_url.get_origin()); - Element html("html", { + std::string html = ""s + Element("html", { Element("head", { Element("meta", {{"charset", "utf-8"}}, {}), Element("title", {std::move(title)}), - Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url)}}, {}), + Element("link", {{"rel", "stylesheet"}, {"href", std::move(css_url) + "?v=" + std::to_string(css_hash)}}, {}), Element("meta", {{"name", "viewport"}, {"content", "width=device-width,initial-scale=1"}}, {}) }), std::move(element) - }); - res.set_content(""s + html.serialize(), "text/html"); + }).serialize(); + + uint64_t hash = FastHash(html.data(), html.size(), 0); + res.set_header("ETag", std::string(1, '"') + std::to_string(hash) + '"'); + + if (should_send_304(req, hash)) { + res.status = 304; + res.set_header("Content-Length", std::to_string(html.size())); + res.set_header("Content-Type", "text/html"); + } else { + res.set_content(std::move(html), "text/html"); + } } void serve_error(const httplib::Request& req, httplib::Response& res, const Config& config, @@ -100,6 +112,16 @@ std::string proxy_image_url(const Config& config, blankie::murl::Url url) { return proxy_url(config.image_proxy_url, std::move(url)); } +bool should_send_304(const httplib::Request& req, uint64_t hash) { + std::string header = req.get_header_value("If-None-Match"); + if (header == "*") { + return true; + } + + size_t pos = header.find(std::string(1, '"') + std::to_string(hash) + '"'); + return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/'); +} + Element generate_illusts_pager(const httplib::Request& req, const Config& config, const Illusts& illusts, size_t page, const char* id) { diff --git a/servehelper.h b/servehelper.h index 07d2076..76e6a56 100644 --- a/servehelper.h +++ b/servehelper.h @@ -18,5 +18,6 @@ void serve_redirect(const httplib::Request& req, httplib::Response& res, const C std::string get_origin(const httplib::Request& req, const Config& config); std::string proxy_url(blankie::murl::Url base, blankie::murl::Url url); std::string proxy_image_url(const Config& config, blankie::murl::Url url); +bool should_send_304(const httplib::Request& req, uint64_t hash); Element generate_illusts_pager(const httplib::Request& req, const Config& config, const Illusts& illusts, size_t page, const char* id); diff --git a/thirdparty/FastHash.h b/thirdparty/FastHash.h new file mode 100644 index 0000000..9771eb7 --- /dev/null +++ b/thirdparty/FastHash.h @@ -0,0 +1,80 @@ +// https://github.com/KoneLinx/small_ECS/blob/master/FastHash.h +// but slightly modified to add FastHashConstEval and abandon ranges + +// Copyright(C) 2021 Kobe Vrijsen +// +// A simple ECS example +// +// This file is free software and distributed under the terms of the European Union +// Public Lincense as published by the European Commision; either version 1.2 of the +// License, or , at your option, any later version. + +/* The MIT License + + Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +// Source: fast-hash https://github.com/ztanml/fast-hash (26 Nov 2021) +// Changes: General uplift and providing a general constant hash for ranges + +#pragma once + +// Compression function for Merkle-Damgard construction. +// This function is generated using the framework provided. +//#define mix(h) ({ +// (h) ^= (h) >> 23; +// (h) *= 0x2127599bf4325c37ULL; +// (h) ^= (h) >> 47; }) + +constexpr uint64_t FastHash(const char* str, size_t size, uint64_t seed/*, uint64_t back = {}*/) +{ + auto const mix = [](uint64_t h) + { + h ^= h >> 23; + h *= 0x2127599bf4325c37ULL; + h ^= h >> 47; + return h; + }; + + const uint64_t m = 0x880355f21e6d1965ULL; + + uint64_t h = seed ^ (size * m); + + for (size_t i = 0; i < size; i++) + { + h ^= mix(static_cast(str[i])); + h *= m; + } + + //if (back != uint64_t()) + //{ + // h ^= mix(back); + // h *= m; + //} + + return mix(h); +} + +consteval uint64_t FastHashConstEval(const char* str, size_t size, uint64_t seed) { + return FastHash(str, size, seed); +}