Commit f6f9e61f authored by jan.koester's avatar jan.koester
Browse files

test

parent ea2a538e
Loading
Loading
Loading
Loading
+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;
        }
    }
+71 −50
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -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) {
@@ -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
@@ -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; };
@@ -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;
}
+11 −1
Original line number Diff line number Diff line
@@ -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>
@@ -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;
    };

@@ -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
+4 MiB

File added.

No diff preview for this file type.