Loading src/compress.h +21 −55 Original line number Diff line number Diff line #include <algorithm> #include <string> #include <cstring> #include <brotli/encode.h> #include <httppp/http.h> #pragma once inline void compress(const std::string& in, std::string& out) { size_t compressed_size = BrotliEncoderMaxCompressedSize(in.length()); unsigned char *buf = new unsigned char[compressed_size]; size_t actual_compressed_len = compressed_size; BROTLI_BOOL result = BrotliEncoderCompress( BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_TEXT, in.length(), (const unsigned char*)in.c_str(), &actual_compressed_len, // buf ); if (result != BROTLI_TRUE) { out.clear(); delete[] buf; return; } out.resize(actual_compressed_len); std::memcpy(out.data(), buf, actual_compressed_len); #include <string> #include <vector> delete[] buf; } #include <httppp/http.h> #include <httppp/httpcompression.h> inline void sendCompressed(libhttppp::HttpRequest &req, libhttppp::HttpResponse &resp, const char *body, size_t bodylen) { bool accept_br = false; libhttppp::HttpHeader::HeaderData *acph = req.getHeaderData("accept-encoding"); if (acph) { for (auto *cdat = acph->getfirstValue(); cdat; cdat = cdat->nextvalue()) { std::string val = cdat->getvalue(); std::transform(val.begin(), val.end(), val.begin(), [](unsigned char c) { return std::tolower(c); }); if (val.find("br") != std::string::npos) { accept_br = true; break; } } } if (accept_br && bodylen > 0) { std::string in(body, bodylen); std::string compressed; compress(in, compressed); if (!compressed.empty()) { resp.setHeaderData("content-encoding")->push_back("br"); using namespace libhttppp::compression; std::string acceptEncoding; if (auto *acph = req.getHeaderData("accept-encoding")) { if (auto *v = acph->getfirstValue()) acceptEncoding = v->getvalue(); } Encoding wanted = negotiate(acceptEncoding); if (wanted != Encoding::None && bodylen > 0) { std::vector<char> in(body, body + bodylen); std::vector<char> compressed; bool ok = (wanted == Encoding::Brotli) ? brotliCompress(in, compressed) : gzipCompress(in, compressed); if (ok && !compressed.empty()) { resp.setHeaderData("content-encoding")->push_back(toToken(wanted)); resp.setHeaderData("vary")->push_back("Accept-Encoding"); resp.send(req, reinterpret_cast<const unsigned char *>(compressed.data()), static_cast<int>(compressed.length())); static_cast<int>(compressed.size())); return; } } Loading src/theme.cpp +71 −50 Original line number Diff line number Diff line Loading @@ -38,14 +38,36 @@ #include <httppp/exception.h> #include <httppp/http.h> #include <httppp/httpcompression.h> #include <htmlpp/exception.h> #include "theme.h" #include "conf.h" #include "compress.h" #include "i18n.h" namespace { // Precomputes both encodings once at theme-load time so per-request serving (theme.cpp's // Controller/Response pair) never has to compress on the hot path. An encoding that fails // (shouldn't happen in practice) is left empty -- selectEncoding() treats that the same as // "not worth serving compressed" and falls back to the raw content. void precompressContent(const std::string &content, std::string &outGzip, std::string &outBrotli) { std::vector<char> in(content.begin(), content.end()); std::vector<char> gz; if (libhttppp::compression::gzipCompress(in, gz)) { outGzip.assign(gz.begin(), gz.end()); } std::vector<char> br; if (libhttppp::compression::brotliCompress(in, br)) { outBrotli.assign(br.begin(), br.end()); } } } // namespace blogi::Theme::Theme(blogi::ThemeConfig& config,bool formated) : _Config(config){ auto trimlines = [&] (const std::string &in){ std::string out; Loading Loading @@ -172,12 +194,12 @@ blogi::Theme::Theme(blogi::ThemeConfig& config,bool formated) : _Config(config){ } else if (tfile.Ending == "css" || tfile.Ending == "html") { tfile.Content = trimlines(buf); // Apply minification compress(tfile.Content, tfile.Compressed); // Compress content precompressContent(tfile.Content, tfile.CompressedGzip, tfile.CompressedBrotli); tfile.Type = ThemeFilesTypes::TEXT; } else if (tfile.Ending == "js") { tfile.Content = trimlines(buf); // Apply minification compress(tfile.Content, tfile.Compressed); // Compress content precompressContent(tfile.Content, tfile.CompressedGzip, tfile.CompressedBrotli); tfile.Type = ThemeFilesTypes::JAVASCRIPT; } else { Loading Loading @@ -355,6 +377,26 @@ void blogi::Theme::renderPage(const int tid,const char *name,libhtmlpp::HtmlPage page.loadString(index,data.data()); // Load processed HTML content } libhttppp::compression::Encoding blogi::Theme::selectEncoding(libhttppp::HttpRequest &req, const ThemeFiles &file) const { using namespace libhttppp::compression; std::string acceptEncoding; if (auto *acph = req.getHeaderData("accept-encoding")) { if (auto *v = acph->getfirstValue()) acceptEncoding = v->getvalue(); } // No cross-encoding fallback here: if the client's negotiated preference isn't precomputed // for this file (compression yielded nothing -- doesn't happen in practice, but if it did), // falling back to the OTHER encoding could serve it to a client that never declared support // for it (e.g. "Accept-Encoding: gzip" only -- negotiate() then never returns Brotli, but a // blind fallback would still reach for CompressedBrotli). Serving raw is always safe. Encoding wanted = negotiate(acceptEncoding); if (wanted == Encoding::Brotli && !file.CompressedBrotli.empty()) return Encoding::Brotli; if (wanted == Encoding::Gzip && !file.CompressedGzip.empty()) return Encoding::Gzip; return Encoding::None; } bool blogi::Theme::Controller(const int tid,libhttppp::HttpRequest &req){ // Helper lambda for converting a string to lowercase auto to_lower = [](const std::string& str) { Loading Loading @@ -403,24 +445,18 @@ bool blogi::Theme::Controller(const int tid,libhttppp::HttpRequest &req){ resp.setContentType("application/octet-stream"); } bool compressed = false; libhttppp::HttpHeader::HeaderData *acph=req.getHeaderData("accept-encoding"); // Check if client supports Brotli ("br") if(acph){ for(libhttppp::HttpHeader::HeaderData::Values *cdat=acph->getfirstValue(); cdat; cdat=cdat->nextvalue()){ if(to_lower(cdat->getvalue()).find("br")!=std::string::npos){ compressed=true; break; } } } // Final Header Setup: Set Content-Encoding and Content-Length if( curfile.Compressed.length() > 0 && compressed ){ resp.setHeaderData("content-encoding")->push_back("br"); resp.setContentLength(curfile.Compressed.length()); // Final Header Setup: Set Content-Encoding and Content-Length. selectEncoding() // is also called by Response() below for the same request -- keeping the // decision in one place means the header/Content-Length set here can never // disagree with which bytes actually get streamed. using libhttppp::compression::Encoding; Encoding enc = selectEncoding(req, curfile); if (enc != Encoding::None) { const std::string &blob = (enc == Encoding::Brotli) ? curfile.CompressedBrotli : curfile.CompressedGzip; resp.setHeaderData("content-encoding")->push_back(libhttppp::compression::toToken(enc)); resp.setHeaderData("vary")->push_back("Accept-Encoding"); resp.setContentLength(blob.length()); resp.send(req,"",-1); }else{ // Send uncompressed/minified content Loading Loading @@ -464,19 +500,14 @@ bool blogi::Theme::Response(const int tid, libhttppp::HttpRequest &req){ auto &curfile = _PublicFiles[it->second]; // --- Compression Acceptance Check --- bool acpt_bz = false; libhttppp::HttpHeader::HeaderData *acph = req.getHeaderData("accept-encoding"); if (acph) { for(libhttppp::HttpHeader::HeaderData::Values *cdat = acph->getfirstValue(); cdat; cdat = cdat->nextvalue()){ if(to_lower(cdat->getvalue()).find("br") != std::string::npos){ acpt_bz = true; break; } } } // Same negotiation as Controller() -- must agree with it on which bytes are being sent, // since Controller already committed to a Content-Length/Content-Encoding for this // response before Response() ever runs. using libhttppp::compression::Encoding; Encoding enc = selectEncoding(req, curfile); const std::string &src = (enc == Encoding::Brotli) ? curfile.CompressedBrotli : (enc == Encoding::Gzip) ? curfile.CompressedGzip : curfile.Content; // Retrieve per-connection streaming state (set up in Controller) struct ThemeStreamState { size_t srcPos = 0; }; Loading @@ -484,23 +515,13 @@ bool blogi::Theme::Response(const int tid, libhttppp::HttpRequest &req){ if (!state) return false; // Stream data using direct pointer access instead of substr copies if( curfile.Compressed.length() > 0 && acpt_bz){ if(state->srcPos < curfile.Compressed.length()){ size_t remaining = curfile.Compressed.length() - state->srcPos; if (state->srcPos < src.length()) { size_t remaining = src.length() - state->srcPos; size_t len = BLOCKSIZE < remaining ? BLOCKSIZE : remaining; req.SendData.append(curfile.Compressed.c_str() + state->srcPos, len); req.SendData.append(src.c_str() + state->srcPos, len); state->srcPos += len; return true; } }else{ if(state->srcPos < curfile.Content.length()){ size_t remaining = curfile.Content.length() - state->srcPos; size_t len = BLOCKSIZE < remaining ? BLOCKSIZE : remaining; req.SendData.append(curfile.Content.c_str() + state->srcPos, len); state->srcPos += len; return true; } } return false; } Loading src/theme.h +11 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <netplus/connection.h> #include <httppp/http.h> #include <httppp/httpcompression.h> #include <htmlpp/html.h> #include <dbpp/database.h> Loading @@ -57,7 +58,8 @@ namespace blogi { std::string LookupKey; // pre-computed lowercase full path (prefix + path) std::string Content; std::string Ending; std::string Compressed; std::string CompressedGzip; std::string CompressedBrotli; int Type; }; Loading Loading @@ -103,6 +105,14 @@ namespace blogi { void registerCss(const std::string &publicPath, const std::string &variableName); private: /// Picks the best of the encodings actually precomputed for `file` against `req`'s /// Accept-Encoding (falls back to whichever precompressed blob exists if the client's /// top preference wasn't precomputed for this file, e.g. compression yielded nothing). /// Controller() and Response() both call this so their header/Content-Length decision /// and the bytes actually streamed can never disagree. libhttppp::compression::Encoding selectEncoding(libhttppp::HttpRequest &req, const ThemeFiles &file) const; ThemeConfig _Config; std::vector<ThemeFiles> _PublicFiles; std::unordered_map<std::string, size_t> _PublicFileIndex; // lowercase path -> index Loading tls_roundtrip_sha256_test.bin 0 → 100644 +4 MiB File added.No diff preview for this file type. View file Loading
src/compress.h +21 −55 Original line number Diff line number Diff line #include <algorithm> #include <string> #include <cstring> #include <brotli/encode.h> #include <httppp/http.h> #pragma once inline void compress(const std::string& in, std::string& out) { size_t compressed_size = BrotliEncoderMaxCompressedSize(in.length()); unsigned char *buf = new unsigned char[compressed_size]; size_t actual_compressed_len = compressed_size; BROTLI_BOOL result = BrotliEncoderCompress( BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_TEXT, in.length(), (const unsigned char*)in.c_str(), &actual_compressed_len, // buf ); if (result != BROTLI_TRUE) { out.clear(); delete[] buf; return; } out.resize(actual_compressed_len); std::memcpy(out.data(), buf, actual_compressed_len); #include <string> #include <vector> delete[] buf; } #include <httppp/http.h> #include <httppp/httpcompression.h> inline void sendCompressed(libhttppp::HttpRequest &req, libhttppp::HttpResponse &resp, const char *body, size_t bodylen) { bool accept_br = false; libhttppp::HttpHeader::HeaderData *acph = req.getHeaderData("accept-encoding"); if (acph) { for (auto *cdat = acph->getfirstValue(); cdat; cdat = cdat->nextvalue()) { std::string val = cdat->getvalue(); std::transform(val.begin(), val.end(), val.begin(), [](unsigned char c) { return std::tolower(c); }); if (val.find("br") != std::string::npos) { accept_br = true; break; } } } if (accept_br && bodylen > 0) { std::string in(body, bodylen); std::string compressed; compress(in, compressed); if (!compressed.empty()) { resp.setHeaderData("content-encoding")->push_back("br"); using namespace libhttppp::compression; std::string acceptEncoding; if (auto *acph = req.getHeaderData("accept-encoding")) { if (auto *v = acph->getfirstValue()) acceptEncoding = v->getvalue(); } Encoding wanted = negotiate(acceptEncoding); if (wanted != Encoding::None && bodylen > 0) { std::vector<char> in(body, body + bodylen); std::vector<char> compressed; bool ok = (wanted == Encoding::Brotli) ? brotliCompress(in, compressed) : gzipCompress(in, compressed); if (ok && !compressed.empty()) { resp.setHeaderData("content-encoding")->push_back(toToken(wanted)); resp.setHeaderData("vary")->push_back("Accept-Encoding"); resp.send(req, reinterpret_cast<const unsigned char *>(compressed.data()), static_cast<int>(compressed.length())); static_cast<int>(compressed.size())); return; } } Loading
src/theme.cpp +71 −50 Original line number Diff line number Diff line Loading @@ -38,14 +38,36 @@ #include <httppp/exception.h> #include <httppp/http.h> #include <httppp/httpcompression.h> #include <htmlpp/exception.h> #include "theme.h" #include "conf.h" #include "compress.h" #include "i18n.h" namespace { // Precomputes both encodings once at theme-load time so per-request serving (theme.cpp's // Controller/Response pair) never has to compress on the hot path. An encoding that fails // (shouldn't happen in practice) is left empty -- selectEncoding() treats that the same as // "not worth serving compressed" and falls back to the raw content. void precompressContent(const std::string &content, std::string &outGzip, std::string &outBrotli) { std::vector<char> in(content.begin(), content.end()); std::vector<char> gz; if (libhttppp::compression::gzipCompress(in, gz)) { outGzip.assign(gz.begin(), gz.end()); } std::vector<char> br; if (libhttppp::compression::brotliCompress(in, br)) { outBrotli.assign(br.begin(), br.end()); } } } // namespace blogi::Theme::Theme(blogi::ThemeConfig& config,bool formated) : _Config(config){ auto trimlines = [&] (const std::string &in){ std::string out; Loading Loading @@ -172,12 +194,12 @@ blogi::Theme::Theme(blogi::ThemeConfig& config,bool formated) : _Config(config){ } else if (tfile.Ending == "css" || tfile.Ending == "html") { tfile.Content = trimlines(buf); // Apply minification compress(tfile.Content, tfile.Compressed); // Compress content precompressContent(tfile.Content, tfile.CompressedGzip, tfile.CompressedBrotli); tfile.Type = ThemeFilesTypes::TEXT; } else if (tfile.Ending == "js") { tfile.Content = trimlines(buf); // Apply minification compress(tfile.Content, tfile.Compressed); // Compress content precompressContent(tfile.Content, tfile.CompressedGzip, tfile.CompressedBrotli); tfile.Type = ThemeFilesTypes::JAVASCRIPT; } else { Loading Loading @@ -355,6 +377,26 @@ void blogi::Theme::renderPage(const int tid,const char *name,libhtmlpp::HtmlPage page.loadString(index,data.data()); // Load processed HTML content } libhttppp::compression::Encoding blogi::Theme::selectEncoding(libhttppp::HttpRequest &req, const ThemeFiles &file) const { using namespace libhttppp::compression; std::string acceptEncoding; if (auto *acph = req.getHeaderData("accept-encoding")) { if (auto *v = acph->getfirstValue()) acceptEncoding = v->getvalue(); } // No cross-encoding fallback here: if the client's negotiated preference isn't precomputed // for this file (compression yielded nothing -- doesn't happen in practice, but if it did), // falling back to the OTHER encoding could serve it to a client that never declared support // for it (e.g. "Accept-Encoding: gzip" only -- negotiate() then never returns Brotli, but a // blind fallback would still reach for CompressedBrotli). Serving raw is always safe. Encoding wanted = negotiate(acceptEncoding); if (wanted == Encoding::Brotli && !file.CompressedBrotli.empty()) return Encoding::Brotli; if (wanted == Encoding::Gzip && !file.CompressedGzip.empty()) return Encoding::Gzip; return Encoding::None; } bool blogi::Theme::Controller(const int tid,libhttppp::HttpRequest &req){ // Helper lambda for converting a string to lowercase auto to_lower = [](const std::string& str) { Loading Loading @@ -403,24 +445,18 @@ bool blogi::Theme::Controller(const int tid,libhttppp::HttpRequest &req){ resp.setContentType("application/octet-stream"); } bool compressed = false; libhttppp::HttpHeader::HeaderData *acph=req.getHeaderData("accept-encoding"); // Check if client supports Brotli ("br") if(acph){ for(libhttppp::HttpHeader::HeaderData::Values *cdat=acph->getfirstValue(); cdat; cdat=cdat->nextvalue()){ if(to_lower(cdat->getvalue()).find("br")!=std::string::npos){ compressed=true; break; } } } // Final Header Setup: Set Content-Encoding and Content-Length if( curfile.Compressed.length() > 0 && compressed ){ resp.setHeaderData("content-encoding")->push_back("br"); resp.setContentLength(curfile.Compressed.length()); // Final Header Setup: Set Content-Encoding and Content-Length. selectEncoding() // is also called by Response() below for the same request -- keeping the // decision in one place means the header/Content-Length set here can never // disagree with which bytes actually get streamed. using libhttppp::compression::Encoding; Encoding enc = selectEncoding(req, curfile); if (enc != Encoding::None) { const std::string &blob = (enc == Encoding::Brotli) ? curfile.CompressedBrotli : curfile.CompressedGzip; resp.setHeaderData("content-encoding")->push_back(libhttppp::compression::toToken(enc)); resp.setHeaderData("vary")->push_back("Accept-Encoding"); resp.setContentLength(blob.length()); resp.send(req,"",-1); }else{ // Send uncompressed/minified content Loading Loading @@ -464,19 +500,14 @@ bool blogi::Theme::Response(const int tid, libhttppp::HttpRequest &req){ auto &curfile = _PublicFiles[it->second]; // --- Compression Acceptance Check --- bool acpt_bz = false; libhttppp::HttpHeader::HeaderData *acph = req.getHeaderData("accept-encoding"); if (acph) { for(libhttppp::HttpHeader::HeaderData::Values *cdat = acph->getfirstValue(); cdat; cdat = cdat->nextvalue()){ if(to_lower(cdat->getvalue()).find("br") != std::string::npos){ acpt_bz = true; break; } } } // Same negotiation as Controller() -- must agree with it on which bytes are being sent, // since Controller already committed to a Content-Length/Content-Encoding for this // response before Response() ever runs. using libhttppp::compression::Encoding; Encoding enc = selectEncoding(req, curfile); const std::string &src = (enc == Encoding::Brotli) ? curfile.CompressedBrotli : (enc == Encoding::Gzip) ? curfile.CompressedGzip : curfile.Content; // Retrieve per-connection streaming state (set up in Controller) struct ThemeStreamState { size_t srcPos = 0; }; Loading @@ -484,23 +515,13 @@ bool blogi::Theme::Response(const int tid, libhttppp::HttpRequest &req){ if (!state) return false; // Stream data using direct pointer access instead of substr copies if( curfile.Compressed.length() > 0 && acpt_bz){ if(state->srcPos < curfile.Compressed.length()){ size_t remaining = curfile.Compressed.length() - state->srcPos; if (state->srcPos < src.length()) { size_t remaining = src.length() - state->srcPos; size_t len = BLOCKSIZE < remaining ? BLOCKSIZE : remaining; req.SendData.append(curfile.Compressed.c_str() + state->srcPos, len); req.SendData.append(src.c_str() + state->srcPos, len); state->srcPos += len; return true; } }else{ if(state->srcPos < curfile.Content.length()){ size_t remaining = curfile.Content.length() - state->srcPos; size_t len = BLOCKSIZE < remaining ? BLOCKSIZE : remaining; req.SendData.append(curfile.Content.c_str() + state->srcPos, len); state->srcPos += len; return true; } } return false; } Loading
src/theme.h +11 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <netplus/connection.h> #include <httppp/http.h> #include <httppp/httpcompression.h> #include <htmlpp/html.h> #include <dbpp/database.h> Loading @@ -57,7 +58,8 @@ namespace blogi { std::string LookupKey; // pre-computed lowercase full path (prefix + path) std::string Content; std::string Ending; std::string Compressed; std::string CompressedGzip; std::string CompressedBrotli; int Type; }; Loading Loading @@ -103,6 +105,14 @@ namespace blogi { void registerCss(const std::string &publicPath, const std::string &variableName); private: /// Picks the best of the encodings actually precomputed for `file` against `req`'s /// Accept-Encoding (falls back to whichever precompressed blob exists if the client's /// top preference wasn't precomputed for this file, e.g. compression yielded nothing). /// Controller() and Response() both call this so their header/Content-Length decision /// and the bytes actually streamed can never disagree. libhttppp::compression::Encoding selectEncoding(libhttppp::HttpRequest &req, const ThemeFiles &file) const; ThemeConfig _Config; std::vector<ThemeFiles> _PublicFiles; std::unordered_map<std::string, size_t> _PublicFileIndex; // lowercase path -> index Loading
tls_roundtrip_sha256_test.bin 0 → 100644 +4 MiB File added.No diff preview for this file type. View file