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>
22#include <QLoggingCategory>
23#include <QMimeDatabase>
24#include <QStandardPaths>
26#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
30#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
31# include <brotli/encode.h>
36Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
40 , d_ptr(new StaticCompressedPrivate)
43 d->includePaths.append(parent->config(u
"root"_qs).toString());
51 d->includePaths.clear();
52 for (
const QString &path : paths) {
53 d->includePaths.append(
QDir(path));
66 d->serveDirsOnly = dirsOnly;
73 const QVariantMap config = app->
engine()->
config(u
"Cutelyst_StaticCompressed_Plugin"_qs);
74 const QString _defaultCacheDir =
77 d->cacheDir.setPath(config
78 .value(u
"cache_directory"_qs,
79 d->defaultConfig.value(u
"cache_directory"_qs, _defaultCacheDir))
82 if (Q_UNLIKELY(!d->cacheDir.exists())) {
83 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
84 qCCritical(C_STATICCOMPRESSED)
85 <<
"Failed to create cache directory for compressed static files at"
86 << d->cacheDir.absolutePath();
91 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
95 .value(u
"mime_types"_qs,
96 d->defaultConfig.value(u
"mime_types"_qs,
97 u
"text/css,application/javascript,text/javascript"_qs))
99 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
106 d->defaultConfig.value(u
"suffixes"_qs, u
"js.map,css.map,min.js.map,min.css.map"_qs))
108 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
111 d->checkPreCompressed = config
112 .
value(u
"check_pre_compressed"_qs,
113 d->defaultConfig.value(u
"check_pre_compressed"_qs,
true))
115 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
117 d->onTheFlyCompression = config
118 .value(u
"on_the_fly_compression"_qs,
119 d->defaultConfig.value(u
"on_the_fly_compression"_qs,
true))
121 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
123 QStringList supportedCompressions{u
"deflate"_qs, u
"gzip"_qs};
126 d->zlibCompressionLevel =
128 .
value(u
"zlib_compression_level"_qs,
129 d->defaultConfig.value(u
"zlib_compression_level"_qs,
130 StaticCompressedPrivate::zlibCompressionLevelDefault))
133 qCWarning(C_STATICCOMPRESSED).nospace()
134 <<
"Invalid value set for zlib_compression_level. "
135 "Has to to be an integer value between "
136 << StaticCompressedPrivate::zlibCompressionLevelMin <<
" and "
137 << StaticCompressedPrivate::zlibCompressionLevelMax
138 <<
" inclusive. Using default value "
139 << StaticCompressedPrivate::zlibCompressionLevelDefault;
142 if (d->zlibCompressionLevel < StaticCompressedPrivate::zlibCompressionLevelMin ||
143 d->zlibCompressionLevel > StaticCompressedPrivate::zlibCompressionLevelMax) {
144 qCWarning(C_STATICCOMPRESSED).nospace()
145 <<
"Invalid value " << d->zlibCompressionLevel
146 <<
" set for zlib_compression_level. Value hat to be between "
147 << StaticCompressedPrivate::zlibCompressionLevelMin <<
" and "
148 << StaticCompressedPrivate::zlibCompressionLevelMax
149 <<
" inclusive. Using default value "
150 << StaticCompressedPrivate::zlibCompressionLevelDefault;
151 d->zlibCompressionLevel = StaticCompressedPrivate::zlibCompressionLevelDefault;
154#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
155 supportedCompressions << u
"zopfli"_qs;
157 config.value(u
"use_zopfli"_qs, d->defaultConfig.value(u
"use_zopfli"_qs,
false)).toBool();
159 d->zopfliIterations =
161 .value(u
"zopfli_iterations"_qs,
162 d->defaultConfig.value(u
"zopfli_iterations"_qs,
163 StaticCompressedPrivate::zopfliIterationsDefault))
166 qCWarning(C_STATICCOMPRESSED).nospace()
167 <<
"Invalid value for zopfli_iterations. "
168 "Has to be an integer value greater than or equal to "
169 << StaticCompressedPrivate::zopfliIterationsMin <<
". Using default value "
170 << StaticCompressedPrivate::zopfliIterationsDefault;
171 d->zopfliIterations = StaticCompressedPrivate::zopfliIterationsDefault;
174 if (d->zopfliIterations < StaticCompressedPrivate::zopfliIterationsMin) {
175 qCWarning(C_STATICCOMPRESSED).nospace()
176 <<
"Invalid value " << d->zopfliIterations
177 <<
" set for zopfli_iterations. Value has to to be greater than or equal to "
178 << StaticCompressedPrivate::zopfliIterationsMin <<
". Using default value "
179 << StaticCompressedPrivate::zopfliIterationsDefault;
180 d->zopfliIterations = StaticCompressedPrivate::zopfliIterationsDefault;
185#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
186 d->brotliQualityLevel =
188 .value(u
"brotli_quality_level"_qs,
189 d->defaultConfig.value(u
"brotli_quality_level"_qs,
190 StaticCompressedPrivate::brotliQualityLevelDefault))
193 qCWarning(C_STATICCOMPRESSED).nospace()
194 <<
"Invalid value for brotli_quality_level. "
195 "Has to be an integer value between "
196 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
197 <<
" inclusive. Using default value "
198 << StaticCompressedPrivate::brotliQualityLevelDefault;
199 d->brotliQualityLevel = StaticCompressedPrivate::brotliQualityLevelDefault;
202 if (d->brotliQualityLevel < BROTLI_MIN_QUALITY || d->brotliQualityLevel > BROTLI_MAX_QUALITY) {
203 qCWarning(C_STATICCOMPRESSED).nospace()
204 <<
"Invalid value " << d->brotliQualityLevel
205 <<
" set for brotli_quality_level. Value has to be between " << BROTLI_MIN_QUALITY
206 <<
" and " << BROTLI_MAX_QUALITY <<
" inclusive. Using default value "
207 << StaticCompressedPrivate::brotliQualityLevelDefault;
208 d->brotliQualityLevel = StaticCompressedPrivate::brotliQualityLevelDefault;
210 supportedCompressions << u
"brotli"_qs;
213 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
214 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
223void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
232 for (
const QString &dir : dirs) {
234 if (!locateCompressedFile(c, path)) {
238 res->
setBody(u
"File not found: "_qs + path);
252 if (match.
hasMatch() && locateCompressedFile(c, path)) {
257bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
259 for (
const QDir &includePath : includePaths) {
260 qCDebug(C_STATICCOMPRESSED)
261 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
262 const QString path = includePath.absoluteFilePath(relPath);
264 if (fileInfo.exists()) {
266 const QDateTime currentDateTime = fileInfo.lastModified();
286 _mimeTypeName =
"application/json"_qba;
293 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
295#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
296 if (acceptEncoding.contains(
"br")) {
297 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
298 if (!compressedPath.
isEmpty()) {
299 qCDebug(C_STATICCOMPRESSED)
300 <<
"Serving brotli compressed data from" << compressedPath;
301 contentEncoding =
"br"_qba;
305 if (acceptEncoding.contains(
"gzip")) {
307 locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
308 if (!compressedPath.
isEmpty()) {
309 qCDebug(C_STATICCOMPRESSED)
310 <<
"Serving" << (useZopfli ?
"zopfli" :
"gzip")
311 <<
"compressed data from" << compressedPath;
312 contentEncoding =
"gzip"_qba;
314 }
else if (acceptEncoding.contains(
"deflate")) {
315 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
316 if (!compressedPath.
isEmpty()) {
317 qCDebug(C_STATICCOMPRESSED)
318 <<
"Serving deflate compressed data from" << compressedPath;
319 contentEncoding =
"deflate"_qba;
329 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
337 if (!_mimeTypeName.
isEmpty()) {
339 }
else if (mimeType.
isValid()) {
348 if (!contentEncoding.
isEmpty()) {
352 qCDebug(C_STATICCOMPRESSED)
354 <<
"Original Size:" << fileInfo.size();
357 headers.
pushHeader(
"Vary"_qba,
"Accept-Encoding"_qba);
363 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
369 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
373QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
375 Compression compression)
const
381 switch (compression) {
386#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
392 suffix = u
".deflate"_qs;
395 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
399 if (checkPreCompressed) {
400 const QFileInfo origCompressed(origPath + suffix);
401 if (origCompressed.exists()) {
402 compressedPath = origCompressed.absoluteFilePath();
403 return compressedPath;
407 if (onTheFlyCompression) {
409 const QString path = cacheDir.absoluteFilePath(
415 if (info.exists() && (info.lastModified() > origLastModified)) {
416 compressedPath = path;
419 if (lock.tryLock(std::chrono::milliseconds{10})) {
420 switch (compression) {
421#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
423 if (compressBrotli(origPath, path)) {
424 compressedPath = path;
429#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
430 if (compressZopfli(origPath, path)) {
431 compressedPath = path;
436 if (compressGzip(origPath, path, origLastModified)) {
437 compressedPath = path;
441 if (compressDeflate(origPath, path)) {
442 compressedPath = path;
453 return compressedPath;
457static constexpr std::array<quint32, 256> crc_32_tab {
458 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
459 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
460 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
461 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
462 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
463 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
464 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
465 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
466 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
467 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
468 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
469 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
470 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
471 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
472 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
473 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
474 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
475 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
476 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
477 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
478 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
479 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
480 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
481 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
482 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
483 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
484 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
485 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
486 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
487 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
488 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
489 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
490 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
491 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
492 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
493 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
494 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
495 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
496 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
497 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
498 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
499 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
500 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
504quint32 updateCRC32(
unsigned char ch, quint32 crc)
507 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
512 return ~std::accumulate(data.
begin(),
515 [](quint32 oldcrc32,
char buf) {
return updateCRC32(buf, oldcrc32); });
518bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
522 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
524 QFile input(inputPath);
526 qCWarning(C_STATICCOMPRESSED)
527 <<
"Can not open input file to compress with gzip:" << inputPath;
532 if (Q_UNLIKELY(data.
isEmpty())) {
533 qCWarning(C_STATICCOMPRESSED)
534 <<
"Can not read input file or input file is empty:" << inputPath;
539 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
542 QFile output(outputPath);
544 qCWarning(C_STATICCOMPRESSED)
545 <<
"Can not open output file to compress with gzip:" << outputPath;
549 if (Q_UNLIKELY(compressedData.
isEmpty())) {
550 qCWarning(C_STATICCOMPRESSED)
551 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
552 if (output.exists()) {
553 if (Q_UNLIKELY(!output.remove())) {
554 qCWarning(C_STATICCOMPRESSED)
555 <<
"Can not remove invalid compressed gzip file:" << outputPath;
563 compressedData.
remove(0, 6);
564 compressedData.
chop(4);
570 headerStream << quint16(0x1f8b) << quint16(0x0800)
574#elif defined Q_OS_WIN
576#elif defined Q_OS_MACOS
587 footerStream << crc32buf(data) << quint32(data.
size());
589 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
590 qCCritical(C_STATICCOMPRESSED).nospace()
591 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
598bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
599 const QString &outputPath)
const
601 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
603 QFile input(inputPath);
605 qCWarning(C_STATICCOMPRESSED)
606 <<
"Can not open input file to compress with deflate:" << inputPath;
611 if (Q_UNLIKELY(data.
isEmpty())) {
612 qCWarning(C_STATICCOMPRESSED)
613 <<
"Can not read input file or input file is empty:" << inputPath;
618 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
621 QFile output(outputPath);
623 qCWarning(C_STATICCOMPRESSED)
624 <<
"Can not open output file to compress with deflate:" << outputPath;
628 if (Q_UNLIKELY(compressedData.
isEmpty())) {
629 qCWarning(C_STATICCOMPRESSED)
630 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
631 if (output.exists()) {
632 if (Q_UNLIKELY(!output.remove())) {
633 qCWarning(C_STATICCOMPRESSED)
634 <<
"Can not remove invalid compressed deflate file:" << outputPath;
642 compressedData.
remove(0, 6);
643 compressedData.
chop(4);
645 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
646 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file "
647 << inputPath <<
": " << output.errorString();
654#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
655bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
656 const QString &outputPath)
const
658 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
660 QFile input(inputPath);
662 qCWarning(C_STATICCOMPRESSED)
663 <<
"Can not open input file to compress with zopfli:" << inputPath;
668 if (Q_UNLIKELY(data.
isEmpty())) {
669 qCWarning(C_STATICCOMPRESSED)
670 <<
"Can not read input file or input file is empty:" << inputPath;
675 ZopfliOptions options;
676 ZopfliInitOptions(&options);
677 options.numiterations = zopfliIterations;
679 unsigned char *out{
nullptr};
682 ZopfliCompress(&options,
683 ZopfliFormat::ZOPFLI_FORMAT_GZIP,
684 reinterpret_cast<const unsigned char *
>(data.
constData()),
691 QFile output(outputPath);
693 qCWarning(C_STATICCOMPRESSED)
694 <<
"Can not open output file to compress with zopfli:" << outputPath;
696 if (Q_UNLIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
697 qCCritical(C_STATICCOMPRESSED).nospace()
698 <<
"Failed to write compressed zopfi file " << inputPath <<
": "
699 << output.errorString();
700 if (output.exists()) {
701 if (Q_UNLIKELY(!output.remove())) {
702 qCWarning(C_STATICCOMPRESSED)
703 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
711 qCWarning(C_STATICCOMPRESSED)
712 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
721#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
722bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
723 const QString &outputPath)
const
725 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
727 QFile input(inputPath);
729 qCWarning(C_STATICCOMPRESSED)
730 <<
"Can not open input file to compress with brotli:" << inputPath;
735 if (Q_UNLIKELY(data.
isEmpty())) {
736 qCWarning(C_STATICCOMPRESSED)
737 <<
"Can not read input file or input file is empty:" << inputPath;
745 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.
size()));
746 if (Q_LIKELY(outSize > 0)) {
747 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
748 auto out =
static_cast<uint8_t *
>(malloc(
sizeof(uint8_t) * (outSize + 1)));
749 if (Q_LIKELY(out !=
nullptr)) {
750 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel,
751 BROTLI_DEFAULT_WINDOW,
757 if (Q_LIKELY(status == BROTLI_TRUE)) {
758 QFile output(outputPath);
760 if (Q_LIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) > -1)) {
763 qCWarning(C_STATICCOMPRESSED).nospace()
764 <<
"Failed to write brotli compressed data to output file "
765 << outputPath <<
": " << output.errorString();
766 if (output.exists()) {
767 if (Q_UNLIKELY(!output.remove())) {
768 qCWarning(C_STATICCOMPRESSED)
769 <<
"Can not remove invalid compressed brotli file:"
775 qCWarning(C_STATICCOMPRESSED)
776 <<
"Failed to open output file for brotli compression:" << outputPath;
779 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
783 qCWarning(C_STATICCOMPRESSED)
784 <<
"Can not allocate needed output buffer of size"
785 << (
sizeof(uint8_t) * (outSize + 1)) <<
"for brotli compression.";
788 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size"
789 << data.
size() <<
"with brotli";
796#include "moc_staticcompressed.cpp"
The Cutelyst application.
Engine * engine() const noexcept
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
Response * res() const noexcept
Response * response() const noexcept
QVariantMap config(const QString &entity) const
Base class for Cutelyst Plugins.
Headers headers() const noexcept
QByteArray header(QByteArrayView key) const noexcept
void setContentType(const QByteArray &type)
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
Headers & headers() noexcept
Serve static files compressed on the fly or pre-compressed.
~StaticCompressed() override
void setServeDirsOnly(bool dirsOnly)
void setIncludePaths(const QStringList &paths)
void setDirs(const QStringList &dirs)
bool setup(Application *app) override
The Cutelyst namespace holds all public Cutelyst API.
QByteArray::iterator begin()
const char * constData() const const
QByteArray::iterator end()
bool isEmpty() const const
QByteArray & remove(qsizetype pos, qsizetype len)
qsizetype size() const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, QCryptographicHash::Algorithm method)
qint64 toSecsSinceEpoch() const const
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
virtual qint64 size() const const override
QString errorString() const const
T value(qsizetype i) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
bool hasMatch() const const
QString writableLocation(QStandardPaths::StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const