6 #include "staticcompressed_p.h" 8 #include <Cutelyst/Application> 9 #include <Cutelyst/Context> 10 #include <Cutelyst/Engine> 11 #include <Cutelyst/Request> 12 #include <Cutelyst/Response> 16 #include <QCoreApplication> 17 #include <QCryptographicHash> 18 #include <QDataStream> 22 #include <QLoggingCategory> 23 #include <QMimeDatabase> 24 #include <QStandardPaths> 26 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 27 # include <brotli/encode.h> 33 Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
35 StaticCompressed::StaticCompressed(
Application *parent)
37 , d_ptr(new StaticCompressedPrivate)
40 d->includePaths.append(
parent->config(u
"root"_s).toString());
43 StaticCompressed::StaticCompressed(
Application *parent,
const QVariantMap &defaultConfig)
45 , d_ptr(new StaticCompressedPrivate)
48 d->includePaths.append(
parent->config(u
"root"_s).toString());
49 d->defaultConfig = defaultConfig;
52 StaticCompressed::~StaticCompressed() =
default;
54 void StaticCompressed::setIncludePaths(
const QStringList &paths)
57 d->includePaths.clear();
58 for (
const QString &path : paths) {
59 d->includePaths.append(
QDir(path));
63 void StaticCompressed::setDirs(
const QStringList &dirs)
69 void StaticCompressed::setServeDirsOnly(
bool dirsOnly)
72 d->serveDirsOnly = dirsOnly;
79 const QVariantMap config = app->
engine()->
config(u
"Cutelyst_StaticCompressed_Plugin"_s);
80 const QString _defaultCacheDir =
82 d->cacheDir.setPath(config
83 .value(u
"cache_directory"_s,
84 d->defaultConfig.value(u
"cache_directory"_s, _defaultCacheDir))
87 if (Q_UNLIKELY(!d->cacheDir.exists())) {
88 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
89 qCCritical(C_STATICCOMPRESSED)
90 <<
"Failed to create cache directory for compressed static files at" 91 << d->cacheDir.absolutePath();
96 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
100 .value(u
"mime_types"_s,
101 d->defaultConfig.value(u
"mime_types"_s,
102 u
"text/css,application/javascript,text/javascript"_s))
104 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
111 d->defaultConfig.value(u
"suffixes"_s, u
"js.map,css.map,min.js.map,min.css.map"_s))
113 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
116 d->checkPreCompressed = config
117 .
value(u
"check_pre_compressed"_s,
118 d->defaultConfig.value(u
"check_pre_compressed"_s,
true))
120 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
122 d->onTheFlyCompression = config
123 .value(u
"on_the_fly_compression"_s,
124 d->defaultConfig.value(u
"on_the_fly_compression"_s,
true))
126 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
128 QStringList supportedCompressions{u
"deflate"_s, u
"gzip"_s};
129 d->loadZlibConfig(config);
131 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 132 d->loadZopfliConfig(config);
133 qCInfo(C_STATICCOMPRESSED) <<
"Use Zopfli:" << d->useZopfli;
136 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 137 d->loadBrotliConfig(config);
138 supportedCompressions << u
"br"_s;
141 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 142 if (Q_UNLIKELY(!d->loadZstdConfig(config))) {
145 supportedCompressions << u
"zstd"_s;
149 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 152 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 160 .
value(u
"compression_format_order"_s,
161 d->defaultConfig.value(u
"compression_format_order"_s,
162 defaultCompressionFormatOrder.join(u
',')))
165 if (Q_UNLIKELY(_compressionFormatOrder.
empty())) {
166 _compressionFormatOrder = defaultCompressionFormatOrder;
167 qCWarning(C_STATICCOMPRESSED)
168 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 169 "containing supported values. Using default value" 170 << defaultCompressionFormatOrder.
join(u
',');
172 for (
const auto &cfo : std::as_const(_compressionFormatOrder)) {
174 if (supportedCompressions.contains(order)) {
175 d->compressionFormatOrder << order;
178 if (Q_UNLIKELY(d->compressionFormatOrder.empty())) {
179 d->compressionFormatOrder = defaultCompressionFormatOrder;
180 qCWarning(C_STATICCOMPRESSED)
181 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 182 "containing supported values. Using default value" 183 << defaultCompressionFormatOrder.join(u
',');
186 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
187 qCInfo(C_STATICCOMPRESSED) <<
"Compression format order:" 188 << d->compressionFormatOrder.join(u
',');
189 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
192 d->beforePrepareAction(c, skipMethod);
198 void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
207 bool found = std::ranges::any_of(dirs, [&](
const QString &dir) {
209 if (!locateCompressedFile(c, path)) {
213 res->
setBody(u
"File not found: "_s + path);
231 if (match.
hasMatch() && locateCompressedFile(c, path)) {
236 bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const 238 for (
const QDir &includePath : includePaths) {
239 qCDebug(C_STATICCOMPRESSED)
240 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
241 const QString path = includePath.absoluteFilePath(relPath);
243 if (fileInfo.exists()) {
245 const QDateTime currentDateTime = fileInfo.lastModified();
265 _mimeTypeName =
"application/json"_ba;
272 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
274 for (
const QString &format : std::as_const(compressionFormatOrder)) {
275 if (!acceptEncoding.contains(format.toLatin1())) {
278 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 279 if (format == u
"br") {
280 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
281 if (compressedPath.
isEmpty()) {
284 qCDebug(C_STATICCOMPRESSED)
285 <<
"Serving brotli compressed data from" << compressedPath;
286 contentEncoding =
"br"_ba;
291 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 292 if (format == u
"zstd") {
293 compressedPath = locateCacheFile(path, currentDateTime, Zstd);
294 if (compressedPath.
isEmpty()) {
297 qCDebug(C_STATICCOMPRESSED)
298 <<
"Serving zstd compressed data from" << compressedPath;
299 contentEncoding =
"zstd"_ba;
304 if (format == u
"gzip") {
305 compressedPath = locateCacheFile(
306 path, currentDateTime, useZopfli ? ZopfliGzip : Gzip);
307 if (compressedPath.
isEmpty()) {
310 qCDebug(C_STATICCOMPRESSED)
311 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
312 <<
"compressed gzip data from" << compressedPath;
313 contentEncoding =
"gzip"_ba;
316 }
else if (format == u
"deflate") {
317 compressedPath = locateCacheFile(
318 path, currentDateTime, useZopfli ? ZopfliDeflate : Deflate);
319 if (compressedPath.
isEmpty()) {
322 qCDebug(C_STATICCOMPRESSED)
323 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
324 <<
"compressed deflate data from" << compressedPath;
325 contentEncoding =
"deflate"_ba;
337 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
345 if (!_mimeTypeName.
isEmpty()) {
347 }
else if (mimeType.
isValid()) {
356 if (!contentEncoding.
isEmpty()) {
360 qCDebug(C_STATICCOMPRESSED)
362 <<
"Original Size:" << fileInfo.size();
365 headers.
pushHeader(
"Vary"_ba,
"Accept-Encoding"_ba);
371 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
377 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
381 QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
383 Compression compression)
const 389 switch (compression) {
394 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 399 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 406 suffix = u
".deflate"_s;
409 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
413 if (checkPreCompressed) {
414 const QFileInfo origCompressed(origPath + suffix);
415 if (origCompressed.exists()) {
416 compressedPath = origCompressed.absoluteFilePath();
417 return compressedPath;
421 if (onTheFlyCompression) {
423 const QString path = cacheDir.absoluteFilePath(
429 if (info.exists() && (info.lastModified() > origLastModified)) {
430 compressedPath = path;
433 if (lock.tryLock(std::chrono::milliseconds{10})) {
434 switch (compression) {
435 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 437 if (compressZstd(origPath, path)) {
438 compressedPath = path;
442 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 444 if (compressBrotli(origPath, path)) {
445 compressedPath = path;
450 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 451 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_GZIP)) {
452 compressedPath = path;
457 if (compressGzip(origPath, path, origLastModified)) {
458 compressedPath = path;
462 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 463 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_ZLIB)) {
464 compressedPath = path;
469 if (compressDeflate(origPath, path)) {
470 compressedPath = path;
481 return compressedPath;
484 void StaticCompressedPrivate::loadZlibConfig(
const QVariantMap &conf)
487 zlib.compressionLevel =
488 conf.value(u
"zlib_compression_level"_s,
489 defaultConfig.value(u
"zlib_compression_level"_s, zlib.compressionLevelDefault))
492 if (!ok || zlib.compressionLevel < zlib.compressionLevelMin ||
493 zlib.compressionLevel > zlib.compressionLevelMax) {
494 qCWarning(C_STATICCOMPRESSED).nospace()
495 <<
"Invalid value set for zlib_compression_level. Value hat to be between " 496 << zlib.compressionLevelMin <<
" and " << zlib.compressionLevelMax
497 <<
" inclusive. Using default value " << zlib.compressionLevelDefault;
498 zlib.compressionLevel = zlib.compressionLevelDefault;
502 static constexpr std::array<quint32, 256> crc32Tab = []() {
503 std::array<quint32, 256> tab{0};
504 for (std::size_t n = 0; n < 256; n++) {
505 auto c =
static_cast<quint32
>(n);
506 for (
int k = 0; k < 8; k++) {
508 c = 0xedb88320L ^ (c >> 1);
518 quint32 updateCRC32(
unsigned char ch, quint32 crc)
521 return crc32Tab[(crc ^ ch) & 0xff] ^ (crc >> 8);
526 return ~
std::accumulate(data.
begin(),
529 [](quint32 oldcrc32,
char buf) {
530 return updateCRC32(static_cast<unsigned char>(buf), oldcrc32);
534 bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
538 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
540 QFile input(inputPath);
542 qCWarning(C_STATICCOMPRESSED)
543 <<
"Can not open input file to compress with gzip:" << inputPath;
548 if (Q_UNLIKELY(data.
isEmpty())) {
549 qCWarning(C_STATICCOMPRESSED)
550 <<
"Can not read input file or input file is empty:" << inputPath;
555 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
558 QFile output(outputPath);
560 qCWarning(C_STATICCOMPRESSED)
561 <<
"Can not open output file to compress with gzip:" << outputPath;
565 if (Q_UNLIKELY(compressedData.
isEmpty())) {
566 qCWarning(C_STATICCOMPRESSED)
567 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
568 if (output.exists()) {
569 if (Q_UNLIKELY(!output.remove())) {
570 qCWarning(C_STATICCOMPRESSED)
571 <<
"Can not remove invalid compressed gzip file:" << outputPath;
579 compressedData.
remove(0, 6);
580 compressedData.
chop(4);
586 headerStream << quint8(0x1f) << quint8(0x8b)
591 #if defined Q_OS_UNIX 593 #elif defined Q_OS_MACOS 595 #elif defined Q_OS_WIN 604 auto crc = crc32buf(data);
605 auto inSize = data.
size();
608 footerStream << static_cast<quint8>(crc % 256) << static_cast<quint8>((crc >> 8) % 256)
609 << static_cast<quint8>((crc >> 16) % 256) << static_cast<quint8>((crc >> 24) % 256)
610 << static_cast<quint8>(inSize % 256) <<
static_cast<quint8
>((inSize >> 8) % 256)
611 <<
static_cast<quint8
>((inSize >> 16) % 256)
612 <<
static_cast<quint8
>((inSize >> 24) % 256);
614 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
615 qCCritical(C_STATICCOMPRESSED).nospace()
616 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
623 bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
624 const QString &outputPath)
const 626 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
628 QFile input(inputPath);
630 qCWarning(C_STATICCOMPRESSED)
631 <<
"Can not open input file to compress with deflate:" << inputPath;
636 if (Q_UNLIKELY(data.
isEmpty())) {
637 qCWarning(C_STATICCOMPRESSED)
638 <<
"Can not read input file or input file is empty:" << inputPath;
643 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
646 QFile output(outputPath);
648 qCWarning(C_STATICCOMPRESSED)
649 <<
"Can not open output file to compress with deflate:" << outputPath;
653 if (Q_UNLIKELY(compressedData.
isEmpty())) {
654 qCWarning(C_STATICCOMPRESSED)
655 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
656 if (output.exists()) {
657 if (Q_UNLIKELY(!output.remove())) {
658 qCWarning(C_STATICCOMPRESSED)
659 <<
"Can not remove invalid compressed deflate file:" << outputPath;
666 compressedData.
remove(0, 4);
668 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
669 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file " 670 << inputPath <<
": " << output.errorString();
677 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 678 void StaticCompressedPrivate::loadZopfliConfig(
const QVariantMap &conf)
680 useZopfli = conf.value(u
"use_zopfli"_s, defaultConfig.value(u
"use_zopfli"_s,
false)).toBool();
682 ZopfliInitOptions(&zopfli.options);
684 zopfli.options.numiterations =
685 conf.value(u
"zopfli_iterations"_s,
686 defaultConfig.value(u
"zopfli_iterations"_s, zopfli.iterationsDefault))
688 if (!ok || zopfli.options.numiterations < zopfli.iterationsMin) {
689 qCWarning(C_STATICCOMPRESSED).nospace()
690 <<
"Invalid value set for zopfli_iterations. Value has to to be an integer value " 691 "greater than or equal to " 692 << zopfli.iterationsMin <<
". Using default value " << zopfli.iterationsDefault;
693 zopfli.options.numiterations = zopfli.iterationsDefault;
698 bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
700 ZopfliFormat format)
const 702 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
704 QFile input(inputPath);
706 qCWarning(C_STATICCOMPRESSED)
707 <<
"Can not open input file to compress with zopfli:" << inputPath;
712 if (Q_UNLIKELY(data.
isEmpty())) {
713 qCWarning(C_STATICCOMPRESSED)
714 <<
"Can not read input file or input file is empty:" << inputPath;
720 unsigned char *out{
nullptr};
723 ZopfliCompress(&zopfli.options,
725 reinterpret_cast<const unsigned char *>(data.
constData()),
730 if (Q_UNLIKELY(outSize <= 0)) {
731 qCWarning(C_STATICCOMPRESSED)
732 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
737 QFile output{outputPath};
739 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
740 <<
"for zopfli compression:" << output.
errorString();
745 if (Q_UNLIKELY(output.write(reinterpret_cast<const char *>(out), outSize) < 0)) {
746 if (output.exists()) {
747 if (Q_UNLIKELY(!output.remove())) {
748 qCWarning(C_STATICCOMPRESSED)
749 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
752 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zopfli compressed data to output file" 753 << outputPath <<
":" << output.errorString();
764 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 765 void StaticCompressedPrivate::loadBrotliConfig(
const QVariantMap &conf)
768 brotli.qualityLevel =
769 conf.value(u
"brotli_quality_level"_s,
770 defaultConfig.value(u
"brotli_quality_level"_s, brotli.qualityLevelDefault))
773 if (!ok || brotli.qualityLevel < BROTLI_MIN_QUALITY ||
774 brotli.qualityLevel > BROTLI_MAX_QUALITY) {
775 qCWarning(C_STATICCOMPRESSED).nospace()
776 <<
"Invalid value for brotli_quality_level. " 777 "Has to be an integer value between " 778 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
779 <<
" inclusive. Using default value " << brotli.qualityLevelDefault;
780 brotli.qualityLevel = brotli.qualityLevelDefault;
784 bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
785 const QString &outputPath)
const 787 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
789 QFile input(inputPath);
791 qCWarning(C_STATICCOMPRESSED)
792 <<
"Can not open input file to compress with brotli:" << inputPath;
797 if (Q_UNLIKELY(data.
isEmpty())) {
798 qCWarning(C_STATICCOMPRESSED)
799 <<
"Can not read input file or input file is empty:" << inputPath;
805 size_t outSize = BrotliEncoderMaxCompressedSize(static_cast<size_t>(data.
size()));
806 if (Q_UNLIKELY(outSize == 0)) {
807 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size" 808 << data.
size() <<
"with brotli";
811 QByteArray outData{
static_cast<qsizetype
>(outSize), Qt::Uninitialized};
813 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
814 auto out =
reinterpret_cast<uint8_t *
>(outData.data());
816 const BROTLI_BOOL status = BrotliEncoderCompress(brotli.qualityLevel,
817 BROTLI_DEFAULT_WINDOW,
823 if (Q_UNLIKELY(status != BROTLI_TRUE)) {
824 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
828 outData.
resize(static_cast<qsizetype>(outSize));
830 QFile output{outputPath};
832 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
833 <<
"for brotli compression:" << output.
errorString();
837 if (Q_UNLIKELY(output.write(outData) < 0)) {
838 if (output.exists()) {
839 if (Q_UNLIKELY(!output.remove())) {
840 qCWarning(C_STATICCOMPRESSED)
841 <<
"Can not remove invalid compressed brotli file:" << outputPath;
844 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write brotli compressed data to output file" 845 << outputPath <<
":" << output.errorString();
853 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 854 bool StaticCompressedPrivate::loadZstdConfig(
const QVariantMap &conf)
856 zstd.ctx = ZSTD_createCCtx();
858 qCCritical(C_STATICCOMPRESSED) <<
"Failed to create Zstandard compression context";
864 zstd.compressionLevel =
865 conf.value(u
"zstd_compression_level"_s,
866 defaultConfig.value(u
"zstd_compression_level"_s, zstd.compressionLevelDefault))
868 if (!ok || zstd.compressionLevel < ZSTD_minCLevel() ||
869 zstd.compressionLevel > ZSTD_maxCLevel()) {
870 qCWarning(C_STATICCOMPRESSED).nospace()
871 <<
"Invalid value for zstd_compression_level. Has to be an integer value between " 872 << ZSTD_minCLevel() <<
" and " << ZSTD_maxCLevel() <<
" inclusive. Using default value " 873 << zstd.compressionLevelDefault;
874 zstd.compressionLevel = zstd.compressionLevelDefault;
880 bool StaticCompressedPrivate::compressZstd(
const QString &inputPath,
881 const QString &outputPath)
const 883 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zstd to" << outputPath;
885 QFile input{inputPath};
887 qCWarning(C_STATICCOMPRESSED)
888 <<
"Can not open input file to compress with zstd:" << inputPath;
893 if (Q_UNLIKELY(inData.
isEmpty())) {
894 qCWarning(C_STATICCOMPRESSED)
895 <<
"Can not read input file or input file is empty:" << inputPath;
901 const size_t outBufSize = ZSTD_compressBound(static_cast<size_t>(inData.
size()));
902 if (Q_UNLIKELY(ZSTD_isError(outBufSize) == 1)) {
903 qCWarning(C_STATICCOMPRESSED)
904 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outBufSize);
907 QByteArray outData{
static_cast<qsizetype
>(outBufSize), Qt::Uninitialized};
909 auto outDataP =
static_cast<void *
>(outData.data());
910 auto inDataP =
static_cast<const void *
>(inData.
constData());
912 const size_t outSize = ZSTD_compressCCtx(
913 zstd.ctx, outDataP, outBufSize, inDataP, inData.
size(), zstd.compressionLevel);
914 if (Q_UNLIKELY(ZSTD_isError(outSize) == 1)) {
915 qCWarning(C_STATICCOMPRESSED)
916 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outSize);
920 outData.resize(static_cast<qsizetype>(outSize));
922 QFile output{outputPath};
924 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
925 <<
"for zstd compression:" << output.
errorString();
929 if (Q_UNLIKELY(output.write(outData) < 0)) {
930 if (output.exists()) {
931 if (Q_UNLIKELY(!output.remove())) {
932 qCWarning(C_STATICCOMPRESSED)
933 <<
"Can not remove invalid compressed zstd file:" << outputPath;
936 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zstd compressed data to output file" 937 << outputPath <<
":" << output.errorString();
945 #include "moc_staticcompressed.cpp" QString writableLocation(StandardLocation type)
Serve static files compressed on the fly or pre-compressed.
Headers & headers() noexcept
QString errorString() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
Response * res() const noexcept
bool isEmpty() const const
QString join(QChar separator) const const
void setContentType(const QByteArray &type)
T value(qsizetype i) const const
Headers headers() const noexcept
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
QVariantMap config(const QString &entity) const
void resize(qsizetype newSize, QChar fillChar)
bool isEmpty() const const
QString trimmed() const const
const char * constData() const const
bool hasMatch() const const
The Cutelyst namespace holds all public Cutelyst API.
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
QString toLower() const const
virtual qint64 size() const const override
bool isValid() const const
QString fromLatin1(QByteArrayView str)
QString mid(qsizetype position, qsizetype n) const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QByteArray header(QAnyStringView key) const noexcept
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
qint64 toSecsSinceEpoch() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray hash(QByteArrayView data, Algorithm method)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
Base class for Cutelyst Plugins.
The Cutelyst application.
Engine * engine() const noexcept
void setBody(QIODevice *body)
qsizetype size() const const
QObject * parent() const const
Response * response() const noexcept
QByteArray & remove(qsizetype pos, qsizetype len)
void setStatus(quint16 status) noexcept
QByteArray toUtf8() const const