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_BROTLI
27# include <brotli/encode.h>
33Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
37 , d_ptr(new StaticCompressedPrivate)
40 d->includePaths.append(parent->config(u
"root"_s).toString());
45 , d_ptr(new StaticCompressedPrivate)
48 d->includePaths.append(
parent->config(u
"root"_s).toString());
49 d->defaultConfig = defaultConfig;
57 d->includePaths.clear();
58 for (
const QString &path : paths) {
59 d->includePaths.append(
QDir(path));
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;
199void 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)) {
233bool 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;
378QString 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;
481void 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;
499static 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);
515quint32 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);
531bool 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)
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();
620bool 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
675void 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;
695bool 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
762void 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;
781bool 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
851bool 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;
877bool 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"
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.
QByteArray header(QAnyStringView key) const noexcept
Headers headers() 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)
StaticCompressed(Application *parent)
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)
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 endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
void resize(qsizetype newSize, QChar fillChar)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
QString join(QChar separator) const const