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>
14#include <QCoreApplication>
15#include <QCryptographicHash>
20#include <QLoggingCategory>
21#include <QMimeDatabase>
22#include <QStandardPaths>
24#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
28#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
29# include <brotli/encode.h>
34Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
38 , d_ptr(new StaticCompressedPrivate)
41 d->includePaths.append(
parent->config(QStringLiteral(
"root")).toString());
51 d->includePaths.clear();
52 for (
const QString &path : paths) {
53 d->includePaths.append(
QDir(path));
67 const QVariantMap config =
68 app->
engine()->
config(QStringLiteral(
"Cutelyst_StaticCompressed_Plugin"));
70 d->logFailedIp = config.value(QStringLiteral(
"log_failed_ip"),
false).toBool();
72 const QString _defaultCacheDir =
76 config.value(QStringLiteral(
"cache_directory"), _defaultCacheDir).toString());
78 if (Q_UNLIKELY(!d->cacheDir.exists())) {
79 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
80 qCCritical(C_STATICCOMPRESSED,
81 "Failed to create cache directory for compressed static files at \"%s\".",
82 qPrintable(d->cacheDir.absolutePath()));
87 qCInfo(C_STATICCOMPRESSED,
88 "Compressed cache directory: %s",
89 qPrintable(d->cacheDir.absolutePath()));
93 .value(QStringLiteral(
"mime_types"), QStringLiteral(
"text/css,application/javascript"))
95 qCInfo(C_STATICCOMPRESSED,
"MIME Types: %s", qPrintable(_mimeTypes));
98 const QString _suffixes = config
99 .value(QStringLiteral(
"suffixes"),
100 QStringLiteral(
"js.map,css.map,min.js.map,min.css.map"))
102 qCInfo(C_STATICCOMPRESSED,
"Suffixes: %s", qPrintable(_suffixes));
105 d->checkPreCompressed = config.value(QStringLiteral(
"check_pre_compressed"),
true).toBool();
106 qCInfo(C_STATICCOMPRESSED,
107 "Check for pre-compressed files: %s",
108 d->checkPreCompressed ?
"true" :
"false");
110 d->onTheFlyCompression = config.value(QStringLiteral(
"on_the_fly_compression"),
true).toBool();
111 qCInfo(C_STATICCOMPRESSED,
112 "Compress static files on the fly: %s",
113 d->onTheFlyCompression ?
"true" :
"false");
115 QStringList supportedCompressions{QStringLiteral(
"deflate"), QStringLiteral(
"gzip")};
118 d->zlibCompressionLevel = config.value(QStringLiteral(
"zlib_compression_level"), 9).toInt(&ok);
119 if (!ok || (d->zlibCompressionLevel < -1) || (d->zlibCompressionLevel > 9)) {
120 d->zlibCompressionLevel = -1;
123#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
124 d->zopfliIterations = config.value(QStringLiteral(
"zopfli_iterations"), 15).toInt(&ok);
125 if (!ok || (d->zopfliIterations < 0)) {
126 d->zopfliIterations = 15;
128 d->useZopfli = config.value(QStringLiteral(
"use_zopfli"),
false).toBool();
129 supportedCompressions << QStringLiteral(
"zopfli");
132#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
133 d->brotliQualityLevel =
134 config.value(QStringLiteral(
"brotli_quality_level"), BROTLI_DEFAULT_QUALITY).toInt(&ok);
135 if (!ok || (d->brotliQualityLevel < BROTLI_MIN_QUALITY) ||
136 (d->brotliQualityLevel > BROTLI_MAX_QUALITY)) {
137 d->brotliQualityLevel = BROTLI_DEFAULT_QUALITY;
139 supportedCompressions << QStringLiteral(
"brotli");
142 qCInfo(C_STATICCOMPRESSED,
143 "Supported compressions: %s",
144 qPrintable(supportedCompressions.
join(u
',')));
147 d->beforePrepareAction(c, skipMethod);
153void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
159 const QString path = c->req()->path();
162 for (
const QString &dir : dirs) {
164 if (!locateCompressedFile(c, path)) {
168 res->
setBody(QStringLiteral(
"File not found: ") + path);
176 const QRegularExpressionMatch match = _re.
match(path);
177 if (match.
hasMatch() && locateCompressedFile(c, path)) {
182bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
184 for (
const QDir &includePath : includePaths) {
185 const QString path = includePath.absoluteFilePath(relPath);
186 const QFileInfo fileInfo(path);
187 if (fileInfo.exists()) {
189 const QDateTime currentDateTime = fileInfo.lastModified();
195 static QMimeDatabase db;
198 QString contentEncoding;
199 QString compressedPath;
200 QString _mimeTypeName;
209 _mimeTypeName = QStringLiteral(
"application/json");
216 const QString acceptEncoding =
217 c->req()->
header(QStringLiteral(
"Accept-Encoding"));
218 qCDebug(C_STATICCOMPRESSED) <<
"Accept-Encoding:" << acceptEncoding;
220#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
222 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
223 if (!compressedPath.
isEmpty()) {
224 qCDebug(C_STATICCOMPRESSED,
225 "Serving brotli compressed data from \"%s\".",
226 qPrintable(compressedPath));
227 contentEncoding = QStringLiteral(
"br");
233 locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
234 if (!compressedPath.
isEmpty()) {
235 qCDebug(C_STATICCOMPRESSED,
236 "Serving %s compressed data from \"%s\".",
237 useZopfli ?
"zopfli" :
"gzip",
238 qPrintable(compressedPath));
239 contentEncoding = QStringLiteral(
"gzip");
241 }
else if (acceptEncoding.
contains(QLatin1String(
"deflate"),
243 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
244 if (!compressedPath.
isEmpty()) {
245 qCDebug(C_STATICCOMPRESSED,
246 "Serving deflate compressed data from \"%s\".",
247 qPrintable(compressedPath));
248 contentEncoding = QStringLiteral(
"deflate");
254 QFile *file = !compressedPath.
isEmpty() ?
new QFile(compressedPath) : new QFile(path);
256 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
257 Headers &headers = res->headers();
264 if (!_mimeTypeName.isEmpty()) {
265 headers.setContentType(_mimeTypeName);
266 }
else if (mimeType.
isValid()) {
267 headers.setContentType(mimeType.name());
269 headers.setContentLength(file->
size());
271 headers.setLastModified(currentDateTime);
273 headers.setHeader(QStringLiteral(
"CACHE_CONTROL"), QStringLiteral(
"public"));
275 if (!contentEncoding.
isEmpty()) {
277 headers.setContentEncoding(contentEncoding);
280 headers.pushHeader(QStringLiteral(
"Vary"), QStringLiteral(
"Accept-Encoding"));
286 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
291 if (C_STATICCOMPRESSED().isWarningEnabled()) {
293 qCWarning(C_STATICCOMPRESSED).nospace().noquote()
294 <<
"File not found: \"" << relPath <<
'"' <<
" [client "
297 qCWarning(C_STATICCOMPRESSED).nospace().noquote()
298 <<
"File not found: \"" << relPath <<
'"';
305QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
306 const QDateTime &origLastModified,
307 Compression compression)
const
309 QString compressedPath;
313 switch (compression) {
316 suffix = QStringLiteral(
".gz");
318#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
320 suffix = QStringLiteral(
".br");
324 suffix = QStringLiteral(
".deflate");
327 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
331 if (checkPreCompressed) {
332 const QFileInfo origCompressed(origPath + suffix);
333 if (origCompressed.exists()) {
334 compressedPath = origCompressed.absoluteFilePath();
335 return compressedPath;
339 if (onTheFlyCompression) {
341 const QString path = cacheDir.absoluteFilePath(
345 const QFileInfo info(path);
347 if (info.exists() && (info.lastModified() > origLastModified)) {
348 compressedPath = path;
350 QLockFile lock(path + QLatin1String(
".lock"));
351 if (lock.tryLock(10)) {
352 switch (compression) {
353#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
355 if (compressBrotli(origPath, path)) {
356 compressedPath = path;
361#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
362 if (compressZopfli(origPath, path)) {
363 compressedPath = path;
368 if (compressGzip(origPath, path, origLastModified)) {
369 compressedPath = path;
373 if (compressDeflate(origPath, path)) {
374 compressedPath = path;
385 return compressedPath;
389static const quint32 crc_32_tab[] = {
390 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
391 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
392 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
393 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
394 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
395 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
396 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
397 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
398 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
399 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
400 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
401 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
402 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
403 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
404 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
405 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
406 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
407 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
408 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
409 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
410 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
411 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
412 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
413 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
414 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
415 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
416 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
417 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
418 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
419 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
420 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
421 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
422 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
423 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
424 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
425 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
426 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
427 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
428 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
429 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
430 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
431 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
432 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
436quint32 updateCRC32(
unsigned char ch, quint32 crc)
438 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
441quint32 crc32buf(
const QByteArray &data)
443 return ~std::accumulate(data.
begin(),
446 [](quint32 oldcrc32,
char buf) {
return updateCRC32(buf, oldcrc32); });
449bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
450 const QString &outputPath,
451 const QDateTime &origLastModified)
const
453 qCDebug(C_STATICCOMPRESSED,
454 "Compressing \"%s\" with gzip to \"%s\".",
455 qPrintable(inputPath),
456 qPrintable(outputPath));
458 QFile input(inputPath);
460 qCWarning(C_STATICCOMPRESSED)
461 <<
"Can not open input file to compress with gzip:" << inputPath;
465 const QByteArray data = input.readAll();
466 if (Q_UNLIKELY(data.
isEmpty())) {
467 qCWarning(C_STATICCOMPRESSED)
468 <<
"Can not read input file or input file is empty:" << inputPath;
473 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
476 QFile output(outputPath);
478 qCWarning(C_STATICCOMPRESSED)
479 <<
"Can not open output file to compress with gzip:" << outputPath;
483 if (Q_UNLIKELY(compressedData.
isEmpty())) {
484 qCWarning(C_STATICCOMPRESSED)
485 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
486 if (output.exists()) {
487 if (Q_UNLIKELY(!output.remove())) {
488 qCWarning(C_STATICCOMPRESSED)
489 <<
"Can not remove invalid compressed gzip file:" << outputPath;
497 compressedData.
remove(0, 6);
498 compressedData.
chop(4);
503 headerStream << quint16(0x1f8b) << quint16(0x0800)
507#elif defined Q_OS_WIN
509#elif defined Q_OS_MACOS
519 footerStream << crc32buf(data) << quint32(data.
size());
521 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
522 qCCritical(C_STATICCOMPRESSED,
523 "Failed to write compressed gzip file \"%s\": %s",
524 qPrintable(inputPath),
525 qPrintable(output.errorString()));
532bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
533 const QString &outputPath)
const
535 qCDebug(C_STATICCOMPRESSED,
536 "Compressing \"%s\" with deflate to \"%s\".",
537 qPrintable(inputPath),
538 qPrintable(outputPath));
540 QFile input(inputPath);
542 qCWarning(C_STATICCOMPRESSED)
543 <<
"Can not open input file to compress with deflate:" << inputPath;
547 const QByteArray data = input.readAll();
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, zlibCompressionLevel);
558 QFile output(outputPath);
560 qCWarning(C_STATICCOMPRESSED)
561 <<
"Can not open output file to compress with deflate:" << outputPath;
565 if (Q_UNLIKELY(compressedData.
isEmpty())) {
566 qCWarning(C_STATICCOMPRESSED)
567 <<
"Failed to compress file with deflate, 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 deflate file:" << outputPath;
579 compressedData.
remove(0, 6);
580 compressedData.
chop(4);
582 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
583 qCCritical(C_STATICCOMPRESSED,
584 "Failed to write compressed deflate file \"%s\": %s",
585 qPrintable(inputPath),
586 qPrintable(output.errorString()));
593#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
594bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
595 const QString &outputPath)
const
597 qCDebug(C_STATICCOMPRESSED,
598 "Compressing \"%s\" with zopfli to \"%s\".",
599 qPrintable(inputPath),
600 qPrintable(outputPath));
602 QFile input(inputPath);
604 qCWarning(C_STATICCOMPRESSED)
605 <<
"Can not open input file to compress with zopfli:" << inputPath;
609 const QByteArray data = input.readAll();
610 if (Q_UNLIKELY(data.
isEmpty())) {
611 qCWarning(C_STATICCOMPRESSED)
612 <<
"Can not read input file or input file is empty:" << inputPath;
617 ZopfliOptions options;
618 ZopfliInitOptions(&options);
619 options.numiterations = zopfliIterations;
621 unsigned char *out = 0;
624 ZopfliCompress(&options,
625 ZopfliFormat::ZOPFLI_FORMAT_GZIP,
626 reinterpret_cast<const unsigned char *
>(data.
constData()),
633 QFile output(outputPath);
635 qCWarning(C_STATICCOMPRESSED)
636 <<
"Can not open output file to compress with zopfli:" << outputPath;
638 if (Q_UNLIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
639 qCCritical(C_STATICCOMPRESSED,
640 "Failed to write compressed zopfli file \"%s\": %s",
641 qPrintable(inputPath),
642 qPrintable(output.errorString()));
643 if (output.exists()) {
644 if (Q_UNLIKELY(!output.remove())) {
645 qCWarning(C_STATICCOMPRESSED)
646 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
654 qCWarning(C_STATICCOMPRESSED)
655 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
664#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
665bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
666 const QString &outputPath)
const
668 qCDebug(C_STATICCOMPRESSED,
669 "Compressing \"%s\" with brotli to \"%s\".",
670 qPrintable(inputPath),
671 qPrintable(outputPath));
673 QFile input(inputPath);
675 qCWarning(C_STATICCOMPRESSED)
676 <<
"Can not open input file to compress with brotli:" << inputPath;
680 const QByteArray data = input.readAll();
681 if (Q_UNLIKELY(data.
isEmpty())) {
682 qCWarning(C_STATICCOMPRESSED)
683 <<
"Can not read input file or input file is empty:" << inputPath;
691 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.
size()));
692 if (Q_LIKELY(outSize > 0)) {
693 const uint8_t *in = (
const uint8_t *) data.
constData();
695 out = (uint8_t *) malloc(
sizeof(uint8_t) * (outSize + 1));
696 if (Q_LIKELY(out !=
nullptr)) {
697 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel,
698 BROTLI_DEFAULT_WINDOW,
704 if (Q_LIKELY(status == BROTLI_TRUE)) {
705 QFile output(outputPath);
707 if (Q_LIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) > -1)) {
712 "Failed to write brotli compressed data to output file \"%s\": %s",
713 qPrintable(outputPath),
714 qPrintable(output.errorString()));
715 if (output.exists()) {
716 if (Q_UNLIKELY(!output.remove())) {
717 qCWarning(C_STATICCOMPRESSED)
718 <<
"Can not remove invalid compressed brotli file:"
724 qCWarning(C_STATICCOMPRESSED,
725 "Failed to open output file for brotli compression: %s",
726 qPrintable(outputPath));
729 qCWarning(C_STATICCOMPRESSED,
730 "Failed to compress \"%s\" with brotli.",
731 qPrintable(inputPath));
735 qCWarning(C_STATICCOMPRESSED,
736 "Can not allocate needed output buffer of size %lu for brotli compression.",
737 sizeof(uint8_t) * (outSize + 1));
740 qCWarning(C_STATICCOMPRESSED,
741 "Needed output buffer too large to compress input of size %lu with brotli.",
742 static_cast<size_t>(data.
size()));
749#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
user configuration for the application
Plugin(Application *parent)
QString addressString() const
QString header(const QString &key) const
Headers headers() const noexcept
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
void setContentType(const QString &type)
virtual ~StaticCompressed() override
void setIncludePaths(const QStringList &paths)
void setDirs(const QStringList &dirs)
StaticCompressed(Application *parent)
virtual 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
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
bool hasMatch() const const
QString writableLocation(QStandardPaths::StandardLocation type)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() 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
QString join(QChar separator) const const