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 =
83 d->cacheDir.setPath(config
84 .value(u
"cache_directory"_s,
85 d->defaultConfig.value(u
"cache_directory"_s, _defaultCacheDir))
88 if (Q_UNLIKELY(!d->cacheDir.exists())) {
89 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
90 qCCritical(C_STATICCOMPRESSED)
91 <<
"Failed to create cache directory for compressed static files at" 92 << d->cacheDir.absolutePath();
97 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
101 .value(u
"mime_types"_s,
102 d->defaultConfig.value(u
"mime_types"_s,
103 u
"text/css,application/javascript,text/javascript"_s))
105 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
112 d->defaultConfig.value(u
"suffixes"_s, u
"js.map,css.map,min.js.map,min.css.map"_s))
114 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
117 d->checkPreCompressed = config
118 .
value(u
"check_pre_compressed"_s,
119 d->defaultConfig.value(u
"check_pre_compressed"_s,
true))
121 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
123 d->onTheFlyCompression = config
124 .value(u
"on_the_fly_compression"_s,
125 d->defaultConfig.value(u
"on_the_fly_compression"_s,
true))
127 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
129 QStringList supportedCompressions{u
"deflate"_s, u
"gzip"_s};
130 d->loadZlibConfig(config);
132 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 133 d->loadZopfliConfig(config);
134 qCInfo(C_STATICCOMPRESSED) <<
"Use Zopfli:" << d->useZopfli;
137 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 138 d->loadBrotliConfig(config);
139 supportedCompressions << u
"br"_s;
142 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 143 if (Q_UNLIKELY(!d->loadZstdConfig(config))) {
146 supportedCompressions << u
"zstd"_s;
150 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 153 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 161 .
value(u
"compression_format_order"_s,
162 d->defaultConfig.value(u
"compression_format_order"_s,
163 defaultCompressionFormatOrder.join(u
',')))
166 if (Q_UNLIKELY(_compressionFormatOrder.
empty())) {
167 _compressionFormatOrder = defaultCompressionFormatOrder;
168 qCWarning(C_STATICCOMPRESSED)
169 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 170 "containing supported values. Using default value" 171 << defaultCompressionFormatOrder.
join(u
',');
173 for (
const auto &cfo : std::as_const(_compressionFormatOrder)) {
175 if (supportedCompressions.contains(order)) {
176 d->compressionFormatOrder << order;
179 if (Q_UNLIKELY(d->compressionFormatOrder.empty())) {
180 d->compressionFormatOrder = defaultCompressionFormatOrder;
181 qCWarning(C_STATICCOMPRESSED)
182 <<
"Invalid or empty value for compression_format_order. Has to be a string list " 183 "containing supported values. Using default value" 184 << defaultCompressionFormatOrder.join(u
',');
187 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
188 qCInfo(C_STATICCOMPRESSED) <<
"Compression format order:" 189 << d->compressionFormatOrder.join(u
',');
190 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
193 d->beforePrepareAction(c, skipMethod);
199 void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
208 for (
const QString &dir : std::as_const(dirs)) {
210 if (!locateCompressedFile(c, path)) {
214 res->
setBody(u
"File not found: "_s + path);
228 if (match.
hasMatch() && locateCompressedFile(c, path)) {
233 bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const 235 for (
const QDir &includePath : includePaths) {
236 qCDebug(C_STATICCOMPRESSED)
237 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
238 const QString path = includePath.absoluteFilePath(relPath);
240 if (fileInfo.exists()) {
242 const QDateTime currentDateTime = fileInfo.lastModified();
262 _mimeTypeName =
"application/json"_ba;
269 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
271 for (
const QString &format : std::as_const(compressionFormatOrder)) {
272 if (!acceptEncoding.contains(format.toLatin1())) {
275 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 277 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
278 if (compressedPath.
isEmpty()) {
281 qCDebug(C_STATICCOMPRESSED)
282 <<
"Serving brotli compressed data from" << compressedPath;
283 contentEncoding =
"br"_ba;
288 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 290 compressedPath = locateCacheFile(path, currentDateTime, Zstd);
291 if (compressedPath.
isEmpty()) {
294 qCDebug(C_STATICCOMPRESSED)
295 <<
"Serving zstd compressed data from" << compressedPath;
296 contentEncoding =
"zstd"_ba;
302 compressedPath = locateCacheFile(
303 path, currentDateTime, useZopfli ? ZopfliGzip : Gzip);
304 if (compressedPath.
isEmpty()) {
307 qCDebug(C_STATICCOMPRESSED)
308 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
309 <<
"compressed gzip data from" << compressedPath;
310 contentEncoding =
"gzip"_ba;
314 compressedPath = locateCacheFile(
315 path, currentDateTime, useZopfli ? ZopfliDeflate : Deflate);
316 if (compressedPath.
isEmpty()) {
319 qCDebug(C_STATICCOMPRESSED)
320 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
321 <<
"compressed deflate data from" << compressedPath;
322 contentEncoding =
"deflate"_ba;
334 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
342 if (!_mimeTypeName.
isEmpty()) {
344 }
else if (mimeType.
isValid()) {
353 if (!contentEncoding.
isEmpty()) {
357 qCDebug(C_STATICCOMPRESSED)
359 <<
"Original Size:" << fileInfo.size();
362 headers.
pushHeader(
"Vary"_ba,
"Accept-Encoding"_ba);
368 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
374 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
378 QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
380 Compression compression)
const 386 switch (compression) {
391 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 396 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 403 suffix = u
".deflate"_s;
406 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
410 if (checkPreCompressed) {
411 const QFileInfo origCompressed(origPath + suffix);
412 if (origCompressed.exists()) {
413 compressedPath = origCompressed.absoluteFilePath();
414 return compressedPath;
418 if (onTheFlyCompression) {
420 const QString path = cacheDir.absoluteFilePath(
426 if (info.exists() && (info.lastModified() > origLastModified)) {
427 compressedPath = path;
430 if (lock.tryLock(std::chrono::milliseconds{10})) {
431 switch (compression) {
432 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 434 if (compressZstd(origPath, path)) {
435 compressedPath = path;
439 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 441 if (compressBrotli(origPath, path)) {
442 compressedPath = path;
447 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 448 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_GZIP)) {
449 compressedPath = path;
454 if (compressGzip(origPath, path, origLastModified)) {
455 compressedPath = path;
459 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 460 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_ZLIB)) {
461 compressedPath = path;
466 if (compressDeflate(origPath, path)) {
467 compressedPath = path;
478 return compressedPath;
481 void StaticCompressedPrivate::loadZlibConfig(
const QVariantMap &conf)
484 zlib.compressionLevel =
485 conf.value(u
"zlib_compression_level"_s,
486 defaultConfig.value(u
"zlib_compression_level"_s, zlib.compressionLevelDefault))
489 if (!ok || zlib.compressionLevel < zlib.compressionLevelMin ||
490 zlib.compressionLevel > zlib.compressionLevelMax) {
491 qCWarning(C_STATICCOMPRESSED).nospace()
492 <<
"Invalid value set for zlib_compression_level. Value hat to be between " 493 << zlib.compressionLevelMin <<
" and " << zlib.compressionLevelMax
494 <<
" inclusive. Using default value " << zlib.compressionLevelDefault;
495 zlib.compressionLevel = zlib.compressionLevelDefault;
499 static constexpr std::array<quint32, 256> crc32Tab = []() {
500 std::array<quint32, 256> tab{0};
501 for (std::size_t n = 0; n < 256; n++) {
502 auto c =
static_cast<quint32
>(n);
503 for (
int k = 0; k < 8; k++) {
505 c = 0xedb88320L ^ (c >> 1);
515 quint32 updateCRC32(
unsigned char ch, quint32 crc)
518 return crc32Tab[(crc ^ ch) & 0xff] ^ (crc >> 8);
523 return ~
std::accumulate(data.
begin(),
526 [](quint32 oldcrc32,
char buf) {
527 return updateCRC32(static_cast<unsigned char>(buf), oldcrc32);
531 bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
535 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
537 QFile input(inputPath);
539 qCWarning(C_STATICCOMPRESSED)
540 <<
"Can not open input file to compress with gzip:" << inputPath;
545 if (Q_UNLIKELY(data.
isEmpty())) {
546 qCWarning(C_STATICCOMPRESSED)
547 <<
"Can not read input file or input file is empty:" << inputPath;
552 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
555 QFile output(outputPath);
557 qCWarning(C_STATICCOMPRESSED)
558 <<
"Can not open output file to compress with gzip:" << outputPath;
562 if (Q_UNLIKELY(compressedData.
isEmpty())) {
563 qCWarning(C_STATICCOMPRESSED)
564 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
565 if (output.exists()) {
566 if (Q_UNLIKELY(!output.remove())) {
567 qCWarning(C_STATICCOMPRESSED)
568 <<
"Can not remove invalid compressed gzip file:" << outputPath;
576 compressedData.
remove(0, 6);
577 compressedData.
chop(4);
583 headerStream << quint8(0x1f) << quint8(0x8b)
588 #if defined Q_OS_UNIX 590 #elif defined Q_OS_MACOS 592 #elif defined Q_OS_WIN 601 auto crc = crc32buf(data);
602 auto inSize = data.
size();
605 footerStream << static_cast<quint8>(crc % 256) << static_cast<quint8>((crc >> 8) % 256)
606 << static_cast<quint8>((crc >> 16) % 256) << static_cast<quint8>((crc >> 24) % 256)
607 << static_cast<quint8>(inSize % 256) <<
static_cast<quint8
>((inSize >> 8) % 256)
608 <<
static_cast<quint8
>((inSize >> 16) % 256)
609 <<
static_cast<quint8
>((inSize >> 24) % 256);
611 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
612 qCCritical(C_STATICCOMPRESSED).nospace()
613 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
620 bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
621 const QString &outputPath)
const 623 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
625 QFile input(inputPath);
627 qCWarning(C_STATICCOMPRESSED)
628 <<
"Can not open input file to compress with deflate:" << inputPath;
633 if (Q_UNLIKELY(data.
isEmpty())) {
634 qCWarning(C_STATICCOMPRESSED)
635 <<
"Can not read input file or input file is empty:" << inputPath;
640 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
643 QFile output(outputPath);
645 qCWarning(C_STATICCOMPRESSED)
646 <<
"Can not open output file to compress with deflate:" << outputPath;
650 if (Q_UNLIKELY(compressedData.
isEmpty())) {
651 qCWarning(C_STATICCOMPRESSED)
652 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
653 if (output.exists()) {
654 if (Q_UNLIKELY(!output.remove())) {
655 qCWarning(C_STATICCOMPRESSED)
656 <<
"Can not remove invalid compressed deflate file:" << outputPath;
663 compressedData.
remove(0, 4);
665 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
666 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file " 667 << inputPath <<
": " << output.errorString();
674 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 675 void StaticCompressedPrivate::loadZopfliConfig(
const QVariantMap &conf)
677 useZopfli = conf.value(u
"use_zopfli"_s, defaultConfig.value(u
"use_zopfli"_s,
false)).toBool();
679 ZopfliInitOptions(&zopfli.options);
681 zopfli.options.numiterations =
682 conf.value(u
"zopfli_iterations"_s,
683 defaultConfig.value(u
"zopfli_iterations"_s, zopfli.iterationsDefault))
685 if (!ok || zopfli.options.numiterations < zopfli.iterationsMin) {
686 qCWarning(C_STATICCOMPRESSED).nospace()
687 <<
"Invalid value set for zopfli_iterations. Value has to to be an integer value " 688 "greater than or equal to " 689 << zopfli.iterationsMin <<
". Using default value " << zopfli.iterationsDefault;
690 zopfli.options.numiterations = zopfli.iterationsDefault;
695 bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
697 ZopfliFormat format)
const 699 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
701 QFile input(inputPath);
703 qCWarning(C_STATICCOMPRESSED)
704 <<
"Can not open input file to compress with zopfli:" << inputPath;
709 if (Q_UNLIKELY(data.
isEmpty())) {
710 qCWarning(C_STATICCOMPRESSED)
711 <<
"Can not read input file or input file is empty:" << inputPath;
717 unsigned char *out{
nullptr};
720 ZopfliCompress(&zopfli.options,
722 reinterpret_cast<const unsigned char *>(data.
constData()),
727 if (Q_UNLIKELY(outSize <= 0)) {
728 qCWarning(C_STATICCOMPRESSED)
729 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
734 QFile output{outputPath};
736 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
737 <<
"for zopfli compression:" << output.
errorString();
742 if (Q_UNLIKELY(output.write(reinterpret_cast<const char *>(out), outSize) < 0)) {
743 if (output.exists()) {
744 if (Q_UNLIKELY(!output.remove())) {
745 qCWarning(C_STATICCOMPRESSED)
746 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
749 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zopfli compressed data to output file" 750 << outputPath <<
":" << output.errorString();
761 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 762 void StaticCompressedPrivate::loadBrotliConfig(
const QVariantMap &conf)
765 brotli.qualityLevel =
766 conf.value(u
"brotli_quality_level"_s,
767 defaultConfig.value(u
"brotli_quality_level"_s, brotli.qualityLevelDefault))
770 if (!ok || brotli.qualityLevel < BROTLI_MIN_QUALITY ||
771 brotli.qualityLevel > BROTLI_MAX_QUALITY) {
772 qCWarning(C_STATICCOMPRESSED).nospace()
773 <<
"Invalid value for brotli_quality_level. " 774 "Has to be an integer value between " 775 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
776 <<
" inclusive. Using default value " << brotli.qualityLevelDefault;
777 brotli.qualityLevel = brotli.qualityLevelDefault;
781 bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
782 const QString &outputPath)
const 784 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
786 QFile input(inputPath);
788 qCWarning(C_STATICCOMPRESSED)
789 <<
"Can not open input file to compress with brotli:" << inputPath;
794 if (Q_UNLIKELY(data.
isEmpty())) {
795 qCWarning(C_STATICCOMPRESSED)
796 <<
"Can not read input file or input file is empty:" << inputPath;
802 size_t outSize = BrotliEncoderMaxCompressedSize(static_cast<size_t>(data.
size()));
803 if (Q_UNLIKELY(outSize == 0)) {
804 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size" 805 << data.
size() <<
"with brotli";
808 QByteArray outData{
static_cast<qsizetype
>(outSize), Qt::Uninitialized};
810 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
811 auto out =
reinterpret_cast<uint8_t *
>(outData.data());
813 const BROTLI_BOOL status = BrotliEncoderCompress(brotli.qualityLevel,
814 BROTLI_DEFAULT_WINDOW,
820 if (Q_UNLIKELY(status != BROTLI_TRUE)) {
821 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
825 outData.
resize(static_cast<qsizetype>(outSize));
827 QFile output{outputPath};
829 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
830 <<
"for brotli compression:" << output.
errorString();
834 if (Q_UNLIKELY(output.write(outData) < 0)) {
835 if (output.exists()) {
836 if (Q_UNLIKELY(!output.remove())) {
837 qCWarning(C_STATICCOMPRESSED)
838 <<
"Can not remove invalid compressed brotli file:" << outputPath;
841 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write brotli compressed data to output file" 842 << outputPath <<
":" << output.errorString();
850 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD 851 bool StaticCompressedPrivate::loadZstdConfig(
const QVariantMap &conf)
853 zstd.ctx = ZSTD_createCCtx();
855 qCCritical(C_STATICCOMPRESSED) <<
"Failed to create Zstandard compression context";
861 zstd.compressionLevel =
862 conf.value(u
"zstd_compression_level"_s,
863 defaultConfig.value(u
"zstd_compression_level"_s, zstd.compressionLevelDefault))
865 if (!ok || zstd.compressionLevel < ZSTD_minCLevel() ||
866 zstd.compressionLevel > ZSTD_maxCLevel()) {
867 qCWarning(C_STATICCOMPRESSED).nospace()
868 <<
"Invalid value for zstd_compression_level. Has to be an integer value between " 869 << ZSTD_minCLevel() <<
" and " << ZSTD_maxCLevel() <<
" inclusive. Using default value " 870 << zstd.compressionLevelDefault;
871 zstd.compressionLevel = zstd.compressionLevelDefault;
877 bool StaticCompressedPrivate::compressZstd(
const QString &inputPath,
878 const QString &outputPath)
const 880 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zstd to" << outputPath;
882 QFile input{inputPath};
884 qCWarning(C_STATICCOMPRESSED)
885 <<
"Can not open input file to compress with zstd:" << inputPath;
890 if (Q_UNLIKELY(inData.
isEmpty())) {
891 qCWarning(C_STATICCOMPRESSED)
892 <<
"Can not read input file or input file is empty:" << inputPath;
898 const size_t outBufSize = ZSTD_compressBound(static_cast<size_t>(inData.
size()));
899 if (Q_UNLIKELY(ZSTD_isError(outBufSize) == 1)) {
900 qCWarning(C_STATICCOMPRESSED)
901 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outBufSize);
904 QByteArray outData{
static_cast<qsizetype
>(outBufSize), Qt::Uninitialized};
906 auto outDataP =
static_cast<void *
>(outData.data());
907 auto inDataP =
static_cast<const void *
>(inData.
constData());
909 const size_t outSize = ZSTD_compressCCtx(
910 zstd.ctx, outDataP, outBufSize, inDataP, inData.
size(), zstd.compressionLevel);
911 if (Q_UNLIKELY(ZSTD_isError(outSize) == 1)) {
912 qCWarning(C_STATICCOMPRESSED)
913 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outSize);
917 outData.resize(static_cast<qsizetype>(outSize));
919 QFile output{outputPath};
921 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
922 <<
"for zstd compression:" << output.
errorString();
926 if (Q_UNLIKELY(output.write(outData) < 0)) {
927 if (output.exists()) {
928 if (Q_UNLIKELY(!output.remove())) {
929 qCWarning(C_STATICCOMPRESSED)
930 <<
"Can not remove invalid compressed zstd file:" << outputPath;
933 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zstd compressed data to output file" 934 << outputPath <<
":" << output.errorString();
942 #include "moc_staticcompressed.cpp" QByteArray header(QByteArrayView key) const noexcept
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
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