cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
server.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2016-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "localserver.h"
6#include "protocol.h"
7#include "protocolfastcgi.h"
8#include "protocolhttp.h"
9#include "protocolhttp2.h"
10#include "server_p.h"
11#include "serverengine.h"
12#include "socket.h"
13#include "tcpserverbalancer.h"
14
15#ifdef Q_OS_UNIX
16# include "unixfork.h"
17#else
18# include "windowsfork.h"
19#endif
20
21#ifdef Q_OS_LINUX
22# include "../EventLoopEPoll/eventdispatcher_epoll.h"
23# include "systemdnotify.h"
24#endif
25
26#include <iostream>
27
28#include <QCommandLineParser>
29#include <QCoreApplication>
30#include <QDir>
31#include <QLoggingCategory>
32#include <QMetaProperty>
33#include <QPluginLoader>
34#include <QSettings>
35#include <QSocketNotifier>
36#include <QThread>
37#include <QTimer>
38#include <QUrl>
39
40Q_LOGGING_CATEGORY(CUTELYST_SERVER, "cutelyst.server", QtWarningMsg)
41
42using namespace Cutelyst;
43using namespace Qt::Literals::StringLiterals;
44
46 : QObject(parent)
47 , d_ptr(new ServerPrivate(this))
48{
49 QCoreApplication::addLibraryPath(QDir().absolutePath());
50
51 if (!qEnvironmentVariableIsSet("QT_MESSAGE_PATTERN")) {
52 if (qEnvironmentVariableIsSet("JOURNAL_STREAM")) {
53 // systemd journal already logs PID, check if it logs threadid as well
54 qSetMessagePattern(u"%{category}[%{type}] %{message}"_s);
55 } else {
56 qSetMessagePattern(u"%{pid}:%{threadid} %{category}[%{type}] %{message}"_s);
57 }
58 }
59
60#ifdef Q_OS_LINUX
61 if (!qEnvironmentVariableIsSet("CUTELYST_QT_EVENT_LOOP")) {
62 qCInfo(CUTELYST_SERVER) << "Trying to install EPoll event loop";
63 QCoreApplication::setEventDispatcher(new EventDispatcherEPoll);
64 }
65#endif
66
67 auto cleanUp = [this]() {
68 Q_D(Server);
69 delete d->protoHTTP;
70 d->protoHTTP = nullptr;
71
72 delete d->protoHTTP2;
73 d->protoHTTP2 = nullptr;
74
75 delete d->protoFCGI;
76 d->protoFCGI = nullptr;
77
78 delete d->engine;
79 d->engine = nullptr;
80
81 qDeleteAll(d->servers);
82 d->servers.clear();
83 };
84
85 connect(this, &Server::errorOccured, this, cleanUp);
86 connect(this, &Server::stopped, this, cleanUp);
87}
88
90{
91 delete d_ptr;
92 std::cout << "Cutelyst-Server terminated" << '\n';
93}
94
96{
97 Q_D(Server);
98
99 QCommandLineParser parser;
101 //: CLI app description
102 //% "Fast, developer-friendly server."
103 qtTrId("cutelystd-cli-desc"));
104 parser.addHelpOption();
105 parser.addVersionOption();
106
107 QCommandLineOption iniOpt(QStringLiteral("ini"),
108 //: CLI option description
109 //% "Load config from INI file. When used multiple times, content "
110 //% "will be merged and same keys in the sections will be "
111 //% "overwritten by content from later files."
112 qtTrId("cutelystd-opt-ini-desc"),
113 //: CLI option value name
114 //% "file"
115 qtTrId("cutelystd-opt-value-file"));
116 parser.addOption(iniOpt);
117
118 QCommandLineOption jsonOpt({QStringLiteral("j"), QStringLiteral("json")},
119 //: CLI option description
120 //% "Load config from JSON file. When used multiple times, content "
121 //% "will be merged and same keys in the sections will be "
122 //% "overwritten by content from later files."
123 qtTrId("cutelystd-opt-json-desc"),
124 qtTrId("cutelystd-opt-value-file"));
125 parser.addOption(jsonOpt);
126
128 QStringLiteral("chdir"),
129 //: CLI option description
130 //% "Change to the specified directory before the application is loaded."
131 qtTrId("cutelystd-opt-chdir-desc"),
132 //: CLI option value name
133 //% "directory"
134 qtTrId("cutelystd-opt-value-directory"));
135 parser.addOption(chdir);
136
138 QStringLiteral("chdir2"),
139 //: CLI option description
140 //% "Change to the specified directory after the application has been loaded."
141 qtTrId("cutelystd-opt-chdir2-desc"),
142 qtTrId("cutelystd-opt-value-directory"));
143 parser.addOption(chdir2);
144
145 QCommandLineOption lazyOption(
146 QStringLiteral("lazy"),
147 //: CLI option description
148 //% "Use lazy mode (load the application in the workers instead of master)."
149 qtTrId("cutelystd-opt-lazy-desc"));
150 parser.addOption(lazyOption);
151
152 QCommandLineOption application({QStringLiteral("application"), QStringLiteral("a")},
153 //: CLI option description
154 //% "Path to the application file to load."
155 qtTrId("cutelystd-opt-application-desc"),
156 qtTrId("cutelystd-opt-value-file"));
157 parser.addOption(application);
158
159 QCommandLineOption threads({QStringLiteral("threads"), QStringLiteral("t")},
160 //: CLI option description
161 //% "The number of threads to use. If set to “auto”, the ideal "
162 //% "thread count is used."
163 qtTrId("cutelystd-opt-threads-desc"),
164 //: CLI option value name
165 //% "threads"
166 qtTrId("cutelystd-opt-threads-value"));
167 parser.addOption(threads);
168
169#ifdef Q_OS_UNIX
170 QCommandLineOption processes({QStringLiteral("processes"), QStringLiteral("p")},
171 //: CLI option description
172 //% "Spawn the specified number of processes. If set to “auto”, "
173 //% "the ideal process count is used."
174 qtTrId("cutelystd-opt-processes-desc"),
175 //: CLI option value name
176 //% "processes"
177 qtTrId("cutelystd-opt-processes-value"));
178 parser.addOption(processes);
179#endif
180
181 QCommandLineOption master({QStringLiteral("master"), QStringLiteral("M")},
182 //: CLI option description
183 //% "Enable master process."
184 qtTrId("cutelystd-opt-master-desc"));
185 parser.addOption(master);
186
187 QCommandLineOption listenQueue({QStringLiteral("listen"), QStringLiteral("l")},
188 //: CLI option description
189 //% "Set the socket listen queue size. Default value: 100."
190 qtTrId("cutelystd-opt-listen-desc"),
191 //: CLI option value name
192 //% "size"
193 qtTrId("cutelystd-opt-value-size"));
194 parser.addOption(listenQueue);
195
196 QCommandLineOption bufferSize({QStringLiteral("buffer-size"), QStringLiteral("b")},
197 //: CLI option description
198 //% "Set the internal buffer size. Default value: 4096."
199 qtTrId("cutelystd-opt-buffer-size-desc"),
200 //: CLI option value name
201 //% "bytes"
202 qtTrId("cutelystd-opt-value-bytes"));
203 parser.addOption(bufferSize);
204
205 QCommandLineOption postBuffering(QStringLiteral("post-buffering"),
206 //: CLI option description
207 //% "Sets the size after which buffering takes place on the "
208 //% "hard disk instead of in the main memory. "
209 //% "Default value: -1."
210 qtTrId("cutelystd-opt-post-buffering-desc"),
211 qtTrId("cutelystd-opt-value-bytes"));
212 parser.addOption(postBuffering);
213
214 QCommandLineOption postBufferingBufsize(
215 QStringLiteral("post-buffering-bufsize"),
216 //: CLI option description
217 //% "Set the buffer size for read() in post buffering mode. Default value: 4096."
218 qtTrId("cutelystd-opt-post-buffering-bufsize-desc"),
219 qtTrId("cutelystd-opt-value-bytes"));
220 parser.addOption(postBufferingBufsize);
221
222 QCommandLineOption httpSocketOpt({QStringLiteral("http-socket"), QStringLiteral("h1")},
223 //: CLI option description
224 //% "Bind to the specified TCP socket using the HTTP protocol."
225 qtTrId("cutelystd-opt-http-socket-desc"),
226 //: CLI option value name
227 //% "[address]:port"
228 qtTrId("cutelystd-opt-value-address"));
229 parser.addOption(httpSocketOpt);
230
231 QCommandLineOption http2SocketOpt(
232 {QStringLiteral("http2-socket"), QStringLiteral("h2")},
233 //: CLI option description
234 //% "Bind to the specified TCP socket using the HTTP/2 Clear Text protocol."
235 qtTrId("cutelystd-opt-http2-socket-desc"),
236 qtTrId("cutelystd-opt-value-address"));
237 parser.addOption(http2SocketOpt);
238
239 QCommandLineOption http2HeaderTableSizeOpt(QStringLiteral("http2-header-table-size"),
240 //: CLI option description
241 //% "Sets the HTTP/2 header table size."
242 qtTrId("cutelystd-opt-http2-header-table-size-desc"),
243 qtTrId("cutelystd-opt-value-size"));
244 parser.addOption(http2HeaderTableSizeOpt);
245
246 QCommandLineOption upgradeH2cOpt(QStringLiteral("upgrade-h2c"),
247 //: CLI option description
248 //% "Upgrades HTTP/1 to H2c (HTTP/2 Clear Text)."
249 qtTrId("cutelystd-opt-upgrade-h2c-desc"));
250 parser.addOption(upgradeH2cOpt);
251
252 QCommandLineOption httpsH2Opt(QStringLiteral("https-h2"),
253 //: CLI option description
254 //% "Negotiate HTTP/2 on HTTPS socket."
255 qtTrId("cutelystd-opt-https-h2-desc"));
256 parser.addOption(httpsH2Opt);
257
258 QCommandLineOption httpsSocketOpt({QStringLiteral("https-socket"), QStringLiteral("hs1")},
259 //: CLI option description
260 //% "Bind to the specified TCP socket using HTTPS protocol."
261 qtTrId("cutelystd-opt-https-socket-desc"),
262 //% "[address]:port,certFile,keyFile[,algorithm]"
263 qtTrId("cutelystd-opt-value-httpsaddress"));
264 parser.addOption(httpsSocketOpt);
265
266 QCommandLineOption fastcgiSocketOpt(
267 QStringLiteral("fastcgi-socket"),
268 //: CLI option description
269 //% "Bind to the specified UNIX/TCP socket using FastCGI protocol."
270 qtTrId("cutelystd-opt-fastcgi-socket-desc"),
271 qtTrId("cutelystd-opt-value-address"));
272 parser.addOption(fastcgiSocketOpt);
273
274 QCommandLineOption socketAccess(
275 QStringLiteral("socket-access"),
276 //: CLI option description
277 //% "Set the LOCAL socket access, such as 'ugo' standing for User, Group, Other access."
278 qtTrId("cutelystd-opt-socket-access-desc"),
279 //: CLI option value name
280 //% "options"
281 qtTrId("cutelystd-opt-socket-access-value"));
282 parser.addOption(socketAccess);
283
284 QCommandLineOption socketTimeout({QStringLiteral("socket-timeout"), QStringLiteral("z")},
285 //: CLI option description
286 //% "Set internal socket timeouts. Default value: 4."
287 qtTrId("cutelystd-opt-socket-timeout-desc"),
288 //: CLI option value name
289 //% "seconds"
290 qtTrId("cutelystd-opt-socket-timeout-value"));
291 parser.addOption(socketTimeout);
292
293 QCommandLineOption staticMapOpt(QStringLiteral("static-map"),
294 //: CLI option description
295 //% "Map mountpoint to local directory to serve static files. "
296 //% "The mountpoint will be removed from the request path and "
297 //% "the rest will be appended to the local path to find the "
298 //% "file to serve. Can be used multiple times."
299 qtTrId("cutelystd-opt-static-map-desc"),
300 //: CLI option value name
301 //% "/mountpoint=/path"
302 qtTrId("cutelystd-opt-value-static-map"));
303 parser.addOption(staticMapOpt);
304
305 QCommandLineOption staticMap2Opt(QStringLiteral("static-map2"),
306 //: CLI option description
307 //% "Like static-map but completely appending the request "
308 //% "path to the local path. Can be used multiple times."
309 qtTrId("cutelystd-opt-static-map2-desc"),
310 //: CLI option value name
311 //% "/mountpoint=/path"
312 qtTrId("cutelystd-opt-value-static-map"));
313 parser.addOption(staticMap2Opt);
314
315 QCommandLineOption autoReload({QStringLiteral("auto-restart"), QStringLiteral("r")},
316 //: CLI option description
317 //% "Auto restarts when the application file changes. Master "
318 //% "process and lazy mode have to be enabled."
319 qtTrId("cutelystd-opt-auto-restart-desc"));
320 parser.addOption(autoReload);
321
322 QCommandLineOption touchReloadOpt(
323 QStringLiteral("touch-reload"),
324 //: CLI option description
325 //% "Reload the application if the specified file is modified/touched. Master process "
326 //% "and lazy mode have to be enabled."
327 qtTrId("cutelystd-opt-touch-reload-desc"),
328 qtTrId("cutelystd-opt-value-file"));
329 parser.addOption(touchReloadOpt);
330
331 QCommandLineOption tcpNoDelay(QStringLiteral("tcp-nodelay"),
332 //: CLI option description
333 //% "Enable TCP NODELAY on each request."
334 qtTrId("cutelystd-opt-tcp-nodelay-desc"));
335 parser.addOption(tcpNoDelay);
336
337 QCommandLineOption soKeepAlive(QStringLiteral("so-keepalive"),
338 //: CLI option description
339 //% "Enable TCP KEEPALIVE."
340 qtTrId("cutelystd-opt-so-keepalive-desc"));
341 parser.addOption(soKeepAlive);
342
343 QCommandLineOption socketSndbuf(QStringLiteral("socket-sndbuf"),
344 //: CLI option description
345 //% "Sets the socket send buffer size in bytes at the OS "
346 //% "level. This maps to the SO_SNDBUF socket option."
347 qtTrId("cutelystd-opt-socket-sndbuf-desc"),
348 qtTrId("cutelystd-opt-value-bytes"));
349 parser.addOption(socketSndbuf);
350
351 QCommandLineOption socketRcvbuf(QStringLiteral("socket-rcvbuf"),
352 //: CLI option description
353 //% "Sets the socket receive buffer size in bytes at the OS "
354 //% "level. This maps to the SO_RCVBUF socket option."
355 qtTrId("cutelystd-opt-socket-rcvbuf-desc"),
356 qtTrId("cutelystd-opt-value-bytes"));
357 parser.addOption(socketRcvbuf);
358
359 QCommandLineOption wsMaxSize(QStringLiteral("websocket-max-size"),
360 //: CLI option description
361 //% "Maximum allowed payload size for websocket in kibibytes. "
362 //% "Default value: 1024 KiB."
363 qtTrId("cutelystd-opt-websocket-max-size-desc"),
364 //: CLI option value name
365 //% "kibibyte"
366 qtTrId("cutelystd-opt-websocket-max-size-value"));
367 parser.addOption(wsMaxSize);
368
369 QCommandLineOption pidfileOpt(QStringLiteral("pidfile"),
370 //: CLI option description
371 //% "Create pidfile (before privilege drop)."
372 qtTrId("cutelystd-opt-pidfile-desc"),
373 //: CLI option value name
374 //% "pidfile"
375 qtTrId("cutelystd-opt-value-pidfile"));
376 parser.addOption(pidfileOpt);
377
378 QCommandLineOption pidfile2Opt(QStringLiteral("pidfile2"),
379 //: CLI option description
380 //% "Create pidfile (after privilege drop)."
381 qtTrId("cutelystd-opt-pidfile2-desc"),
382 qtTrId("cutelystd-opt-value-pidfile"));
383 parser.addOption(pidfile2Opt);
384
385#ifdef Q_OS_UNIX
386 QCommandLineOption stopOption(QStringLiteral("stop"),
387 //: CLI option description
388 //% "Stop an instance identified by the PID in the pidfile."
389 qtTrId("cutelystd-opt-stop-desc"),
390 qtTrId("cutelystd-opt-value-pidfile"));
391 parser.addOption(stopOption);
392
393 QCommandLineOption uidOption(QStringLiteral("uid"),
394 //: CLI option description
395 //% "Setuid to the specified user/uid."
396 qtTrId("cutelystd-opt-uid-desc"),
397 //: CLI option value name
398 //% "user/uid"
399 qtTrId("cutelystd-opt-uid-value"));
400 parser.addOption(uidOption);
401
402 QCommandLineOption gidOption(QStringLiteral("gid"),
403 //: CLI option description
404 //% "Setuid to the specified group/gid."
405 qtTrId("cutelystd-opt-gid-desc"),
406 //: CLI option value name
407 //% "group/gid"
408 qtTrId("cutelystd-opt-gid-value"));
409 parser.addOption(gidOption);
410
411 QCommandLineOption noInitgroupsOption(QStringLiteral("no-initgroups"),
412 //: CLI option description
413 //% "Disable additional groups set via initgroups()."
414 qtTrId("cutelystd-opt-no-init-groups-desc"));
415 parser.addOption(noInitgroupsOption);
416
417 QCommandLineOption chownSocketOption(QStringLiteral("chown-socket"),
418 //: CLI option description
419 //% "Change the ownership of the UNIX socket."
420 qtTrId("cutelystd-opt-chown-socket-desc"),
421 //: CLI option value name
422 //% "uid:gid"
423 qtTrId("cutelystd-opt-chown-socket-value"));
424 parser.addOption(chownSocketOption);
425
426 QCommandLineOption umaskOption(QStringLiteral("umask"),
427 //: CLI option description
428 //% "Set file mode creation mask."
429 qtTrId("cutelystd-opt-umask-desc"),
430 //: CLI option value name
431 //% "mask"
432 qtTrId("cutelystd-opt-umask-value"));
433 parser.addOption(umaskOption);
434
435 QCommandLineOption cpuAffinityOption(
436 QStringLiteral("cpu-affinity"),
437 //: CLI option description
438 //% "Set CPU affinity with the number of CPUs available for each worker core."
439 qtTrId("cutelystd-opt-cpu-affinity-desc"),
440 //: CLI option value name
441 //% "core count"
442 qtTrId("cutelystd-opt-cpu-affinity-value"));
443 parser.addOption(cpuAffinityOption);
444#endif // Q_OS_UNIX
445
446#ifdef Q_OS_LINUX
447 QCommandLineOption reusePortOption(QStringLiteral("reuse-port"),
448 //: CLI option description
449 //% "Enable SO_REUSEPORT flag on socket (Linux 3.9+)."
450 qtTrId("cutelystd-opt-reuse-port-desc"));
451 parser.addOption(reusePortOption);
452#endif
453
454 QCommandLineOption threadBalancerOpt(
455 QStringLiteral("experimental-thread-balancer"),
456 //: CLI option description
457 //% "Balances new connections to threads using round-robin."
458 qtTrId("cutelystd-opt-experimental-thread-balancer-desc"));
459 parser.addOption(threadBalancerOpt);
460
461 QCommandLineOption frontendProxy(QStringLiteral("using-frontend-proxy"),
462 //: CLI option description
463 //% "Enable frontend (reverse-)proxy support."
464 qtTrId("cutelystd-opt-using-frontend-proxy-desc"));
465 parser.addOption(frontendProxy);
466
467 // Process the actual command line arguments given by the user
468 parser.process(arguments);
469
470 setIni(parser.values(iniOpt));
471
472 setJson(parser.values(jsonOpt));
473
474 if (parser.isSet(chdir)) {
475 setChdir(parser.value(chdir));
476 }
477
478 if (parser.isSet(chdir2)) {
479 setChdir2(parser.value(chdir2));
480 }
481
482 if (parser.isSet(threads)) {
483 setThreads(parser.value(threads));
484 }
485
486 if (parser.isSet(socketAccess)) {
487 setSocketAccess(parser.value(socketAccess));
488 }
489
490 if (parser.isSet(socketTimeout)) {
491 bool ok;
492 auto size = parser.value(socketTimeout).toInt(&ok);
493 setSocketTimeout(size);
494 if (!ok || size < 0) {
495 parser.showHelp(1);
496 }
497 }
498
499 if (parser.isSet(pidfileOpt)) {
500 setPidfile(parser.value(pidfileOpt));
501 }
502
503 if (parser.isSet(pidfile2Opt)) {
504 setPidfile2(parser.value(pidfile2Opt));
505 }
506
507#ifdef Q_OS_UNIX
508 if (parser.isSet(stopOption)) {
509 UnixFork::stopWSGI(parser.value(stopOption));
510 }
511
512 if (parser.isSet(processes)) {
513 setProcesses(parser.value(processes));
514 }
515
516 if (parser.isSet(uidOption)) {
517 setUid(parser.value(uidOption));
518 }
519
520 if (parser.isSet(gidOption)) {
521 setGid(parser.value(gidOption));
522 }
523
524 if (parser.isSet(noInitgroupsOption)) {
525 setNoInitgroups(true);
526 }
527
528 if (parser.isSet(chownSocketOption)) {
529 setChownSocket(parser.value(chownSocketOption));
530 }
531
532 if (parser.isSet(umaskOption)) {
533 setUmask(parser.value(umaskOption));
534 }
535
536 if (parser.isSet(cpuAffinityOption)) {
537 bool ok;
538 auto value = parser.value(cpuAffinityOption).toInt(&ok);
539 setCpuAffinity(value);
540 if (!ok || value < 0) {
541 parser.showHelp(1);
542 }
543 }
544#endif // Q_OS_UNIX
545
546#ifdef Q_OS_LINUX
547 if (parser.isSet(reusePortOption)) {
548 setReusePort(true);
549 }
550#endif
551
552 if (parser.isSet(lazyOption)) {
553 setLazy(true);
554 }
555
556 if (parser.isSet(listenQueue)) {
557 bool ok;
558 auto size = parser.value(listenQueue).toInt(&ok);
559 setListenQueue(size);
560 if (!ok || size < 1) {
561 parser.showHelp(1);
562 }
563 }
564
565 if (parser.isSet(bufferSize)) {
566 bool ok;
567 auto size = parser.value(bufferSize).toInt(&ok);
568 setBufferSize(size);
569 if (!ok || size < 1) {
570 parser.showHelp(1);
571 }
572 }
573
574 if (parser.isSet(postBuffering)) {
575 bool ok;
576 auto size = parser.value(postBuffering).toLongLong(&ok);
577 setPostBuffering(size);
578 if (!ok || size < 1) {
579 parser.showHelp(1);
580 }
581 }
582
583 if (parser.isSet(postBufferingBufsize)) {
584 bool ok;
585 auto size = parser.value(postBufferingBufsize).toLongLong(&ok);
586 setPostBufferingBufsize(size);
587 if (!ok || size < 1) {
588 parser.showHelp(1);
589 }
590 }
591
592 if (parser.isSet(application)) {
593 setApplication(parser.value(application));
594 }
595
596 if (parser.isSet(master)) {
597 setMaster(true);
598 }
599
600 if (parser.isSet(autoReload)) {
601 setAutoReload(true);
602 }
603
604 if (parser.isSet(tcpNoDelay)) {
605 setTcpNodelay(true);
606 }
607
608 if (parser.isSet(soKeepAlive)) {
609 setSoKeepalive(true);
610 }
611
612 if (parser.isSet(upgradeH2cOpt)) {
613 setUpgradeH2c(true);
614 }
615
616 if (parser.isSet(httpsH2Opt)) {
617 setHttpsH2(true);
618 }
619
620 if (parser.isSet(socketSndbuf)) {
621 bool ok;
622 auto size = parser.value(socketSndbuf).toInt(&ok);
623 setSocketSndbuf(size);
624 if (!ok || size < 1) {
625 parser.showHelp(1);
626 }
627 }
628
629 if (parser.isSet(socketRcvbuf)) {
630 bool ok;
631 auto size = parser.value(socketRcvbuf).toInt(&ok);
632 setSocketRcvbuf(size);
633 if (!ok || size < 1) {
634 parser.showHelp(1);
635 }
636 }
637
638 if (parser.isSet(wsMaxSize)) {
639 bool ok;
640 auto size = parser.value(wsMaxSize).toInt(&ok);
641 setWebsocketMaxSize(size);
642 if (!ok || size < 1) {
643 parser.showHelp(1);
644 }
645 }
646
647 if (parser.isSet(http2HeaderTableSizeOpt)) {
648 bool ok;
649 auto size = parser.value(http2HeaderTableSizeOpt).toUInt(&ok);
650 setHttp2HeaderTableSize(size);
651 if (!ok || size < 1) {
652 parser.showHelp(1);
653 }
654 }
655
656 if (parser.isSet(frontendProxy)) {
657 setUsingFrontendProxy(true);
658 }
659
660 setHttpSocket(httpSocket() + parser.values(httpSocketOpt));
661
662 setHttp2Socket(http2Socket() + parser.values(http2SocketOpt));
663
664 setHttpsSocket(httpsSocket() + parser.values(httpsSocketOpt));
665
666 setFastcgiSocket(fastcgiSocket() + parser.values(fastcgiSocketOpt));
667
668 setStaticMap(staticMap() + parser.values(staticMapOpt));
669
670 setStaticMap2(staticMap2() + parser.values(staticMap2Opt));
671
672 setTouchReload(touchReload() + parser.values(touchReloadOpt));
673
674 d->threadBalancer = parser.isSet(threadBalancerOpt);
675}
676
678{
679 Q_D(Server);
680 std::cout << "Cutelyst-Server starting" << '\n';
681
682 if (!qEnvironmentVariableIsSet("CUTELYST_SERVER_IGNORE_MASTER") && !d->master) {
683 std::cout
684 << "*** WARNING: you are running Cutelyst-Server without its master process manager ***"
685 << '\n';
686 }
687
688#ifdef Q_OS_UNIX
689 if (d->processes == -1 && d->threads == -1) {
690 d->processes = UnixFork::idealProcessCount();
691 d->threads = UnixFork::idealThreadCount() / d->processes;
692 } else if (d->processes == -1) {
693 d->processes = UnixFork::idealThreadCount();
694 } else if (d->threads == -1) {
695 d->threads = UnixFork::idealThreadCount();
696 }
697
698 if (d->processes == 0 && d->master) {
699 d->processes = 1;
700 }
701 d->genericFork = new UnixFork(d->processes, qMax(d->threads, 1), !d->userEventLoop, this);
702#else
703 if (d->processes == -1) {
704 d->processes = 1;
705 }
706 if (d->threads == -1) {
707 d->threads = QThread::idealThreadCount();
708 }
709 d->genericFork = new WindowsFork(this);
710#endif
711
712 connect(
713 d->genericFork, &AbstractFork::forked, d, &ServerPrivate::postFork, Qt::DirectConnection);
714 connect(
715 d->genericFork, &AbstractFork::shutdown, d, &ServerPrivate::shutdown, Qt::DirectConnection);
716
717 if (d->master && d->lazy) {
718 if (d->autoReload && !d->application.isEmpty()) {
719 d->touchReload.append(d->application);
720 }
721 d->genericFork->setTouchReload(d->touchReload);
722 }
723
724 int ret;
725 if (d->master && !d->genericFork->continueMaster(&ret)) {
726 return ret;
727 }
728
729#ifdef Q_OS_LINUX
730 if (systemdNotify::is_systemd_notify_available()) {
731 auto sd = new systemdNotify(this);
732 sd->setWatchdog(true, systemdNotify::sd_watchdog_enabled(true));
733 connect(this, &Server::ready, sd, [sd] {
734 sd->sendStatus(qApp->applicationName().toLatin1() + " is ready");
735 sd->sendReady("1");
736 });
737 connect(d, &ServerPrivate::postForked, sd, [sd] { sd->setWatchdog(false); });
738 qInfo(CUTELYST_SERVER) << "systemd notify detected";
739 }
740#endif
741
742 // TCP needs root privileges, but SO_REUSEPORT must have an effective user ID that
743 // matches the effective user ID used to perform the first bind on the socket.
744
745 if (!d->reusePort) {
746 if (!d->listenTcpSockets()) {
747 //% "No specified sockets were able to be opened"
748 Q_EMIT errorOccured(qtTrId("cutelystd-err-no-socket-opened"));
749 return 1; // No sockets has been opened
750 }
751 }
752
753 if (!d->writePidFile(d->pidfile)) {
754 //% "Failed to write pidfile %1"
755 Q_EMIT errorOccured(qtTrId("cutelystd-err-write-pidfile").arg(d->pidfile));
756 }
757
758#ifdef Q_OS_UNIX
759 bool isListeningLocalSockets = false;
760 if (!d->chownSocket.isEmpty()) {
761 if (!d->listenLocalSockets()) {
762 //% "Error on opening local sockets"
763 Q_EMIT errorOccured(qtTrId("cutelystd-err-open-local-socket"));
764 return 1;
765 }
766 isListeningLocalSockets = true;
767 }
768
769 if (!d->umask.isEmpty() && !UnixFork::setUmask(d->umask.toLatin1())) {
770 return 1;
771 }
772
773 if (!UnixFork::setGidUid(d->gid, d->uid, d->noInitgroups)) {
774 //% "Error on setting GID or UID"
775 Q_EMIT errorOccured(qtTrId("cutelystd-err-setgiduid"));
776 return 1;
777 }
778
779 if (!isListeningLocalSockets) {
780#endif
781 d->listenLocalSockets();
782#ifdef Q_OS_UNIX
783 }
784#endif
785
786 if (d->reusePort) {
787 if (!d->listenTcpSockets()) {
788 Q_EMIT errorOccured(qtTrId("cutelystd-err-no-socket-opened"));
789 return 1; // No sockets has been opened
790 }
791 }
792
793 if (d->servers.empty()) {
794 std::cout << "Please specify a socket to listen to" << '\n';
795 //% "No socket specified"
796 Q_EMIT errorOccured(qtTrId("cutelystd-err-no-socket-specified"));
797 return 1;
798 }
799
800 d->writePidFile(d->pidfile2);
801
802 if (!d->chdir.isEmpty()) {
803 std::cout << "Changing directory to: " << d->chdir.toLatin1().constData() << '\n';
804 if (!QDir::setCurrent(d->chdir)) {
805 Q_EMIT errorOccured(QString::fromLatin1("Failed to chdir to: '%s'")
806 .arg(QString::fromLatin1(d->chdir.toLatin1().constData())));
807 return 1;
808 }
809 }
810
811 d->app = app;
812
813 if (!d->lazy) {
814 if (!d->setupApplication()) {
815 //% "Failed to setup Application"
816 Q_EMIT errorOccured(qtTrId("cutelystd-err-fail-setup-app"));
817 return 1;
818 }
819 }
820
821 if (d->userEventLoop) {
822 d->postFork(0);
823 return 0;
824 }
825
826 ret = d->genericFork->exec(d->lazy, d->master);
827
828 return ret;
829}
830
832{
833 Q_D(Server);
834
835 if (d->engine) {
836 //% "Server not fully stopped."
837 Q_EMIT errorOccured(qtTrId("cutelystd-err-server-not-fully-stopped"));
838 return false;
839 }
840
841 d->processes = 0;
842 d->master = false;
843 d->lazy = false;
844 d->userEventLoop = true;
845#ifdef Q_OS_UNIX
846 d->uid.clear();
847 d->gid.clear();
848#endif
849 qputenv("CUTELYST_SERVER_IGNORE_MASTER", QByteArrayLiteral("1"));
850
851 if (exec(app) == 0) {
852 return true;
853 }
854
855 return false;
856}
857
859{
860 Q_D(Server);
861 if (d->userEventLoop) {
862 Q_EMIT d->shutdown();
863 }
864}
865
866ServerPrivate::~ServerPrivate()
867{
868 delete protoHTTP;
869 delete protoHTTP2;
870 delete protoFCGI;
871}
872
873bool ServerPrivate::listenTcpSockets()
874{
875 if (httpSockets.isEmpty() && httpsSockets.isEmpty() && http2Sockets.isEmpty() &&
876 fastcgiSockets.isEmpty()) {
877 // no sockets to listen to
878 return false;
879 }
880
881 // HTTP
882 for (const auto &socket : qAsConst(httpSockets)) {
883 if (!listenTcp(socket, getHttpProto(), false)) {
884 return false;
885 }
886 }
887
888 // HTTPS
889 for (const auto &socket : qAsConst(httpsSockets)) {
890 if (!listenTcp(socket, getHttpProto(), true)) {
891 return false;
892 }
893 }
894
895 // HTTP/2
896 for (const auto &socket : qAsConst(http2Sockets)) {
897 if (!listenTcp(socket, getHttp2Proto(), false)) {
898 return false;
899 }
900 }
901
902 // FastCGI
903 bool allOk = std::ranges::all_of(fastcgiSockets, [this](const QString &socket) {
904 return listenTcp(socket, getFastCgiProto(), false);
905 });
906
907 return allOk;
908}
909
910bool ServerPrivate::listenTcp(const QString &line, Protocol *protocol, bool secure)
911{
912 Q_Q(Server);
913
914 bool ret = true;
915 if (!line.startsWith(u'/')) {
916 auto server = new TcpServerBalancer(q);
917 server->setBalancer(threadBalancer);
918 ret = server->listen(line, protocol, secure);
919
920 if (ret && server->socketDescriptor()) {
921 auto qEnum = Protocol::staticMetaObject.enumerator(0);
922 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
923 << QByteArray::number(static_cast<int>(servers.size())).constData()
924 << " bound to TCP address " << server->serverName().constData() << " fd "
925 << QByteArray::number(server->socketDescriptor()).constData() << '\n';
926 servers.emplace_back(server);
927 }
928 }
929
930 return ret;
931}
932
933bool ServerPrivate::listenLocalSockets()
934{
935 QStringList http = httpSockets;
936 QStringList http2 = http2Sockets;
937 QStringList fastcgi = fastcgiSockets;
938
939#ifdef Q_OS_LINUX
940 Q_Q(Server);
941
942 std::vector<int> fds = systemdNotify::listenFds();
943 for (int fd : fds) {
944 auto server = new LocalServer(q, this);
945 if (server->listen(fd)) {
946 const QString name = server->serverName();
947 const QString fullName = server->fullServerName();
948
949 Protocol *protocol;
950 if (http.removeOne(fullName) || http.removeOne(name)) {
951 protocol = getHttpProto();
952 } else if (http2.removeOne(fullName) || http2.removeOne(name)) {
953 protocol = getHttp2Proto();
954 } else if (fastcgi.removeOne(fullName) || fastcgi.removeOne(name)) {
955 protocol = getFastCgiProto();
956 } else {
957 std::cerr << "systemd activated socket does not match any configured socket"
958 << '\n';
959 return false;
960 }
961 server->setProtocol(protocol);
962 server->pauseAccepting();
963
964 auto qEnum = Protocol::staticMetaObject.enumerator(0);
965 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
966 << QByteArray::number(static_cast<int>(servers.size())).constData()
967 << " bound to LOCAL address " << qPrintable(fullName) << " fd "
968 << QByteArray::number(server->socket()).constData() << '\n';
969 servers.push_back(server);
970 } else {
971 std::cerr << "Failed to listen on activated LOCAL FD: "
972 << QByteArray::number(fd).constData() << " : "
973 << qPrintable(server->errorString()) << '\n';
974 return false;
975 }
976 }
977#endif
978
979 bool ret = false;
980 const auto httpConst = http;
981 for (const auto &socket : httpConst) {
982 ret |= listenLocal(socket, getHttpProto());
983 }
984
985 const auto http2Const = http2;
986 for (const auto &socket : http2Const) {
987 ret |= listenLocal(socket, getHttp2Proto());
988 }
989
990 const auto fastcgiConst = fastcgi;
991 for (const auto &socket : fastcgiConst) {
992 ret |= listenLocal(socket, getFastCgiProto());
993 }
994
995 return ret;
996}
997
998bool ServerPrivate::listenLocal(const QString &line, Protocol *protocol)
999{
1000 Q_Q(Server);
1001
1002 bool ret = true;
1003 if (line.startsWith(QLatin1Char('/'))) {
1004 auto server = new LocalServer(q, this);
1005 server->setProtocol(protocol);
1006 if (!socketAccess.isEmpty()) {
1008 if (socketAccess.contains(u'u')) {
1010 }
1011
1012 if (socketAccess.contains(u'g')) {
1014 }
1015
1016 if (socketAccess.contains(u'o')) {
1018 }
1019 server->setSocketOptions(options);
1020 }
1021
1023 server->setListenBacklogSize(listenQueue);
1024 ret = server->listen(line);
1025 server->pauseAccepting();
1026
1027 if (!ret || !server->socket()) {
1028 std::cerr << "Failed to listen on LOCAL: " << qPrintable(line) << " : "
1029 << qPrintable(server->errorString()) << '\n';
1030 return false;
1031 }
1032
1033#ifdef Q_OS_UNIX
1034 if (!chownSocket.isEmpty()) {
1035 UnixFork::chownSocket(line, chownSocket);
1036 }
1037#endif
1038 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1039 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
1040 << QByteArray::number(static_cast<int>(servers.size())).constData()
1041 << " bound to LOCAL address " << qPrintable(line) << " fd "
1042 << QByteArray::number(server->socket()).constData() << '\n';
1043 servers.push_back(server);
1044 }
1045
1046 return ret;
1047}
1048
1049void Server::setApplication(const QString &application)
1050{
1051 Q_D(Server);
1052
1053 QPluginLoader loader(application);
1054 if (loader.fileName().isEmpty()) {
1055 d->application = application;
1056 } else {
1057 // We use the loader filename since it can provide
1058 // the suffix for the file watcher
1059 d->application = loader.fileName();
1060 }
1061 Q_EMIT changed();
1062}
1063
1065{
1066 Q_D(const Server);
1067 return d->application;
1068}
1069
1070void Server::setThreads(const QString &threads)
1071{
1072 Q_D(Server);
1073 if (threads.compare(u"auto", Qt::CaseInsensitive) == 0) {
1074 d->threads = -1;
1075 } else {
1076 d->threads = qMax(1, threads.toInt());
1077 }
1078 Q_EMIT changed();
1079}
1080
1082{
1083 Q_D(const Server);
1084 if (d->threads == -1) {
1085 return QStringLiteral("auto");
1086 }
1087 return QString::number(d->threads);
1088}
1089
1090void Server::setProcesses(const QString &process)
1091{
1092#ifdef Q_OS_UNIX
1093 Q_D(Server);
1094 if (process.compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0) {
1095 d->processes = -1;
1096 } else {
1097 d->processes = process.toInt();
1098 }
1099 Q_EMIT changed();
1100#endif
1101}
1102
1104{
1105 Q_D(const Server);
1106 if (d->processes == -1) {
1107 return QStringLiteral("auto");
1108 }
1109 return QString::number(d->processes);
1110}
1111
1112void Server::setChdir(const QString &chdir)
1113{
1114 Q_D(Server);
1115 d->chdir = chdir;
1116 Q_EMIT changed();
1117}
1118
1119QString Server::chdir() const
1120{
1121 Q_D(const Server);
1122 return d->chdir;
1123}
1124
1125void Server::setHttpSocket(const QStringList &httpSocket)
1126{
1127 Q_D(Server);
1128 d->httpSockets = httpSocket;
1129 Q_EMIT changed();
1130}
1131
1132QStringList Server::httpSocket() const
1133{
1134 Q_D(const Server);
1135 return d->httpSockets;
1136}
1137
1138void Server::setHttp2Socket(const QStringList &http2Socket)
1139{
1140 Q_D(Server);
1141 d->http2Sockets = http2Socket;
1142 Q_EMIT changed();
1143}
1144
1145QStringList Server::http2Socket() const
1146{
1147 Q_D(const Server);
1148 return d->http2Sockets;
1149}
1150
1151void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1152{
1153 Q_D(Server);
1154 d->http2HeaderTableSize = headerTableSize;
1155 Q_EMIT changed();
1156}
1157
1158quint32 Server::http2HeaderTableSize() const
1159{
1160 Q_D(const Server);
1161 return d->http2HeaderTableSize;
1162}
1163
1164void Server::setUpgradeH2c(bool enable)
1165{
1166 Q_D(Server);
1167 d->upgradeH2c = enable;
1168 Q_EMIT changed();
1169}
1170
1171bool Server::upgradeH2c() const
1172{
1173 Q_D(const Server);
1174 return d->upgradeH2c;
1175}
1176
1177void Server::setHttpsH2(bool enable)
1178{
1179 Q_D(Server);
1180 d->httpsH2 = enable;
1181 Q_EMIT changed();
1182}
1183
1184bool Server::httpsH2() const
1185{
1186 Q_D(const Server);
1187 return d->httpsH2;
1188}
1189
1190void Server::setHttpsSocket(const QStringList &httpsSocket)
1191{
1192 Q_D(Server);
1193 d->httpsSockets = httpsSocket;
1194 Q_EMIT changed();
1195}
1196
1197QStringList Server::httpsSocket() const
1198{
1199 Q_D(const Server);
1200 return d->httpsSockets;
1201}
1202
1203void Server::setFastcgiSocket(const QStringList &fastcgiSocket)
1204{
1205 Q_D(Server);
1206 d->fastcgiSockets = fastcgiSocket;
1207 Q_EMIT changed();
1208}
1209
1210QStringList Server::fastcgiSocket() const
1211{
1212 Q_D(const Server);
1213 return d->fastcgiSockets;
1214}
1215
1216void Server::setSocketAccess(const QString &socketAccess)
1217{
1218 Q_D(Server);
1219 d->socketAccess = socketAccess;
1220 Q_EMIT changed();
1221}
1222
1223QString Server::socketAccess() const
1224{
1225 Q_D(const Server);
1226 return d->socketAccess;
1227}
1228
1229void Server::setSocketTimeout(int timeout)
1230{
1231 Q_D(Server);
1232 d->socketTimeout = timeout;
1233 Q_EMIT changed();
1234}
1235
1236int Server::socketTimeout() const
1237{
1238 Q_D(const Server);
1239 return d->socketTimeout;
1240}
1241
1242void Server::setChdir2(const QString &chdir2)
1243{
1244 Q_D(Server);
1245 d->chdir2 = chdir2;
1246 Q_EMIT changed();
1247}
1248
1249QString Server::chdir2() const
1250{
1251 Q_D(const Server);
1252 return d->chdir2;
1253}
1254
1255void Server::setIni(const QStringList &files)
1256{
1257 Q_D(Server);
1258 d->ini.append(files);
1259 d->ini.removeDuplicates();
1260 Q_EMIT changed();
1261
1262 for (const QString &file : files) {
1263 if (!d->configLoaded.contains(file)) {
1264 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Ini);
1265 if (!d->configToLoad.contains(fileToLoad)) {
1266 qCDebug(CUTELYST_SERVER) << "Enqueue INI config file:" << file;
1267 d->configToLoad.enqueue(fileToLoad);
1268 }
1269 }
1270 }
1271
1272 d->loadConfig();
1273}
1274
1276{
1277 Q_D(const Server);
1278 return d->ini;
1279}
1280
1281void Server::setJson(const QStringList &files)
1282{
1283 Q_D(Server);
1284 d->json.append(files);
1285 d->json.removeDuplicates();
1286 Q_EMIT changed();
1287
1288 for (const QString &file : files) {
1289 if (!d->configLoaded.contains(file)) {
1290 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Json);
1291 if (!d->configToLoad.contains(fileToLoad)) {
1292 qCDebug(CUTELYST_SERVER) << "Enqueue JSON config file:" << file;
1293 d->configToLoad.enqueue(fileToLoad);
1294 }
1295 }
1296 }
1297
1298 d->loadConfig();
1299}
1300
1302{
1303 Q_D(const Server);
1304 return d->json;
1305}
1306
1307void Server::setStaticMap(const QStringList &staticMap)
1308{
1309 Q_D(Server);
1310 d->staticMaps = staticMap;
1311 Q_EMIT changed();
1312}
1313
1314QStringList Server::staticMap() const
1315{
1316 Q_D(const Server);
1317 return d->staticMaps;
1318}
1319
1320void Server::setStaticMap2(const QStringList &staticMap)
1321{
1322 Q_D(Server);
1323 d->staticMaps2 = staticMap;
1324 Q_EMIT changed();
1325}
1326
1327QStringList Server::staticMap2() const
1328{
1329 Q_D(const Server);
1330 return d->staticMaps2;
1331}
1332
1333void Server::setMaster(bool enable)
1334{
1335 Q_D(Server);
1336 if (!qEnvironmentVariableIsSet("CUTELYST_SERVER_IGNORE_MASTER")) {
1337 d->master = enable;
1338 }
1339 Q_EMIT changed();
1340}
1341
1342bool Server::master() const
1343{
1344 Q_D(const Server);
1345 return d->master;
1346}
1347
1348void Server::setAutoReload(bool enable)
1349{
1350 Q_D(Server);
1351 if (enable) {
1352 d->autoReload = true;
1353 }
1354 Q_EMIT changed();
1355}
1356
1357bool Server::autoReload() const
1358{
1359 Q_D(const Server);
1360 return d->autoReload;
1361}
1362
1363void Server::setTouchReload(const QStringList &files)
1364{
1365 Q_D(Server);
1366 d->touchReload = files;
1367 Q_EMIT changed();
1368}
1369
1370QStringList Server::touchReload() const
1371{
1372 Q_D(const Server);
1373 return d->touchReload;
1374}
1375
1376void Server::setListenQueue(int size)
1377{
1378 Q_D(Server);
1379 d->listenQueue = size;
1380 Q_EMIT changed();
1381}
1382
1383int Server::listenQueue() const
1384{
1385 Q_D(const Server);
1386 return d->listenQueue;
1387}
1388
1389void Server::setBufferSize(int size)
1390{
1391 Q_D(Server);
1392 if (size < 4096) {
1393 qCWarning(CUTELYST_SERVER) << "Buffer size must be at least 4096 bytes, ignoring";
1394 return;
1395 }
1396 d->bufferSize = size;
1397 Q_EMIT changed();
1398}
1399
1400int Server::bufferSize() const
1401{
1402 Q_D(const Server);
1403 return d->bufferSize;
1404}
1405
1406void Server::setPostBuffering(qint64 size)
1407{
1408 Q_D(Server);
1409 d->postBuffering = size;
1410 Q_EMIT changed();
1411}
1412
1413qint64 Server::postBuffering() const
1414{
1415 Q_D(const Server);
1416 return d->postBuffering;
1417}
1418
1419void Server::setPostBufferingBufsize(qint64 size)
1420{
1421 Q_D(Server);
1422 if (size < 4096) {
1423 qCWarning(CUTELYST_SERVER) << "Post buffer size must be at least 4096 bytes, ignoring";
1424 return;
1425 }
1426 d->postBufferingBufsize = size;
1427 Q_EMIT changed();
1428}
1429
1430qint64 Server::postBufferingBufsize() const
1431{
1432 Q_D(const Server);
1433 return d->postBufferingBufsize;
1434}
1435
1436void Server::setTcpNodelay(bool enable)
1437{
1438 Q_D(Server);
1439 d->tcpNodelay = enable;
1440 Q_EMIT changed();
1441}
1442
1443bool Server::tcpNodelay() const
1444{
1445 Q_D(const Server);
1446 return d->tcpNodelay;
1447}
1448
1449void Server::setSoKeepalive(bool enable)
1450{
1451 Q_D(Server);
1452 d->soKeepalive = enable;
1453 Q_EMIT changed();
1454}
1455
1456bool Server::soKeepalive() const
1457{
1458 Q_D(const Server);
1459 return d->soKeepalive;
1460}
1461
1462void Server::setSocketSndbuf(int value)
1463{
1464 Q_D(Server);
1465 d->socketSendBuf = value;
1466 Q_EMIT changed();
1467}
1468
1469int Server::socketSndbuf() const
1470{
1471 Q_D(const Server);
1472 return d->socketSendBuf;
1473}
1474
1475void Server::setSocketRcvbuf(int value)
1476{
1477 Q_D(Server);
1478 d->socketReceiveBuf = value;
1479 Q_EMIT changed();
1480}
1481
1482int Server::socketRcvbuf() const
1483{
1484 Q_D(const Server);
1485 return d->socketReceiveBuf;
1486}
1487
1488void Server::setWebsocketMaxSize(int value)
1489{
1490 Q_D(Server);
1491 d->websocketMaxSize = value * 1024;
1492 Q_EMIT changed();
1493}
1494
1495int Server::websocketMaxSize() const
1496{
1497 Q_D(const Server);
1498 return d->websocketMaxSize / 1024;
1499}
1500
1501void Server::setPidfile(const QString &file)
1502{
1503 Q_D(Server);
1504 d->pidfile = file;
1505 Q_EMIT changed();
1506}
1507
1509{
1510 Q_D(const Server);
1511 return d->pidfile;
1512}
1513
1514void Server::setPidfile2(const QString &file)
1515{
1516 Q_D(Server);
1517 d->pidfile2 = file;
1518 Q_EMIT changed();
1519}
1520
1522{
1523 Q_D(const Server);
1524 return d->pidfile2;
1525}
1526
1527void Server::setUid(const QString &uid)
1528{
1529#ifdef Q_OS_UNIX
1530 Q_D(Server);
1531 d->uid = uid;
1532 Q_EMIT changed();
1533#endif
1534}
1535
1536QString Server::uid() const
1537{
1538 Q_D(const Server);
1539 return d->uid;
1540}
1541
1542void Server::setGid(const QString &gid)
1543{
1544#ifdef Q_OS_UNIX
1545 Q_D(Server);
1546 d->gid = gid;
1547 Q_EMIT changed();
1548#endif
1549}
1550
1551QString Server::gid() const
1552{
1553 Q_D(const Server);
1554 return d->gid;
1555}
1556
1557void Server::setNoInitgroups(bool enable)
1558{
1559#ifdef Q_OS_UNIX
1560 Q_D(Server);
1561 d->noInitgroups = enable;
1562 Q_EMIT changed();
1563#endif
1564}
1565
1566bool Server::noInitgroups() const
1567{
1568 Q_D(const Server);
1569 return d->noInitgroups;
1570}
1571
1572void Server::setChownSocket(const QString &chownSocket)
1573{
1574#ifdef Q_OS_UNIX
1575 Q_D(Server);
1576 d->chownSocket = chownSocket;
1577 Q_EMIT changed();
1578#endif
1579}
1580
1581QString Server::chownSocket() const
1582{
1583 Q_D(const Server);
1584 return d->chownSocket;
1585}
1586
1587void Server::setUmask(const QString &value)
1588{
1589#ifdef Q_OS_UNIX
1590 Q_D(Server);
1591 d->umask = value;
1592 Q_EMIT changed();
1593#endif
1594}
1595
1596QString Server::umask() const
1597{
1598 Q_D(const Server);
1599 return d->umask;
1600}
1601
1602void Server::setCpuAffinity(int value)
1603{
1604#ifdef Q_OS_UNIX
1605 Q_D(Server);
1606 d->cpuAffinity = value;
1607 Q_EMIT changed();
1608#endif
1609}
1610
1611int Server::cpuAffinity() const
1612{
1613 Q_D(const Server);
1614 return d->cpuAffinity;
1615}
1616
1617void Server::setReusePort(bool enable)
1618{
1619#ifdef Q_OS_LINUX
1620 Q_D(Server);
1621 d->reusePort = enable;
1622 Q_EMIT changed();
1623#else
1624 Q_UNUSED(enable);
1625#endif
1626}
1627
1628bool Server::reusePort() const
1629{
1630 Q_D(const Server);
1631 return d->reusePort;
1632}
1633
1634void Server::setLazy(bool enable)
1635{
1636 Q_D(Server);
1637 d->lazy = enable;
1638 Q_EMIT changed();
1639}
1640
1641bool Server::lazy() const
1642{
1643 Q_D(const Server);
1644 return d->lazy;
1645}
1646
1647void Server::setUsingFrontendProxy(bool enable)
1648{
1649 Q_D(Server);
1650 d->usingFrontendProxy = enable;
1651 Q_EMIT changed();
1652}
1653
1654bool Server::usingFrontendProxy() const
1655{
1656 Q_D(const Server);
1657 return d->usingFrontendProxy;
1658}
1659
1660QVariantMap Server::config() const noexcept
1661{
1662 Q_D(const Server);
1663 return d->config;
1664}
1665
1666bool ServerPrivate::setupApplication()
1667{
1668 Cutelyst::Application *localApp = app;
1669
1670 Q_Q(Server);
1671
1672 if (!localApp) {
1673 std::cout << "Loading application: " << application.toLatin1().constData() << '\n';
1674 QPluginLoader loader(application);
1676 if (!loader.load()) {
1677 qCCritical(CUTELYST_SERVER) << "Could not load application:" << loader.errorString();
1678 return false;
1679 }
1680
1681 QObject *instance = loader.instance();
1682 if (!instance) {
1683 qCCritical(CUTELYST_SERVER) << "Could not get a QObject instance: %s\n"
1684 << loader.errorString();
1685 return false;
1686 }
1687
1688 localApp = qobject_cast<Cutelyst::Application *>(instance);
1689 if (!localApp) {
1690 qCCritical(CUTELYST_SERVER)
1691 << "Could not cast Cutelyst::Application from instance: %s\n"
1692 << loader.errorString();
1693 return false;
1694 }
1695
1696 // Sets the application name with the name from our library
1697 // if (QCoreApplication::applicationName() == applicationName) {
1698 // QCoreApplication::setApplicationName(QString::fromLatin1(app->metaObject()->className()));
1699 // }
1700 qCDebug(CUTELYST_SERVER) << "Loaded application: " << QCoreApplication::applicationName();
1701 }
1702
1703 if (!chdir2.isEmpty()) {
1704 std::cout << "Changing directory2 to: " << chdir2.toLatin1().constData() << '\n';
1705 if (!QDir::setCurrent(chdir2)) {
1706 Q_EMIT q->errorOccured(QString::fromLatin1("Failed to chdir2 to: '%s'")
1707 .arg(QString::fromLatin1(chdir2.toLatin1().constData())));
1708 return false;
1709 }
1710 }
1711
1712 if (threads > 1) {
1713 engine = createEngine(localApp, 0);
1714 for (int i = 1; i < threads; ++i) {
1715 if (createEngine(localApp, i)) {
1716 ++workersNotRunning;
1717 }
1718 }
1719 } else {
1720 engine = createEngine(localApp, 0);
1721 workersNotRunning = 1;
1722 }
1723
1724 if (!engine) {
1725 std::cerr << "Application failed to init, cheaping..." << '\n';
1726 return false;
1727 }
1728
1729 return true;
1730}
1731
1732void ServerPrivate::engineShutdown(ServerEngine *engine)
1733{
1734 const auto engineThread = engine->thread();
1735 if (QThread::currentThread() != engineThread) {
1736 connect(engineThread, &QThread::finished, this, [this, engine] {
1737 engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1738 checkEngineShutdown();
1739 });
1740 engineThread->quit();
1741 } else {
1742 engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1743 }
1744
1745 checkEngineShutdown();
1746}
1747
1748void ServerPrivate::checkEngineShutdown()
1749{
1750 if (engines.empty()) {
1751 if (userEventLoop) {
1752 Q_Q(Server);
1753 Q_EMIT q->stopped();
1754 } else {
1755 QTimer::singleShot(std::chrono::seconds{0}, this, [] { qApp->exit(15); });
1756 }
1757 }
1758}
1759
1760void ServerPrivate::workerStarted()
1761{
1762 Q_Q(Server);
1763
1764 // All workers have started
1765 if (--workersNotRunning == 0) {
1766 Q_EMIT q->ready();
1767 }
1768}
1769
1770bool ServerPrivate::postFork(int workerId)
1771{
1772 Q_Q(Server);
1773
1774 if (lazy) {
1775 if (!setupApplication()) {
1776 Q_EMIT q->errorOccured(qtTrId("cutelystd-err-fail-setup-app"));
1777 return false;
1778 }
1779 }
1780
1781 if (engines.size() > 1) {
1782 qCDebug(CUTELYST_SERVER) << "Starting threads";
1783 }
1784
1785 for (ServerEngine *engine : engines) {
1786 QThread *thread = engine->thread();
1787 if (thread != qApp->thread()) {
1788#ifdef Q_OS_LINUX
1789 if (!qEnvironmentVariableIsSet("CUTELYST_QT_EVENT_LOOP")) {
1790 // NOLINTNEXTLINE
1791 thread->setEventDispatcher(new EventDispatcherEPoll);
1792 }
1793#endif
1794
1795 thread->start();
1796 }
1797 }
1798
1799 Q_EMIT postForked(workerId);
1800
1801 QTimer::singleShot(std::chrono::seconds{1}, this, [=]() {
1802 // THIS IS NEEDED when
1803 // --master --threads N --experimental-thread-balancer
1804 // for some reason sometimes the balancer doesn't get
1805 // the ready signal (which stays on event loop queue)
1806 // from TcpServer and doesn't starts listening.
1807 qApp->processEvents();
1808 });
1809
1810 return true;
1811}
1812
1813bool ServerPrivate::writePidFile(const QString &filename)
1814{
1815 if (filename.isEmpty()) {
1816 return true;
1817 }
1818
1819 QFile file(filename);
1820 if (!file.open(QFile::WriteOnly | QFile::Text)) {
1821 std::cerr << "Failed write pid file " << qPrintable(filename) << '\n';
1822 return false;
1823 }
1824
1825 std::cout << "Writing pidfile to " << qPrintable(filename) << '\n';
1827
1828 return true;
1829}
1830
1831ServerEngine *ServerPrivate::createEngine(Application *app, int workerCore)
1832{
1833 Q_Q(Server);
1834
1835 // If threads is greater than 1 we need a new application instance
1836 if (workerCore > 0) {
1837 app = qobject_cast<Application *>(app->metaObject()->newInstance());
1838 if (!app) {
1839 qFatal("*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
1840 "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
1841 }
1842 }
1843
1844 auto engine = new ServerEngine(app, workerCore, opt, q);
1845 connect(this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1846 connect(
1847 this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, Qt::QueuedConnection);
1848 connect(engine,
1849 &ServerEngine::shutdownCompleted,
1850 this,
1851 &ServerPrivate::engineShutdown,
1853 connect(
1854 engine, &ServerEngine::started, this, &ServerPrivate::workerStarted, Qt::QueuedConnection);
1855
1856 engine->setConfig(config);
1857 engine->setServers(servers);
1858 if (!engine->init()) {
1859 std::cerr << "Application failed to init(), cheaping core: " << workerCore << '\n';
1860 delete engine;
1861 return nullptr;
1862 }
1863
1864 engines.push_back(engine);
1865
1866 // If threads is greater than 1 we need a new thread
1867 if (workerCore > 0) {
1868 // To make easier for engines to clean up
1869 // the NEW app must be a child of it
1870 app->setParent(engine);
1871
1872 auto thread = new QThread(this);
1873 engine->moveToThread(thread);
1874 } else {
1875 engine->setParent(this);
1876 }
1877
1878 return engine;
1879}
1880
1881void ServerPrivate::loadConfig()
1882{
1883 if (loadingConfig) {
1884 return;
1885 }
1886
1887 loadingConfig = true;
1888
1889 if (configToLoad.isEmpty()) {
1890 loadingConfig = false;
1891 return;
1892 }
1893
1894 auto fileToLoad = configToLoad.dequeue();
1895
1896 if (fileToLoad.first.isEmpty()) {
1897 qCWarning(CUTELYST_SERVER) << "Can not load config from empty config file name";
1898 loadingConfig = false;
1899 return;
1900 }
1901
1902 if (configLoaded.contains(fileToLoad.first)) {
1903 loadingConfig = false;
1904 return;
1905 }
1906
1907 configLoaded.append(fileToLoad.first);
1908
1909 QVariantMap loadedConfig;
1910 switch (fileToLoad.second) {
1911 case ConfigFormat::Ini:
1912 qCInfo(CUTELYST_SERVER) << "Loading INI configuratin:" << fileToLoad.first;
1913 loadedConfig = Engine::loadIniConfig(fileToLoad.first);
1914 break;
1915 case ConfigFormat::Json:
1916 qCInfo(CUTELYST_SERVER) << "Loading JSON configuration:" << fileToLoad.first;
1917 loadedConfig = Engine::loadJsonConfig(fileToLoad.first);
1918 break;
1919 }
1920
1921 for (const auto &[key, value] : std::as_const(loadedConfig).asKeyValueRange()) {
1922 if (config.contains(key)) {
1923 QVariantMap currentMap = config.value(key).toMap();
1924 const QVariantMap loadedMap = value.toMap();
1925 for (const auto &[key, value] : loadedMap.asKeyValueRange()) {
1926 currentMap.insert(key, value);
1927 }
1928 config.insert(key, currentMap);
1929 } else {
1930 config.insert(key, value);
1931 }
1932 }
1933
1934 QVariantMap sessionConfig = loadedConfig.value(u"server"_s).toMap();
1935
1936 applyConfig(sessionConfig);
1937
1938 opt.insert(sessionConfig);
1939
1940 loadingConfig = false;
1941
1942 if (!configToLoad.empty()) {
1943 loadConfig();
1944 }
1945}
1946
1947void ServerPrivate::applyConfig(const QVariantMap &config)
1948{
1949 Q_Q(Server);
1950
1951 for (const auto &[key, value] : config.asKeyValueRange()) {
1952 QString normKey = key;
1953 normKey.replace(u'-', u'_');
1954
1955 int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
1956 if (ix == -1) {
1957 continue;
1958 }
1959
1960 const QMetaProperty prop = q->metaObject()->property(ix);
1961 if (prop.userType() == value.userType()) {
1962 if (prop.userType() == QMetaType::QStringList) {
1963 const QStringList currentValues = prop.read(q).toStringList();
1964 prop.write(q, currentValues + value.toStringList());
1965 } else {
1966 prop.write(q, value);
1967 }
1968 } else if (prop.userType() == QMetaType::QStringList) {
1969 const QStringList currentValues = prop.read(q).toStringList();
1970 prop.write(q, currentValues + QStringList{value.toString()});
1971 } else {
1972 prop.write(q, value);
1973 }
1974 }
1975}
1976
1977Protocol *ServerPrivate::getHttpProto()
1978{
1979 Q_Q(Server);
1980 if (!protoHTTP) {
1981 if (upgradeH2c) {
1982 protoHTTP = new ProtocolHttp(q, getHttp2Proto());
1983 } else {
1984 protoHTTP = new ProtocolHttp(q);
1985 }
1986 }
1987 return protoHTTP;
1988}
1989
1990ProtocolHttp2 *ServerPrivate::getHttp2Proto()
1991{
1992 Q_Q(Server);
1993 if (!protoHTTP2) {
1994 protoHTTP2 = new ProtocolHttp2(q);
1995 }
1996 return protoHTTP2;
1997}
1998
1999Protocol *ServerPrivate::getFastCgiProto()
2000{
2001 Q_Q(Server);
2002 if (!protoFCGI) {
2003 protoFCGI = new ProtocolFastCGI(q);
2004 }
2005 return protoFCGI;
2006}
2007
2008#include "moc_server.cpp"
2009#include "moc_server_p.cpp"
The Cutelyst application.
Definition application.h:66
static QVariantMap loadJsonConfig(const QString &filename)
Definition engine.cpp:158
void setConfig(const QVariantMap &config)
Definition engine.cpp:128
static QVariantMap loadIniConfig(const QString &filename)
Definition engine.cpp:134
virtual bool init() override
Implements a web server.
Definition server.h:60
QString application
Definition server.h:134
QString pidfile2
Definition server.h:459
void errorOccured(const QString &error)
QString chdir
Definition server.h:167
bool start(Cutelyst::Application *app=nullptr)
Definition server.cpp:831
virtual ~Server()
Definition server.cpp:89
QString gid
Definition server.h:477
QString threads
Definition server.h:150
QString pidfile
Definition server.h:451
int exec(Cutelyst::Application *app=nullptr)
Definition server.cpp:677
QString processes
Definition server.h:159
void parseCommandLine(const QStringList &args)
Definition server.cpp:95
QStringList json
Definition server.h:299
Server(QObject *parent=nullptr)
Definition server.cpp:45
QString chdir2
Definition server.h:251
QString umask
Definition server.h:504
QString uid
Definition server.h:468
QStringList ini
Definition server.h:272
QVariantMap config() const noexcept
Definition server.cpp:1660
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
QByteArray number(double n, char format, int precision)
QCommandLineOption addHelpOption()
bool addOption(const QCommandLineOption &option)
QCommandLineOption addVersionOption()
bool isSet(const QCommandLineOption &option) const const
void process(const QCoreApplication &app)
void setApplicationDescription(const QString &description)
void showHelp(int exitCode)
QString value(const QCommandLineOption &option) const const
QStringList values(const QCommandLineOption &option) const const
void addLibraryPath(const QString &path)
qint64 applicationPid()
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
bool setCurrent(const QString &path)
ResolveAllSymbolsHint
bool removeOne(const AT &t)
typedef SocketOptions
bool removeServer(const QString &name)
QObject * newInstance(Args &&... arguments) const const
QVariant read(const QObject *object) const const
int userType() const const
bool write(QObject *object, QVariant &&v) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual const QMetaObject * metaObject() const const
void moveToThread(QThread *targetThread)
void setParent(QObject *parent)
QThread * thread() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
qlonglong toLongLong(bool *ok, int base) const const
uint toUInt(bool *ok, int base) const const
CaseInsensitive
DirectConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
void finished()
int idealThreadCount()
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
void start(QThread::Priority priority)
QStringList toStringList() const const