mirror of
https://github.com/hoytech/strfry.git
synced 2025-06-18 17:27:11 +00:00
simplify caching
This commit is contained in:
@ -34,18 +34,21 @@ struct HTTPResponse : NonCopyable {
|
||||
std::string_view contentType = "text/html; charset=utf-8";
|
||||
std::string extraHeaders;
|
||||
std::string body;
|
||||
bool noCompress = false;
|
||||
|
||||
std::string eTag() {
|
||||
std::string getETag() {
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256(reinterpret_cast<unsigned char*>(body.data()), body.size(), hash);
|
||||
return to_hex(std::string_view(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH/2));
|
||||
}
|
||||
|
||||
std::string encode(bool doCompress) {
|
||||
std::string eTag = getETag();
|
||||
|
||||
std::string compressed;
|
||||
bool didCompress = false;
|
||||
|
||||
if (doCompress) {
|
||||
if (!noCompress && doCompress) {
|
||||
compressed.resize(body.size());
|
||||
|
||||
z_stream zs;
|
||||
@ -78,7 +81,9 @@ struct HTTPResponse : NonCopyable {
|
||||
output += std::to_string(bodySize);
|
||||
output += "\r\nContent-Type: ";
|
||||
output += contentType;
|
||||
output += "\r\n";
|
||||
output += "\r\nETag: \"";
|
||||
output += eTag;
|
||||
output += "\"\r\n";
|
||||
if (didCompress) output += "Content-Encoding: gzip\r\nVary: Accept-Encoding\r\n";
|
||||
output += extraHeaders;
|
||||
output += "Connection: Keep-Alive\r\n\r\n";
|
||||
|
@ -132,7 +132,7 @@ TemplarResult renderCommunityEvents(lmdb::txn &txn, Decompressor &decomp, UserCa
|
||||
|
||||
|
||||
|
||||
HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decomp, const HTTPRequest &req, uint64_t &cacheTime) {
|
||||
HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decomp, const HTTPRequest &req) {
|
||||
HTTPResponse httpResp;
|
||||
|
||||
auto startTime = hoytech::curr_time_us();
|
||||
@ -157,7 +157,7 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom
|
||||
|
||||
if (u.path.size() == 0 || u.path[0] == "algo") {
|
||||
communitySpec = lookupCommunitySpec(txn, decomp, userCache, cfg().web__homepageCommunity);
|
||||
cacheTime = 30'000'000;
|
||||
httpResp.extraHeaders += "Cache-Control: max-age=600\r\n";
|
||||
}
|
||||
|
||||
if (u.path.size() == 0) {
|
||||
@ -280,6 +280,8 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom
|
||||
} else if (u.path[0] == "post") {
|
||||
body = tmpl::newPost(nullptr);
|
||||
} else if (u.path[0] == "static" && u.path.size() >= 2) {
|
||||
httpResp.extraHeaders += "Cache-Control: max-age=31536000\r\n";
|
||||
|
||||
if (u.path[1] == "oddbean.js") {
|
||||
rawBody = std::string(oddbeanStatic__oddbean_js());
|
||||
contentType = "application/javascript";
|
||||
@ -293,6 +295,8 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom
|
||||
} else if (u.path[0] == "favicon.ico") {
|
||||
rawBody = std::string(oddbeanStatic__favicon_ico());
|
||||
contentType = "image/x-icon";
|
||||
httpResp.extraHeaders += "Cache-Control: max-age=2592000\r\n";
|
||||
httpResp.noCompress = true;
|
||||
} else if (u.path[0] == "login") {
|
||||
body = tmpl::login(0);
|
||||
} else if (u.path[0] == "about") {
|
||||
@ -312,11 +316,17 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom
|
||||
const std::optional<CommunitySpec> &communitySpec;
|
||||
std::string_view title;
|
||||
std::string staticFilesPrefix;
|
||||
std::string_view staticOddbeanCssHash;
|
||||
std::string_view staticOddbeanJsHash;
|
||||
std::string_view staticOddbeanSvgHash;
|
||||
} ctx = {
|
||||
*body,
|
||||
communitySpec,
|
||||
title,
|
||||
cfg().web__staticFilesPrefix.size() ? cfg().web__staticFilesPrefix : "/static",
|
||||
oddbeanStatic__oddbean_css__hash().substr(0, 16),
|
||||
oddbeanStatic__oddbean_js__hash().substr(0, 16),
|
||||
oddbeanStatic__oddbean_svg__hash().substr(0, 16),
|
||||
};
|
||||
|
||||
responseData = std::move(tmpl::main(ctx).str);
|
||||
@ -337,107 +347,6 @@ HTTPResponse WebServer::generateReadResponse(lmdb::txn &txn, Decompressor &decom
|
||||
}
|
||||
|
||||
|
||||
|
||||
void WebServer::handleReadRequest(lmdb::txn &txn, Decompressor &decomp, uint64_t lockedThreadId, HTTPRequest &req) {
|
||||
auto now = hoytech::curr_time_us();
|
||||
std::string response;
|
||||
bool cacheItemFound = false, preGenerate = false;
|
||||
|
||||
{
|
||||
CacheItem *item = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(cacheLock);
|
||||
auto it = cache.find(req.url);
|
||||
if (it != cache.end()) item = it->second.get();
|
||||
}
|
||||
|
||||
bool addedToPending = false;
|
||||
|
||||
if (item) {
|
||||
cacheItemFound = true;
|
||||
std::lock_guard<std::mutex> guard(item->lock);
|
||||
|
||||
if (now < item->expiry) {
|
||||
response = req.acceptGzip ? item->payloadGzip : item->payload;
|
||||
if (now > item->softExpiry && !item->generationInProgress) {
|
||||
preGenerate = true;
|
||||
item->generationInProgress = true;
|
||||
LI << "DOING PREGEN";
|
||||
}
|
||||
} else {
|
||||
if (item->generationInProgress) {
|
||||
item->pendingRequests.emplace_back(std::move(req));
|
||||
addedToPending = true;
|
||||
}
|
||||
|
||||
item->generationInProgress = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedToPending) {
|
||||
unlockThread(lockedThreadId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.size()) {
|
||||
if (preGenerate) {
|
||||
sendHttpResponseAndUnlock(MAX_U64, req, response);
|
||||
} else {
|
||||
sendHttpResponseAndUnlock(lockedThreadId, req, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t cacheTime = 0;
|
||||
|
||||
// FIXME: try/catch
|
||||
auto resp = generateReadResponse(txn, decomp, req, cacheTime);
|
||||
|
||||
if (cacheTime == 0 && !cacheItemFound) {
|
||||
std::string payload = resp.encode(req.acceptGzip);
|
||||
sendHttpResponseAndUnlock(lockedThreadId, req, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string payload = resp.encode(false);
|
||||
std::string payloadGzip = resp.encode(true);
|
||||
now = hoytech::curr_time_us();
|
||||
std::vector<HTTPRequest> pendingRequests;
|
||||
|
||||
{
|
||||
CacheItem *item = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(cacheLock);
|
||||
item = cache.emplace(req.url, std::make_unique<CacheItem>()).first->second.get();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(item->lock);
|
||||
|
||||
item->expiry = now + cacheTime;
|
||||
item->softExpiry = now + (cacheTime/2);
|
||||
|
||||
item->payload = payload;
|
||||
item->payloadGzip = payloadGzip;
|
||||
|
||||
item->generationInProgress = false;
|
||||
std::swap(item->pendingRequests, pendingRequests);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &r : pendingRequests) {
|
||||
std::string myPayload = r.acceptGzip ? payloadGzip : payload;
|
||||
sendHttpResponseAndUnlock(MAX_U64, r, myPayload);
|
||||
}
|
||||
|
||||
if (preGenerate) unlockThread(lockedThreadId);
|
||||
else sendHttpResponseAndUnlock(lockedThreadId, req, req.acceptGzip ? payloadGzip : payload);
|
||||
}
|
||||
|
||||
|
||||
void WebServer::runReader(ThreadPool<MsgWebReader>::Thread &thr) {
|
||||
Decompressor decomp;
|
||||
|
||||
@ -449,7 +358,9 @@ void WebServer::runReader(ThreadPool<MsgWebReader>::Thread &thr) {
|
||||
for (auto &newMsg : newMsgs) {
|
||||
if (auto msg = std::get_if<MsgWebReader::Request>(&newMsg.msg)) {
|
||||
try {
|
||||
handleReadRequest(txn, decomp, msg->lockedThreadId, msg->req);
|
||||
HTTPResponse resp = generateReadResponse(txn, decomp, msg->req);
|
||||
std::string payload = resp.encode(msg->req.acceptGzip);
|
||||
sendHttpResponseAndUnlock(msg->lockedThreadId, msg->req, payload);
|
||||
} catch (std::exception &e) {
|
||||
HTTPResponse res;
|
||||
res.code = "500 Server Error";
|
||||
|
@ -77,26 +77,6 @@ struct WebServer {
|
||||
std::unique_ptr<uS::Async> hubTrigger;
|
||||
|
||||
|
||||
// HTTP response cache
|
||||
|
||||
struct CacheItem {
|
||||
std::mutex lock;
|
||||
|
||||
uint64_t expiry;
|
||||
uint64_t softExpiry;
|
||||
|
||||
std::string payload;
|
||||
std::string payloadGzip;
|
||||
std::string eTag;
|
||||
|
||||
bool generationInProgress = false;
|
||||
std::vector<HTTPRequest> pendingRequests;
|
||||
};
|
||||
|
||||
std::mutex cacheLock;
|
||||
flat_hash_map<std::string, std::unique_ptr<CacheItem>> cache;
|
||||
|
||||
|
||||
// Thread Pools
|
||||
|
||||
ThreadPool<MsgHttpsocket> tpHttpsocket;
|
||||
@ -110,7 +90,7 @@ struct WebServer {
|
||||
|
||||
void runReader(ThreadPool<MsgWebReader>::Thread &thr);
|
||||
void handleReadRequest(lmdb::txn &txn, Decompressor &decomp, uint64_t lockedThreadId, HTTPRequest &req);
|
||||
HTTPResponse generateReadResponse(lmdb::txn &txn, Decompressor &decomp, const HTTPRequest &req, uint64_t &cacheTime);
|
||||
HTTPResponse generateReadResponse(lmdb::txn &txn, Decompressor &decomp, const HTTPRequest &req);
|
||||
|
||||
void runWriter(ThreadPool<MsgWebWriter>::Thread &thr);
|
||||
|
||||
|
@ -15,7 +15,7 @@ void WebServer::run() {
|
||||
|
||||
// FIXME: cfg().web__numThreads__*
|
||||
|
||||
tpReader.init("Reader", 3, [this](auto &thr){
|
||||
tpReader.init("Reader", 10, [this](auto &thr){
|
||||
runReader(thr);
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="info-page">
|
||||
<p>
|
||||
Hi, thanks for stopping by! Oddbean is a discussion site built on <a href="https://github.com/nostr-protocol/nostr">the nostr protocol</a>. You may notice that our design is heavily inspired by Hacker News and Reddit. This is on purpose. I want Oddbean to be a comfortable and familiar experience, with fast loading pages and minimal distractions.
|
||||
Hi, thanks for stopping by! Oddbean is a discussion site built on <a href="https://github.com/nostr-protocol/nostr">the nostr protocol</a>. You may notice that the design is heavily inspired by Hacker News and Reddit. This is on purpose. I want Oddbean to be a comfortable and familiar experience, with fast loading pages and minimal distractions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@ -8,7 +8,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>No crap.</b> I just want to build a great distraction-free discussion site. No elements bouncing around as the page loads. No cookie pop-ups. In fact, no cookies, period. No "sign-up to our newsletter" modals that appear half way down. Javascript is optional (only needed for posting).
|
||||
<b>No crap.</b> I just want to build a great distraction-free discussion site. No elements bouncing around as the page loads. No cookie pop-ups. In fact, no cookies, period. No "sign-up to our newsletter" modals that appear half way down. No dark patterns to boost engagement numbers. No tracking. Javascript is optional (only needed for posting).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -12,7 +12,7 @@
|
||||
(<a href="/e/$(ctx.ev->getNoteId())/raw.json">raw</a>)
|
||||
<> | <a href="/e/$(ctx.ev->getRootNoteId())">root</a> </> ?(ctx.ev->root.size())
|
||||
|
||||
<> | <a href="/e/$(ctx.ev->getNoteId())">show full thread</a> </> ?(ctx.ev->root.empty() && !ctx.isFullThreadLoaded)
|
||||
<> | <a href="/e/$(ctx.ev->getNoteId())">full thread</a> </> ?(ctx.ev->root.empty() && !ctx.isFullThreadLoaded)
|
||||
<> | <a href="/e/$(ctx.ev->getNoteId())/export.jsonl">export</a> </> ?(ctx.ev->root.empty() && ctx.isFullThreadLoaded)
|
||||
|
||||
<> | <a href="/e/$(ctx.ev->getParentNoteId())">parent</a> </> ?(ctx.ev->parent.size())
|
||||
|
@ -2,8 +2,8 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="$(ctx.staticFilesPrefix)/oddbean.css" rel="stylesheet">
|
||||
<script defer src="$(ctx.staticFilesPrefix)/oddbean.js"></script>
|
||||
<link href="$(ctx.staticFilesPrefix)/oddbean.css?$(ctx.staticOddbeanCssHash)" rel="stylesheet">
|
||||
<script defer src="$(ctx.staticFilesPrefix)/oddbean.js?$(ctx.staticOddbeanJsHash)"></script>
|
||||
|
||||
<title>$(ctx.title)Oddbean</title>
|
||||
</head>
|
||||
@ -11,7 +11,7 @@
|
||||
<body>
|
||||
<div id="ob-header">
|
||||
<span class="links">
|
||||
<a href="/"><img class="logo" src="$(ctx.staticFilesPrefix)/oddbean.svg"></a>
|
||||
<a href="/"><img class="logo" src="$(ctx.staticFilesPrefix)/oddbean.svg?$(ctx.staticOddbeanSvgHash)"></a>
|
||||
|
||||
<a href="/" class="sitename">
|
||||
<span class="oddbean-name">Oddbean</span>
|
||||
|
Reference in New Issue
Block a user