6 #include "staticcompressed_p.h" 8 #include <Cutelyst/Application> 9 #include <Cutelyst/Request> 10 #include <Cutelyst/Response> 11 #include <Cutelyst/Context> 12 #include <Cutelyst/Engine> 14 #include <QMimeDatabase> 17 #include <QStandardPaths> 18 #include <QCoreApplication> 19 #include <QCryptographicHash> 20 #include <QLoggingCategory> 21 #include <QDataStream> 24 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 28 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 29 #include <brotli/encode.h> 34 Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
37 Plugin(parent), d_ptr(new StaticCompressedPrivate)
40 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 = app->
engine()->
config(QStringLiteral(
"Cutelyst_StaticCompressed_Plugin"));
69 d->cacheDir.setPath(config.value(QStringLiteral(
"cache_directory"), _defaultCacheDir).toString());
71 if (Q_UNLIKELY(!d->cacheDir.exists())) {
72 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
73 qCCritical(C_STATICCOMPRESSED,
"Failed to create cache directory for compressed static files at \"%s\".", qPrintable(d->cacheDir.absolutePath()));
78 qCInfo(C_STATICCOMPRESSED,
"Compressed cache directory: %s", qPrintable(d->cacheDir.absolutePath()));
80 const QString _mimeTypes = config.value(QStringLiteral(
"mime_types"), QStringLiteral(
"text/css,application/javascript")).toString();
81 qCInfo(C_STATICCOMPRESSED,
"MIME Types: %s", qPrintable(_mimeTypes));
83 #
if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
86 QString::SkipEmptyParts);
89 const QString _suffixes = config.value(QStringLiteral(
"suffixes"), QStringLiteral(
"js.map,css.map,min.js.map,min.css.map")).toString();
90 qCInfo(C_STATICCOMPRESSED,
"Suffixes: %s", qPrintable(_suffixes));
92 #
if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
95 QString::SkipEmptyParts);
98 d->checkPreCompressed = config.
value(QStringLiteral(
"check_pre_compressed"),
true).toBool();
99 qCInfo(C_STATICCOMPRESSED,
"Check for pre-compressed files: %s", d->checkPreCompressed ?
"true" :
"false");
101 d->onTheFlyCompression = config.value(QStringLiteral(
"on_the_fly_compression"),
true).toBool();
102 qCInfo(C_STATICCOMPRESSED,
"Compress static files on the fly: %s", d->onTheFlyCompression ?
"true" :
"false");
104 QStringList supportedCompressions{QStringLiteral(
"deflate"), QStringLiteral(
"gzip")};
107 d->zlibCompressionLevel = config.
value(QStringLiteral(
"zlib_compression_level"), 9).toInt(&ok);
108 if (!ok || (d->zlibCompressionLevel < -1) || (d->zlibCompressionLevel > 9)) {
109 d->zlibCompressionLevel = -1;
112 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 113 d->zopfliIterations = config.value(QStringLiteral(
"zopfli_iterations"), 15).toInt(&ok);
114 if (!ok || (d->zopfliIterations < 0)) {
115 d->zopfliIterations = 15;
117 d->useZopfli = config.value(QStringLiteral(
"use_zopfli"),
false).toBool();
118 supportedCompressions << QStringLiteral(
"zopfli");
121 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 122 d->brotliQualityLevel = config.value(QStringLiteral(
"brotli_quality_level"), BROTLI_DEFAULT_QUALITY).toInt(&ok);
123 if (!ok || (d->brotliQualityLevel < BROTLI_MIN_QUALITY) || (d->brotliQualityLevel > BROTLI_MAX_QUALITY)) {
124 d->brotliQualityLevel = BROTLI_DEFAULT_QUALITY;
126 supportedCompressions << QStringLiteral(
"brotli");
129 qCInfo(C_STATICCOMPRESSED,
"Supported compressions: %s", qPrintable(supportedCompressions.join(
QLatin1Char(
','))));
132 d->beforePrepareAction(c, skipMethod);
138 void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
144 const QString path = c->req()->path();
147 for (
const QString &dir : dirs) {
149 if (!locateCompressedFile(c, path)) {
153 res->
setBody(QStringLiteral(
"File not found: ") + path);
162 if (match.
hasMatch() && locateCompressedFile(c, path)) {
167 bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const 169 for (
const QDir &includePath : includePaths) {
170 const QString path = includePath.absoluteFilePath(relPath);
172 if (fileInfo.exists()) {
174 const QDateTime currentDateTime = fileInfo.lastModified();
192 if (path.
endsWith(u
"css.map", Qt::CaseInsensitive) || path.
endsWith(u
"js.map", Qt::CaseInsensitive)) {
193 _mimeTypeName = QStringLiteral(
"application/json");
197 if (mimeTypes.contains(mimeType.
name(), Qt::CaseInsensitive) || suffixes.contains(fileInfo.completeSuffix(), Qt::CaseInsensitive)) {
199 const QString acceptEncoding = c->req()->
header(QStringLiteral(
"Accept-Encoding"));
200 qCDebug(C_STATICCOMPRESSED) <<
"Accept-Encoding:" << acceptEncoding;
202 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 204 compressedPath = locateCacheFile(path, currentDateTime, Brotli) ;
205 if (!compressedPath.
isEmpty()) {
206 qCDebug(C_STATICCOMPRESSED,
"Serving brotli compressed data from \"%s\".", qPrintable(compressedPath));
207 contentEncoding = QStringLiteral(
"br");
212 compressedPath = locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
213 if (!compressedPath.
isEmpty()) {
214 qCDebug(C_STATICCOMPRESSED,
"Serving %s compressed data from \"%s\".", useZopfli ?
"zopfli" :
"gzip", qPrintable(compressedPath));
215 contentEncoding = QStringLiteral(
"gzip");
218 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
219 if (!compressedPath.
isEmpty()) {
220 qCDebug(C_STATICCOMPRESSED,
"Serving deflate compressed data from \"%s\".", qPrintable(compressedPath));
221 contentEncoding = QStringLiteral(
"deflate");
229 if (file->
open(QFile::ReadOnly)) {
230 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
238 if (!_mimeTypeName.
isEmpty()) {
240 }
else if (mimeType.
isValid()) {
247 headers.
setHeader(QStringLiteral(
"CACHE_CONTROL"), QStringLiteral(
"public"));
249 if (!contentEncoding.
isEmpty()) {
254 headers.
pushHeader(QStringLiteral(
"Vary"), QStringLiteral(
"Accept-Encoding"));
260 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
265 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
269 QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
const QDateTime &origLastModified, Compression compression)
const 275 switch (compression) {
278 suffix = QStringLiteral(
".gz");
280 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 282 suffix = QStringLiteral(
".br");
286 suffix = QStringLiteral(
".deflate");
289 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
293 if (checkPreCompressed) {
294 const QFileInfo origCompressed(origPath + suffix);
295 if (origCompressed.exists()) {
296 compressedPath = origCompressed.absoluteFilePath();
297 return compressedPath;
301 if (onTheFlyCompression) {
306 if (info.exists() && (info.lastModified() > origLastModified)) {
307 compressedPath = path;
310 if (lock.tryLock(10)) {
311 switch (compression) {
312 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 314 if (compressBrotli(origPath, path)) {
315 compressedPath = path;
320 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 321 if (compressZopfli(origPath, path)) {
322 compressedPath = path;
327 if (compressGzip(origPath, path, origLastModified)) {
328 compressedPath = path;
332 if (compressDeflate(origPath, path)) {
333 compressedPath = path;
344 return compressedPath;
347 static const quint32 crc_32_tab[] = {
348 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
349 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
350 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
351 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
352 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
353 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
354 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
355 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
356 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
357 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
358 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
359 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
360 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
361 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
362 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
363 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
364 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
365 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
366 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
367 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
368 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
369 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
370 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
371 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
372 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
373 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
374 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
375 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
376 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
377 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
378 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
379 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
380 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
381 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
382 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
383 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
384 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
385 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
386 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
387 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
388 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
389 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
390 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
393 quint32 updateCRC32(
unsigned char ch, quint32 crc)
395 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
400 return ~std::accumulate(
404 [](quint32 oldcrc32,
char buf){
return updateCRC32(buf, oldcrc32); });
407 bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
const QString &outputPath,
const QDateTime &origLastModified)
const 409 qCDebug(C_STATICCOMPRESSED,
"Compressing \"%s\" with gzip to \"%s\".", qPrintable(inputPath), qPrintable(outputPath));
411 QFile input(inputPath);
412 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
413 qCWarning(C_STATICCOMPRESSED) <<
"Can not open input file to compress with gzip:" << inputPath;
418 if (Q_UNLIKELY(data.
isEmpty())) {
419 qCWarning(C_STATICCOMPRESSED) <<
"Can not read input file or input file is empty:" << inputPath;
424 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
427 QFile output(outputPath);
428 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
429 qCWarning(C_STATICCOMPRESSED) <<
"Can not open output file to compress with gzip:" << outputPath;
433 if (Q_UNLIKELY(compressedData.
isEmpty())) {
434 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
435 if (output.exists()) {
436 if (Q_UNLIKELY(!output.remove())) {
437 qCWarning(C_STATICCOMPRESSED) <<
"Can not remove invalid compressed gzip file:" << outputPath;
445 compressedData.
remove(0, 6);
446 compressedData.
chop(4);
449 QDataStream headerStream(&header, QIODevice::WriteOnly);
451 headerStream << quint16(0x1f8b)
453 #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) 456 << quint32(origLastModified.
toTime_t())
458 #
if defined Q_OS_UNIX
460 #elif defined Q_OS_WIN 462 #elif defined Q_OS_MACOS 471 QDataStream footerStream(&footer, QIODevice::WriteOnly);
472 footerStream << crc32buf(data)
473 << quint32(data.
size());
475 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
476 qCCritical(C_STATICCOMPRESSED,
"Failed to write compressed gzip file \"%s\": %s", qPrintable(inputPath), qPrintable(output.errorString()));
483 bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
const QString &outputPath)
const 485 qCDebug(C_STATICCOMPRESSED,
"Compressing \"%s\" with deflate to \"%s\".", qPrintable(inputPath), qPrintable(outputPath));
487 QFile input(inputPath);
488 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
489 qCWarning(C_STATICCOMPRESSED) <<
"Can not open input file to compress with deflate:" << inputPath;
494 if (Q_UNLIKELY(data.
isEmpty())) {
495 qCWarning(C_STATICCOMPRESSED) <<
"Can not read input file or input file is empty:" << inputPath;
500 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
503 QFile output(outputPath);
504 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
505 qCWarning(C_STATICCOMPRESSED) <<
"Can not open output file to compress with deflate:" << outputPath;
509 if (Q_UNLIKELY(compressedData.
isEmpty())) {
510 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
511 if (output.exists()) {
512 if (Q_UNLIKELY(!output.remove())) {
513 qCWarning(C_STATICCOMPRESSED) <<
"Can not remove invalid compressed deflate file:" << outputPath;
521 compressedData.
remove(0, 6);
522 compressedData.
chop(4);
524 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
525 qCCritical(C_STATICCOMPRESSED,
"Failed to write compressed deflate file \"%s\": %s", qPrintable(inputPath), qPrintable(output.errorString()));
532 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 533 bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
const QString &outputPath)
const 535 qCDebug(C_STATICCOMPRESSED,
"Compressing \"%s\" with zopfli to \"%s\".", qPrintable(inputPath), qPrintable(outputPath));
537 QFile input(inputPath);
538 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
539 qCWarning(C_STATICCOMPRESSED) <<
"Can not open input file to compress with zopfli:" << inputPath;
544 if (Q_UNLIKELY(data.
isEmpty())) {
545 qCWarning(C_STATICCOMPRESSED) <<
"Can not read input file or input file is empty:" << inputPath;
550 ZopfliOptions options;
551 ZopfliInitOptions(&options);
552 options.numiterations = zopfliIterations;
554 unsigned char* out = 0;
557 ZopfliCompress(&options, ZopfliFormat::ZOPFLI_FORMAT_GZIP, reinterpret_cast<const unsigned char *>(data.
constData()), data.
size(), &out, &outSize);
561 QFile output(outputPath);
562 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
563 qCWarning(C_STATICCOMPRESSED) <<
"Can not open output file to compress with zopfli:" << outputPath;
565 if (Q_UNLIKELY(output.write(reinterpret_cast<const char *>(out), outSize) < 0)) {
566 qCCritical(C_STATICCOMPRESSED,
"Failed to write compressed zopfli file \"%s\": %s", qPrintable(inputPath), qPrintable(output.errorString()));
567 if (output.exists()) {
568 if (Q_UNLIKELY(!output.remove())) {
569 qCWarning(C_STATICCOMPRESSED) <<
"Can not remove invalid compressed zopfli file:" << outputPath;
577 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
586 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 587 bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
const QString &outputPath)
const 589 qCDebug(C_STATICCOMPRESSED,
"Compressing \"%s\" with brotli to \"%s\".", qPrintable(inputPath), qPrintable(outputPath));
591 QFile input(inputPath);
592 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
593 qCWarning(C_STATICCOMPRESSED) <<
"Can not open input file to compress with brotli:" << inputPath;
598 if (Q_UNLIKELY(data.
isEmpty())) {
599 qCWarning(C_STATICCOMPRESSED) <<
"Can not read input file or input file is empty:" << inputPath;
607 size_t outSize = BrotliEncoderMaxCompressedSize(static_cast<size_t>(data.
size()));
608 if (Q_LIKELY(outSize > 0)) {
609 const uint8_t *in = (
const uint8_t *) data.
constData();
611 out = (uint8_t *) malloc(
sizeof(uint8_t) * (outSize+1));
612 if (Q_LIKELY(out !=
nullptr)) {
613 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, data.
size(), in, &outSize, out);
614 if (Q_LIKELY(status == BROTLI_TRUE)) {
615 QFile output(outputPath);
616 if (Q_LIKELY(output.open(QIODevice::WriteOnly))) {
617 if (Q_LIKELY(output.write(reinterpret_cast<const char *>(out), outSize) > -1)) {
620 qCWarning(C_STATICCOMPRESSED,
"Failed to write brotli compressed data to output file \"%s\": %s", qPrintable(outputPath), qPrintable(output.errorString()));
621 if (output.exists()) {
622 if (Q_UNLIKELY(!output.remove())) {
623 qCWarning(C_STATICCOMPRESSED) <<
"Can not remove invalid compressed brotli file:" << outputPath;
628 qCWarning(C_STATICCOMPRESSED,
"Failed to open output file for brotli compression: %s", qPrintable(outputPath));
631 qCWarning(C_STATICCOMPRESSED,
"Failed to compress \"%s\" with brotli.", qPrintable(inputPath));
635 qCWarning(C_STATICCOMPRESSED,
"Can not allocate needed output buffer of size %lu for brotli compression.",
sizeof(uint8_t) * (outSize+1));
638 qCWarning(C_STATICCOMPRESSED,
"Needed output buffer too large to compress input of size %lu with brotli.", static_cast<size_t>(data.
size()));
645 #include "moc_staticcompressed.cpp"
virtual bool setup(Application *app) override
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
void setContentType(const QString &type)
QString writableLocation(QStandardPaths::StandardLocation type)
Deliver static files compressed on the fly or precompressed.
Headers & headers() noexcept
QString errorString() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
Response * res() const noexcept
bool isEmpty() const const
virtual ~StaticCompressed() override
T value(int i) const const
QMimeType mimeTypeForFile(const QString &fileName, QMimeDatabase::MatchMode mode) const const
void setDirs(const QStringList &dirs)
Headers headers() const noexcept
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
QVariantMap config(const QString &entity) const
user configuration for the application
bool isEmpty() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString header(const QString &key) const
QByteArray::iterator begin()
bool hasMatch() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
The Cutelyst namespace holds all public Cutelyst API.
virtual bool open(QIODevice::OpenMode mode) override
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
virtual qint64 size() const const override
bool isValid() const const
qint64 toSecsSinceEpoch() const const
QByteArray hash(const QByteArray &data, QCryptographicHash::Algorithm method)
QString fromLatin1(const char *str, int size)
The Cutelyst Application.
void setIncludePaths(const QStringList &paths)
Engine * engine() const noexcept
void setBody(QIODevice *body)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Response * response() const noexcept
QByteArray & remove(int pos, int len)
void setStatus(quint16 status) noexcept
QByteArray::iterator end()
uint toTime_t() const const
QByteArray toUtf8() const const