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