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> 16 #include <QDataStream> 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> 34 Q_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"));
69 const QString _defaultCacheDir =
73 config.value(QStringLiteral(
"cache_directory"), _defaultCacheDir).toString());
75 if (Q_UNLIKELY(!d->cacheDir.exists())) {
76 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
77 qCCritical(C_STATICCOMPRESSED,
78 "Failed to create cache directory for compressed static files at \"%s\".",
79 qPrintable(d->cacheDir.absolutePath()));
84 qCInfo(C_STATICCOMPRESSED,
85 "Compressed cache directory: %s",
86 qPrintable(d->cacheDir.absolutePath()));
90 .value(QStringLiteral(
"mime_types"), QStringLiteral(
"text/css,application/javascript"))
92 qCInfo(C_STATICCOMPRESSED,
"MIME Types: %s", qPrintable(_mimeTypes));
95 const QString _suffixes = config
96 .value(QStringLiteral(
"suffixes"),
97 QStringLiteral(
"js.map,css.map,min.js.map,min.css.map"))
99 qCInfo(C_STATICCOMPRESSED,
"Suffixes: %s", qPrintable(_suffixes));
102 d->checkPreCompressed = config.
value(QStringLiteral(
"check_pre_compressed"),
true).toBool();
103 qCInfo(C_STATICCOMPRESSED,
104 "Check for pre-compressed files: %s",
105 d->checkPreCompressed ?
"true" :
"false");
107 d->onTheFlyCompression = config.value(QStringLiteral(
"on_the_fly_compression"),
true).toBool();
108 qCInfo(C_STATICCOMPRESSED,
109 "Compress static files on the fly: %s",
110 d->onTheFlyCompression ?
"true" :
"false");
112 QStringList supportedCompressions{QStringLiteral(
"deflate"), QStringLiteral(
"gzip")};
115 d->zlibCompressionLevel = config.
value(QStringLiteral(
"zlib_compression_level"), 9).toInt(&ok);
116 if (!ok || (d->zlibCompressionLevel < -1) || (d->zlibCompressionLevel > 9)) {
117 d->zlibCompressionLevel = -1;
120 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 121 d->zopfliIterations = config.value(QStringLiteral(
"zopfli_iterations"), 15).toInt(&ok);
122 if (!ok || (d->zopfliIterations < 0)) {
123 d->zopfliIterations = 15;
125 d->useZopfli = config.value(QStringLiteral(
"use_zopfli"),
false).toBool();
126 supportedCompressions << QStringLiteral(
"zopfli");
129 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 130 d->brotliQualityLevel =
131 config.value(QStringLiteral(
"brotli_quality_level"), BROTLI_DEFAULT_QUALITY).toInt(&ok);
132 if (!ok || (d->brotliQualityLevel < BROTLI_MIN_QUALITY) ||
133 (d->brotliQualityLevel > BROTLI_MAX_QUALITY)) {
134 d->brotliQualityLevel = BROTLI_DEFAULT_QUALITY;
136 supportedCompressions << QStringLiteral(
"brotli");
139 qCInfo(C_STATICCOMPRESSED,
140 "Supported compressions: %s",
141 qPrintable(supportedCompressions.join(u
',')));
144 d->beforePrepareAction(c, skipMethod);
150 void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
156 const QString path = c->req()->path();
159 for (
const QString &dir : dirs) {
161 if (!locateCompressedFile(c, path)) {
165 res->
setBody(QStringLiteral(
"File not found: ") + path);
174 if (match.
hasMatch() && locateCompressedFile(c, path)) {
179 bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const 181 for (
const QDir &includePath : includePaths) {
182 const QString path = includePath.absoluteFilePath(relPath);
184 if (fileInfo.exists()) {
186 const QDateTime currentDateTime = fileInfo.lastModified();
206 _mimeTypeName = QStringLiteral(
"application/json");
214 c->req()->
header(QStringLiteral(
"Accept-Encoding"));
215 qCDebug(C_STATICCOMPRESSED) <<
"Accept-Encoding:" << acceptEncoding;
217 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 219 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
220 if (!compressedPath.
isEmpty()) {
221 qCDebug(C_STATICCOMPRESSED,
222 "Serving brotli compressed data from \"%s\".",
223 qPrintable(compressedPath));
224 contentEncoding = QStringLiteral(
"br");
230 locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
231 if (!compressedPath.
isEmpty()) {
232 qCDebug(C_STATICCOMPRESSED,
233 "Serving %s compressed data from \"%s\".",
234 useZopfli ?
"zopfli" :
"gzip",
235 qPrintable(compressedPath));
236 contentEncoding = QStringLiteral(
"gzip");
240 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
241 if (!compressedPath.
isEmpty()) {
242 qCDebug(C_STATICCOMPRESSED,
243 "Serving deflate compressed data from \"%s\".",
244 qPrintable(compressedPath));
245 contentEncoding = QStringLiteral(
"deflate");
253 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
261 if (!_mimeTypeName.
isEmpty()) {
263 }
else if (mimeType.
isValid()) {
270 headers.
setHeader(QStringLiteral(
"CACHE_CONTROL"), QStringLiteral(
"public"));
272 if (!contentEncoding.
isEmpty()) {
277 headers.
pushHeader(QStringLiteral(
"Vary"), QStringLiteral(
"Accept-Encoding"));
283 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
288 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
292 QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
294 Compression compression)
const 300 switch (compression) {
303 suffix = QStringLiteral(
".gz");
305 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 307 suffix = QStringLiteral(
".br");
311 suffix = QStringLiteral(
".deflate");
314 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
318 if (checkPreCompressed) {
319 const QFileInfo origCompressed(origPath + suffix);
320 if (origCompressed.exists()) {
321 compressedPath = origCompressed.absoluteFilePath();
322 return compressedPath;
326 if (onTheFlyCompression) {
328 const QString path = cacheDir.absoluteFilePath(
334 if (info.exists() && (info.lastModified() > origLastModified)) {
335 compressedPath = path;
338 if (lock.tryLock(10)) {
339 switch (compression) {
340 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 342 if (compressBrotli(origPath, path)) {
343 compressedPath = path;
348 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 349 if (compressZopfli(origPath, path)) {
350 compressedPath = path;
355 if (compressGzip(origPath, path, origLastModified)) {
356 compressedPath = path;
360 if (compressDeflate(origPath, path)) {
361 compressedPath = path;
372 return compressedPath;
376 static const quint32 crc_32_tab[] = {
377 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
378 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
379 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
380 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
381 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
382 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
383 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
384 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
385 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
386 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
387 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
388 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
389 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
390 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
391 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
392 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
393 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
394 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
395 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
396 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
397 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
398 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
399 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
400 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
401 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
402 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
403 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
404 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
405 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
406 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
407 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
408 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
409 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
410 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
411 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
412 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
413 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
414 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
415 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
416 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
417 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
418 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
419 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
423 quint32 updateCRC32(
unsigned char ch, quint32 crc)
425 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
430 return ~
std::accumulate(data.
begin(),
433 [](quint32 oldcrc32,
char buf) {
return updateCRC32(buf, oldcrc32); });
436 bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
440 qCDebug(C_STATICCOMPRESSED,
441 "Compressing \"%s\" with gzip to \"%s\".",
442 qPrintable(inputPath),
443 qPrintable(outputPath));
445 QFile input(inputPath);
447 qCWarning(C_STATICCOMPRESSED)
448 <<
"Can not open input file to compress with gzip:" << inputPath;
453 if (Q_UNLIKELY(data.
isEmpty())) {
454 qCWarning(C_STATICCOMPRESSED)
455 <<
"Can not read input file or input file is empty:" << inputPath;
460 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
463 QFile output(outputPath);
465 qCWarning(C_STATICCOMPRESSED)
466 <<
"Can not open output file to compress with gzip:" << outputPath;
470 if (Q_UNLIKELY(compressedData.
isEmpty())) {
471 qCWarning(C_STATICCOMPRESSED)
472 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
473 if (output.exists()) {
474 if (Q_UNLIKELY(!output.remove())) {
475 qCWarning(C_STATICCOMPRESSED)
476 <<
"Can not remove invalid compressed gzip file:" << outputPath;
484 compressedData.
remove(0, 6);
485 compressedData.
chop(4);
490 headerStream << quint16(0x1f8b) << quint16(0x0800)
492 #
if defined Q_OS_UNIX
494 #elif defined Q_OS_WIN 496 #elif defined Q_OS_MACOS 506 footerStream << crc32buf(data) << quint32(data.
size());
508 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
509 qCCritical(C_STATICCOMPRESSED,
510 "Failed to write compressed gzip file \"%s\": %s",
511 qPrintable(inputPath),
512 qPrintable(output.errorString()));
519 bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
520 const QString &outputPath)
const 522 qCDebug(C_STATICCOMPRESSED,
523 "Compressing \"%s\" with deflate to \"%s\".",
524 qPrintable(inputPath),
525 qPrintable(outputPath));
527 QFile input(inputPath);
529 qCWarning(C_STATICCOMPRESSED)
530 <<
"Can not open input file to compress with deflate:" << inputPath;
535 if (Q_UNLIKELY(data.
isEmpty())) {
536 qCWarning(C_STATICCOMPRESSED)
537 <<
"Can not read input file or input file is empty:" << inputPath;
542 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
545 QFile output(outputPath);
547 qCWarning(C_STATICCOMPRESSED)
548 <<
"Can not open output file to compress with deflate:" << outputPath;
552 if (Q_UNLIKELY(compressedData.
isEmpty())) {
553 qCWarning(C_STATICCOMPRESSED)
554 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
555 if (output.exists()) {
556 if (Q_UNLIKELY(!output.remove())) {
557 qCWarning(C_STATICCOMPRESSED)
558 <<
"Can not remove invalid compressed deflate file:" << outputPath;
566 compressedData.
remove(0, 6);
567 compressedData.
chop(4);
569 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
570 qCCritical(C_STATICCOMPRESSED,
571 "Failed to write compressed deflate file \"%s\": %s",
572 qPrintable(inputPath),
573 qPrintable(output.errorString()));
580 #ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI 581 bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
582 const QString &outputPath)
const 584 qCDebug(C_STATICCOMPRESSED,
585 "Compressing \"%s\" with zopfli to \"%s\".",
586 qPrintable(inputPath),
587 qPrintable(outputPath));
589 QFile input(inputPath);
591 qCWarning(C_STATICCOMPRESSED)
592 <<
"Can not open input file to compress with zopfli:" << inputPath;
597 if (Q_UNLIKELY(data.
isEmpty())) {
598 qCWarning(C_STATICCOMPRESSED)
599 <<
"Can not read input file or input file is empty:" << inputPath;
604 ZopfliOptions options;
605 ZopfliInitOptions(&options);
606 options.numiterations = zopfliIterations;
608 unsigned char *out = 0;
611 ZopfliCompress(&options,
612 ZopfliFormat::ZOPFLI_FORMAT_GZIP,
613 reinterpret_cast<const unsigned char *>(data.
constData()),
620 QFile output(outputPath);
622 qCWarning(C_STATICCOMPRESSED)
623 <<
"Can not open output file to compress with zopfli:" << outputPath;
625 if (Q_UNLIKELY(output.write(reinterpret_cast<const char *>(out), outSize) < 0)) {
626 qCCritical(C_STATICCOMPRESSED,
627 "Failed to write compressed zopfli file \"%s\": %s",
628 qPrintable(inputPath),
629 qPrintable(output.errorString()));
630 if (output.exists()) {
631 if (Q_UNLIKELY(!output.remove())) {
632 qCWarning(C_STATICCOMPRESSED)
633 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
641 qCWarning(C_STATICCOMPRESSED)
642 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
651 #ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI 652 bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
653 const QString &outputPath)
const 655 qCDebug(C_STATICCOMPRESSED,
656 "Compressing \"%s\" with brotli to \"%s\".",
657 qPrintable(inputPath),
658 qPrintable(outputPath));
660 QFile input(inputPath);
662 qCWarning(C_STATICCOMPRESSED)
663 <<
"Can not open input file to compress with brotli:" << inputPath;
668 if (Q_UNLIKELY(data.
isEmpty())) {
669 qCWarning(C_STATICCOMPRESSED)
670 <<
"Can not read input file or input file is empty:" << inputPath;
678 size_t outSize = BrotliEncoderMaxCompressedSize(static_cast<size_t>(data.
size()));
679 if (Q_LIKELY(outSize > 0)) {
680 const uint8_t *in = (
const uint8_t *) data.
constData();
682 out = (uint8_t *) malloc(
sizeof(uint8_t) * (outSize + 1));
683 if (Q_LIKELY(out !=
nullptr)) {
684 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel,
685 BROTLI_DEFAULT_WINDOW,
691 if (Q_LIKELY(status == BROTLI_TRUE)) {
692 QFile output(outputPath);
694 if (Q_LIKELY(output.write(reinterpret_cast<const char *>(out), outSize) > -1)) {
699 "Failed to write brotli compressed data to output file \"%s\": %s",
700 qPrintable(outputPath),
701 qPrintable(output.errorString()));
702 if (output.exists()) {
703 if (Q_UNLIKELY(!output.remove())) {
704 qCWarning(C_STATICCOMPRESSED)
705 <<
"Can not remove invalid compressed brotli file:" 711 qCWarning(C_STATICCOMPRESSED,
712 "Failed to open output file for brotli compression: %s",
713 qPrintable(outputPath));
716 qCWarning(C_STATICCOMPRESSED,
717 "Failed to compress \"%s\" with brotli.",
718 qPrintable(inputPath));
722 qCWarning(C_STATICCOMPRESSED,
723 "Can not allocate needed output buffer of size %lu for brotli compression.",
724 sizeof(uint8_t) * (outSize + 1));
727 qCWarning(C_STATICCOMPRESSED,
728 "Needed output buffer too large to compress input of size %lu with brotli.",
729 static_cast<size_t>(data.
size()));
736 #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
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
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const 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()
QByteArray toUtf8() const const