diff options
Diffstat (limited to 'include/webassets.hpp')
-rw-r--r-- | include/webassets.hpp | 66 |
1 files changed, 63 insertions, 3 deletions
diff --git a/include/webassets.hpp b/include/webassets.hpp index c5c7228ede..4bcc8cb69b 100644 --- a/include/webassets.hpp +++ b/include/webassets.hpp @@ -25,6 +25,37 @@ struct CmpStr } }; +inline std::string getStaticEtag(const std::filesystem::path& webpath) +{ + // webpack outputs production chunks in the form: + // <filename>.<hash>.<extension> + // For example app.63e2c453.css + // Try to detect this, so we can use the hash as the ETAG + std::vector<std::string> split; + bmcweb::split(split, webpath.filename().string(), '.'); + BMCWEB_LOG_DEBUG("Checking {} split.size() {}", webpath.filename().string(), + split.size()); + if (split.size() < 3) + { + return ""; + } + + // get the second to last element + std::string hash = split.rbegin()[1]; + + // Webpack hashes are 8 characters long + if (hash.size() != 8) + { + return ""; + } + // Webpack hashes only include hex printable characters + if (hash.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) + { + return ""; + } + return std::format("\"{}\"", hash); +} + inline void requestRoutes(App& app) { constexpr static std::array<std::pair<const char*, const char*>, 17> @@ -99,6 +130,9 @@ inline void requestRoutes(App& app) contentEncoding = "gzip"; } + std::string etag = getStaticEtag(webpath); + + bool renamed = false; if (webpath.filename().string().starts_with("index.")) { webpath = webpath.parent_path(); @@ -107,6 +141,7 @@ inline void requestRoutes(App& app) // insert the non-directory version of this path webroutes::routes.insert(webpath); webpath += "/"; + renamed = true; } } @@ -147,9 +182,9 @@ inline void requestRoutes(App& app) } app.routeDynamic(webpath)( - [absolutePath, contentType, contentEncoding]( - const crow::Request&, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { + [absolutePath, contentType, contentEncoding, etag, + renamed](const crow::Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { if (contentType != nullptr) { asyncResp->res.addHeader( @@ -163,6 +198,31 @@ inline void requestRoutes(App& app) contentEncoding); } + if (!etag.empty()) + { + asyncResp->res.addHeader(boost::beast::http::field::etag, + etag); + // Don't cache paths that don't have the etag in them, like + // index, which gets transformed to / + if (!renamed) + { + // Anything with a hash can be cached forever and is + // immutable + asyncResp->res.addHeader( + boost::beast::http::field::cache_control, + "max-age=31556926, immutable"); + } + + std::string_view cachedEtag = req.getHeaderValue( + boost::beast::http::field::if_none_match); + if (cachedEtag == etag) + { + asyncResp->res.result( + boost::beast::http::status::not_modified); + return; + } + } + // res.set_header("Cache-Control", "public, max-age=86400"); if (!asyncResp->res.openFile(absolutePath)) { |