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
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
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
83 for (ServerEngine *engine : findChildren<ServerEngine *>(Qt::FindDirectChildrenOnly)) {
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 = d->lastListenError.isEmpty()
760 ? QStringLiteral("No specified sockets were able to be opened")
761 : d->lastListenError;
762 Q_EMIT errorOccured(error);
763 return 1; // No sockets has been opened
764 }
765 }
766
767 if (!d->writePidFile(d->pidfile)) {
768 //% "Failed to write pidfile %1"
769 Q_EMIT errorOccured(qtTrId("cutelystd-err-write-pidfile").arg(d->pidfile));
770 }
771
772#ifdef Q_OS_UNIX
773 bool isListeningLocalSockets = false;
774 if (!d->chownSocket.isEmpty()) {
775 if (!d->listenLocalSockets()) {
776 //% "Error on opening local sockets"
777 Q_EMIT errorOccured(qtTrId("cutelystd-err-open-local-socket"));
778 return 1;
779 }
780 isListeningLocalSockets = true;
781 }
782
783 if (!d->umask.isEmpty() && !UnixFork::setUmask(d->umask.toLatin1())) {
784 return 1;
785 }
786
787 if (!UnixFork::setGidUid(d->gid, d->uid, d->noInitgroups)) {
788 //% "Error on setting GID or UID"
789 Q_EMIT errorOccured(qtTrId("cutelystd-err-setgiduid"));
790 return 1;
791 }
792
793 if (!isListeningLocalSockets) {
794#endif
795 d->listenLocalSockets();
796#ifdef Q_OS_UNIX
797 }
798#endif
799
800 if (d->reusePort) {
801 if (!d->listenTcpSockets()) {
802 const QString error = d->lastListenError.isEmpty()
803 ? QStringLiteral("No specified sockets were able to be opened")
804 : d->lastListenError;
805 Q_EMIT errorOccured(error);
806 return 1; // No sockets has been opened
807 }
808 }
809
810 if (d->servers.empty()) {
811 std::cout << "Please specify a socket to listen to" << '\n';
812 //% "No socket specified"
813 Q_EMIT errorOccured(qtTrId("cutelystd-err-no-socket-specified"));
814 return 1;
815 }
816
817 d->writePidFile(d->pidfile2);
818
819 if (!d->chdir.isEmpty()) {
820 std::cout << "Changing directory to: " << d->chdir.toLatin1().constData() << '\n';
821 if (!QDir::setCurrent(d->chdir)) {
822 Q_EMIT errorOccured(QString::fromLatin1("Failed to chdir to: '%s'")
823 .arg(QString::fromLatin1(d->chdir.toLatin1().constData())));
824 return 1;
825 }
826 }
827
828 d->app = app;
829
830 if (!d->lazy) {
831 if (!d->setupApplication()) {
832 //% "Failed to setup Application"
833 Q_EMIT errorOccured(qtTrId("cutelystd-err-fail-setup-app"));
834 return 1;
835 }
836 }
837
838 if (d->userEventLoop) {
839 d->postFork(0);
840 return 0;
841 }
842
843 ret = d->genericFork->exec(d->lazy, d->master);
844
845 return ret;
846}
847
849{
850 Q_D(Server);
851
852 if (d->mainEngine) {
854 QStringLiteral("Server not fully stopped. Wait for shutdown to complete."));
855 return false;
856 }
857
858 d->processes = 0;
859 d->master = false;
860 d->lazy = false;
861 d->userEventLoop = true;
862 d->workersNotRunning = 1;
863 d->lastListenError.clear();
864#ifdef Q_OS_UNIX
865 d->uid.clear();
866 d->gid.clear();
867#endif
868 qputenv("CUTELYST_SERVER_IGNORE_MASTER", QByteArrayLiteral("1"));
869
870 if (exec(app) == 0) {
871 return true;
872 }
873
874 return false;
875}
876
878{
879 Q_D(Server);
880 if (d->userEventLoop) {
881 Q_EMIT d->shutdown();
882 }
883}
884
885ServerPrivate::~ServerPrivate()
886{
887 delete protoHTTP;
888 delete protoHTTP2;
889 delete protoFCGI;
890}
891
892bool ServerPrivate::listenTcpSockets()
893{
894 lastListenError.clear();
895
896 if (httpSockets.isEmpty() && httpsSockets.isEmpty() && http2Sockets.isEmpty() &&
897 fastcgiSockets.isEmpty()) {
898 // no sockets to listen to
899 return false;
900 }
901
902 // HTTP
903 bool httpOk = std::ranges::all_of(httpSockets, [this](const auto &socket) {
904 return listenTcp(socket, getHttpProto(), false);
905 });
906 if (!httpOk) {
907 return false;
908 }
909
910 // HTTPS
911 bool httpsOk = std::ranges::all_of(httpsSockets, [this](const auto &socket) {
912 return listenTcp(socket, getHttpProto(), true);
913 });
914 if (!httpsOk) {
915 return false;
916 }
917
918 // HTTP/2
919 bool http2Ok = std::ranges::all_of(http2Sockets, [this](const auto &socket) {
920 return listenTcp(socket, getHttp2Proto(), false);
921 });
922 if (!http2Ok) {
923 return false;
924 }
925
926 // FastCGI
927 bool allOk = std::ranges::all_of(fastcgiSockets, [this](const QString &socket) {
928 return listenTcp(socket, getFastCgiProto(), false);
929 });
930
931 return allOk;
932}
933
934bool ServerPrivate::listenTcp(const QString &line, Protocol *protocol, bool secure)
935{
936 Q_Q(Server);
937
938 if (line.startsWith(u'/')) {
939 return true;
940 }
941
942 auto server = new TcpServerBalancer(q);
943 server->setBalancer(threadBalancer);
944 const bool ret = server->listen(line, protocol, secure);
945
946 if (!ret || !server->socketDescriptor()) {
947 const QString err =
948 server->bindError().isEmpty() ? server->errorString() : server->bindError();
949 if (!ret) {
950 lastListenError = QStringLiteral("Failed to listen on %1: %2").arg(line, err);
951 } else {
952 lastListenError =
953 QStringLiteral("Failed to listen on %1: no socket descriptor").arg(line);
954 }
955 qCWarning(CUTELYST_SERVER) << lastListenError;
956 delete server;
957 return false;
958 }
959
960 auto qEnum = Protocol::staticMetaObject.enumerator(0);
961 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
962 << QByteArray::number(static_cast<int>(servers.size())).constData()
963 << " bound to TCP address " << server->serverName().constData() << " fd "
964 << QByteArray::number(server->socketDescriptor()).constData() << '\n';
965 servers.emplace_back(server);
966 return true;
967}
968
969bool ServerPrivate::listenLocalSockets()
970{
971 QStringList http = httpSockets;
972 QStringList http2 = http2Sockets;
973 QStringList fastcgi = fastcgiSockets;
974
975#ifdef Q_OS_LINUX
976 Q_Q(Server);
977
978 std::vector<int> fds = systemdNotify::listenFds();
979 for (int fd : fds) {
980 auto server = new LocalServer(q, this);
981 if (server->listen(fd)) {
982 const QString name = server->serverName();
983 const QString fullName = server->fullServerName();
984
985 Protocol *protocol;
986 if (http.removeOne(fullName) || http.removeOne(name)) {
987 protocol = getHttpProto();
988 } else if (http2.removeOne(fullName) || http2.removeOne(name)) {
989 protocol = getHttp2Proto();
990 } else if (fastcgi.removeOne(fullName) || fastcgi.removeOne(name)) {
991 protocol = getFastCgiProto();
992 } else {
993 std::cerr << "systemd activated socket does not match any configured socket"
994 << '\n';
995 return false;
996 }
997 server->setProtocol(protocol);
998 server->pauseAccepting();
999
1000 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1001 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
1002 << QByteArray::number(static_cast<int>(servers.size())).constData()
1003 << " bound to LOCAL address " << qPrintable(fullName) << " fd "
1004 << QByteArray::number(server->socket()).constData() << '\n';
1005 servers.push_back(server);
1006 } else {
1007 std::cerr << "Failed to listen on activated LOCAL FD: "
1008 << QByteArray::number(fd).constData() << " : "
1009 << qPrintable(server->errorString()) << '\n';
1010 return false;
1011 }
1012 }
1013#endif
1014
1015 bool ret = false;
1016 const auto httpConst = http;
1017 for (const auto &socket : httpConst) {
1018 ret |= listenLocal(socket, getHttpProto());
1019 }
1020
1021 const auto http2Const = http2;
1022 for (const auto &socket : http2Const) {
1023 ret |= listenLocal(socket, getHttp2Proto());
1024 }
1025
1026 const auto fastcgiConst = fastcgi;
1027 for (const auto &socket : fastcgiConst) {
1028 ret |= listenLocal(socket, getFastCgiProto());
1029 }
1030
1031 return ret;
1032}
1033
1034bool ServerPrivate::listenLocal(const QString &line, Protocol *protocol)
1035{
1036 Q_Q(Server);
1037
1038 bool ret = true;
1039 if (line.startsWith(u'/')) {
1040 auto server = new LocalServer(q, this);
1041 server->setProtocol(protocol);
1042 if (!socketAccess.isEmpty()) {
1044 if (socketAccess.contains(u'u')) {
1046 }
1047
1048 if (socketAccess.contains(u'g')) {
1050 }
1051
1052 if (socketAccess.contains(u'o')) {
1054 }
1055 server->setSocketOptions(options);
1056 }
1057
1059 server->setListenBacklogSize(listenQueue);
1060 ret = server->listen(line);
1061 server->pauseAccepting();
1062
1063 if (!ret || !server->socket()) {
1064 std::cerr << "Failed to listen on LOCAL: " << qPrintable(line) << " : "
1065 << qPrintable(server->errorString()) << '\n';
1066 return false;
1067 }
1068
1069#ifdef Q_OS_UNIX
1070 if (!chownSocket.isEmpty()) {
1071 UnixFork::chownSocket(line, chownSocket);
1072 }
1073#endif
1074 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1075 std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
1076 << QByteArray::number(static_cast<int>(servers.size())).constData()
1077 << " bound to LOCAL address " << qPrintable(line) << " fd "
1078 << QByteArray::number(server->socket()).constData() << '\n';
1079 servers.push_back(server);
1080 }
1081
1082 return ret;
1083}
1084
1085void Server::setApplication(const QString &application)
1086{
1087 Q_D(Server);
1088
1089 QPluginLoader loader(application);
1090 if (loader.fileName().isEmpty()) {
1091 d->application = application;
1092 } else {
1093 // We use the loader filename since it can provide
1094 // the suffix for the file watcher
1095 d->application = loader.fileName();
1096 }
1097 Q_EMIT changed();
1098}
1099
1101{
1102 Q_D(const Server);
1103 return d->application;
1104}
1105
1106void Server::setThreads(const QString &threads)
1107{
1108 Q_D(Server);
1109 if (threads.compare(u"auto", Qt::CaseInsensitive) == 0) {
1110 d->threads = -1;
1111 } else {
1112 d->threads = qMax(1, threads.toInt());
1113 }
1114 Q_EMIT changed();
1115}
1116
1118{
1119 Q_D(const Server);
1120 if (d->threads == -1) {
1121 return u"auto"_s;
1122 }
1123 return QString::number(d->threads);
1124}
1125
1126void Server::setProcesses(const QString &process)
1127{
1128#ifdef Q_OS_UNIX
1129 Q_D(Server);
1130 if (process.compare(u"auto", Qt::CaseInsensitive) == 0) {
1131 d->processes = -1;
1132 } else {
1133 d->processes = process.toInt();
1134 }
1135 Q_EMIT changed();
1136#endif
1137}
1138
1140{
1141 Q_D(const Server);
1142 if (d->processes == -1) {
1143 return u"auto"_s;
1144 }
1145 return QString::number(d->processes);
1146}
1147
1148void Server::setChdir(const QString &chdir)
1149{
1150 Q_D(Server);
1151 d->chdir = chdir;
1152 Q_EMIT changed();
1153}
1154
1155QString Server::chdir() const
1156{
1157 Q_D(const Server);
1158 return d->chdir;
1159}
1160
1161void Server::setHttpSocket(const QStringList &httpSocket)
1162{
1163 Q_D(Server);
1164 d->httpSockets = httpSocket;
1165 Q_EMIT changed();
1166}
1167
1168QStringList Server::httpSocket() const
1169{
1170 Q_D(const Server);
1171 return d->httpSockets;
1172}
1173
1174void Server::setHttp2Socket(const QStringList &http2Socket)
1175{
1176 Q_D(Server);
1177 d->http2Sockets = http2Socket;
1178 Q_EMIT changed();
1179}
1180
1181QStringList Server::http2Socket() const
1182{
1183 Q_D(const Server);
1184 return d->http2Sockets;
1185}
1186
1187void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1188{
1189 Q_D(Server);
1190 d->http2HeaderTableSize = headerTableSize;
1191 Q_EMIT changed();
1192}
1193
1194quint32 Server::http2HeaderTableSize() const
1195{
1196 Q_D(const Server);
1197 return d->http2HeaderTableSize;
1198}
1199
1200void Server::setUpgradeH2c(bool enable)
1201{
1202 Q_D(Server);
1203 d->upgradeH2c = enable;
1204 Q_EMIT changed();
1205}
1206
1207bool Server::upgradeH2c() const
1208{
1209 Q_D(const Server);
1210 return d->upgradeH2c;
1211}
1212
1213void Server::setHttpsH2(bool enable)
1214{
1215 Q_D(Server);
1216 d->httpsH2 = enable;
1217 Q_EMIT changed();
1218}
1219
1220bool Server::httpsH2() const
1221{
1222 Q_D(const Server);
1223 return d->httpsH2;
1224}
1225
1226void Server::setHttpsSocket(const QStringList &httpsSocket)
1227{
1228 Q_D(Server);
1229 d->httpsSockets = httpsSocket;
1230 Q_EMIT changed();
1231}
1232
1233QStringList Server::httpsSocket() const
1234{
1235 Q_D(const Server);
1236 return d->httpsSockets;
1237}
1238
1239void Server::setFastcgiSocket(const QStringList &fastcgiSocket)
1240{
1241 Q_D(Server);
1242 d->fastcgiSockets = fastcgiSocket;
1243 Q_EMIT changed();
1244}
1245
1246QStringList Server::fastcgiSocket() const
1247{
1248 Q_D(const Server);
1249 return d->fastcgiSockets;
1250}
1251
1252void Server::setSocketAccess(const QString &socketAccess)
1253{
1254 Q_D(Server);
1255 d->socketAccess = socketAccess;
1256 Q_EMIT changed();
1257}
1258
1259QString Server::socketAccess() const
1260{
1261 Q_D(const Server);
1262 return d->socketAccess;
1263}
1264
1265void Server::setSocketTimeout(int timeout)
1266{
1267 Q_D(Server);
1268 d->socketTimeout = timeout;
1269 Q_EMIT changed();
1270}
1271
1272int Server::socketTimeout() const
1273{
1274 Q_D(const Server);
1275 return d->socketTimeout;
1276}
1277
1278void Server::setChdir2(const QString &chdir2)
1279{
1280 Q_D(Server);
1281 d->chdir2 = chdir2;
1282 Q_EMIT changed();
1283}
1284
1285QString Server::chdir2() const
1286{
1287 Q_D(const Server);
1288 return d->chdir2;
1289}
1290
1291void Server::setIni(const QStringList &files)
1292{
1293 Q_D(Server);
1294 d->ini.append(files);
1295 d->ini.removeDuplicates();
1296 Q_EMIT changed();
1297
1298 for (const QString &file : files) {
1299 if (!d->configLoaded.contains(file)) {
1300 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Ini);
1301 if (!d->configToLoad.contains(fileToLoad)) {
1302 qCDebug(CUTELYST_SERVER) << "Enqueue INI config file:" << file;
1303 d->configToLoad.enqueue(fileToLoad);
1304 }
1305 }
1306 }
1307
1308 d->loadConfig();
1309}
1310
1312{
1313 Q_D(const Server);
1314 return d->ini;
1315}
1316
1317void Server::setJson(const QStringList &files)
1318{
1319 Q_D(Server);
1320 d->json.append(files);
1321 d->json.removeDuplicates();
1322 Q_EMIT changed();
1323
1324 for (const QString &file : files) {
1325 if (!d->configLoaded.contains(file)) {
1326 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Json);
1327 if (!d->configToLoad.contains(fileToLoad)) {
1328 qCDebug(CUTELYST_SERVER) << "Enqueue JSON config file:" << file;
1329 d->configToLoad.enqueue(fileToLoad);
1330 }
1331 }
1332 }
1333
1334 d->loadConfig();
1335}
1336
1338{
1339 Q_D(const Server);
1340 return d->json;
1341}
1342
1343void Server::setStaticMap(const QStringList &staticMap)
1344{
1345 Q_D(Server);
1346 d->staticMaps = staticMap;
1347 Q_EMIT changed();
1348}
1349
1350QStringList Server::staticMap() const
1351{
1352 Q_D(const Server);
1353 return d->staticMaps;
1354}
1355
1356void Server::setStaticMap2(const QStringList &staticMap)
1357{
1358 Q_D(Server);
1359 d->staticMaps2 = staticMap;
1360 Q_EMIT changed();
1361}
1362
1363QStringList Server::staticMap2() const
1364{
1365 Q_D(const Server);
1366 return d->staticMaps2;
1367}
1368
1369void Server::setMaster(bool enable)
1370{
1371 Q_D(Server);
1372 if (!qEnvironmentVariableIsSet("CUTELYST_SERVER_IGNORE_MASTER")) {
1373 d->master = enable;
1374 }
1375 Q_EMIT changed();
1376}
1377
1378bool Server::master() const
1379{
1380 Q_D(const Server);
1381 return d->master;
1382}
1383
1384void Server::setAutoReload(bool enable)
1385{
1386 Q_D(Server);
1387 if (enable) {
1388 d->autoReload = true;
1389 }
1390 Q_EMIT changed();
1391}
1392
1393bool Server::autoReload() const
1394{
1395 Q_D(const Server);
1396 return d->autoReload;
1397}
1398
1399void Server::setTouchReload(const QStringList &files)
1400{
1401 Q_D(Server);
1402 d->touchReload = files;
1403 Q_EMIT changed();
1404}
1405
1406QStringList Server::touchReload() const
1407{
1408 Q_D(const Server);
1409 return d->touchReload;
1410}
1411
1412void Server::setListenQueue(int size)
1413{
1414 Q_D(Server);
1415 d->listenQueue = size;
1416 Q_EMIT changed();
1417}
1418
1419int Server::listenQueue() const
1420{
1421 Q_D(const Server);
1422 return d->listenQueue;
1423}
1424
1425void Server::setBufferSize(int size)
1426{
1427 Q_D(Server);
1428 if (size < 4096) {
1429 qCWarning(CUTELYST_SERVER) << "Buffer size must be at least 4096 bytes, ignoring";
1430 return;
1431 }
1432 d->bufferSize = size;
1433 Q_EMIT changed();
1434}
1435
1436int Server::bufferSize() const
1437{
1438 Q_D(const Server);
1439 return d->bufferSize;
1440}
1441
1442void Server::setPostBuffering(qint64 size)
1443{
1444 Q_D(Server);
1445 d->postBuffering = size;
1446 Q_EMIT changed();
1447}
1448
1449qint64 Server::postBuffering() const
1450{
1451 Q_D(const Server);
1452 return d->postBuffering;
1453}
1454
1455void Server::setPostBufferingBufsize(qint64 size)
1456{
1457 Q_D(Server);
1458 if (size < 4096) {
1459 qCWarning(CUTELYST_SERVER) << "Post buffer size must be at least 4096 bytes, ignoring";
1460 return;
1461 }
1462 d->postBufferingBufsize = size;
1463 Q_EMIT changed();
1464}
1465
1466qint64 Server::postBufferingBufsize() const
1467{
1468 Q_D(const Server);
1469 return d->postBufferingBufsize;
1470}
1471
1472void Server::setTcpNodelay(bool enable)
1473{
1474 Q_D(Server);
1475 d->tcpNodelay = enable;
1476 Q_EMIT changed();
1477}
1478
1479bool Server::tcpNodelay() const
1480{
1481 Q_D(const Server);
1482 return d->tcpNodelay;
1483}
1484
1485void Server::setSoKeepalive(bool enable)
1486{
1487 Q_D(Server);
1488 d->soKeepalive = enable;
1489 Q_EMIT changed();
1490}
1491
1492bool Server::soKeepalive() const
1493{
1494 Q_D(const Server);
1495 return d->soKeepalive;
1496}
1497
1498void Server::setSocketSndbuf(int value)
1499{
1500 Q_D(Server);
1501 d->socketSendBuf = value;
1502 Q_EMIT changed();
1503}
1504
1505int Server::socketSndbuf() const
1506{
1507 Q_D(const Server);
1508 return d->socketSendBuf;
1509}
1510
1511void Server::setSocketRcvbuf(int value)
1512{
1513 Q_D(Server);
1514 d->socketReceiveBuf = value;
1515 Q_EMIT changed();
1516}
1517
1518int Server::socketRcvbuf() const
1519{
1520 Q_D(const Server);
1521 return d->socketReceiveBuf;
1522}
1523
1524void Server::setWebsocketMaxSize(int value)
1525{
1526 Q_D(Server);
1527 d->websocketMaxSize = value * 1024;
1528 Q_EMIT changed();
1529}
1530
1531int Server::websocketMaxSize() const
1532{
1533 Q_D(const Server);
1534 return d->websocketMaxSize / 1024;
1535}
1536
1537void Server::setPidfile(const QString &file)
1538{
1539 Q_D(Server);
1540 d->pidfile = file;
1541 Q_EMIT changed();
1542}
1543
1545{
1546 Q_D(const Server);
1547 return d->pidfile;
1548}
1549
1550void Server::setPidfile2(const QString &file)
1551{
1552 Q_D(Server);
1553 d->pidfile2 = file;
1554 Q_EMIT changed();
1555}
1556
1558{
1559 Q_D(const Server);
1560 return d->pidfile2;
1561}
1562
1563void Server::setUid(const QString &uid)
1564{
1565#ifdef Q_OS_UNIX
1566 Q_D(Server);
1567 d->uid = uid;
1568 Q_EMIT changed();
1569#endif
1570}
1571
1572QString Server::uid() const
1573{
1574 Q_D(const Server);
1575 return d->uid;
1576}
1577
1578void Server::setGid(const QString &gid)
1579{
1580#ifdef Q_OS_UNIX
1581 Q_D(Server);
1582 d->gid = gid;
1583 Q_EMIT changed();
1584#endif
1585}
1586
1587QString Server::gid() const
1588{
1589 Q_D(const Server);
1590 return d->gid;
1591}
1592
1593void Server::setNoInitgroups(bool enable)
1594{
1595#ifdef Q_OS_UNIX
1596 Q_D(Server);
1597 d->noInitgroups = enable;
1598 Q_EMIT changed();
1599#endif
1600}
1601
1602bool Server::noInitgroups() const
1603{
1604 Q_D(const Server);
1605 return d->noInitgroups;
1606}
1607
1608void Server::setChownSocket(const QString &chownSocket)
1609{
1610#ifdef Q_OS_UNIX
1611 Q_D(Server);
1612 d->chownSocket = chownSocket;
1613 Q_EMIT changed();
1614#endif
1615}
1616
1617QString Server::chownSocket() const
1618{
1619 Q_D(const Server);
1620 return d->chownSocket;
1621}
1622
1623void Server::setUmask(const QString &value)
1624{
1625#ifdef Q_OS_UNIX
1626 Q_D(Server);
1627 d->umask = value;
1628 Q_EMIT changed();
1629#endif
1630}
1631
1632QString Server::umask() const
1633{
1634 Q_D(const Server);
1635 return d->umask;
1636}
1637
1638void Server::setCpuAffinity(int value)
1639{
1640#ifdef Q_OS_UNIX
1641 Q_D(Server);
1642 d->cpuAffinity = value;
1643 Q_EMIT changed();
1644#endif
1645}
1646
1647int Server::cpuAffinity() const
1648{
1649 Q_D(const Server);
1650 return d->cpuAffinity;
1651}
1652
1653void Server::setReusePort(bool enable)
1654{
1655#ifdef Q_OS_LINUX
1656 Q_D(Server);
1657 d->reusePort = enable;
1658 Q_EMIT changed();
1659#else
1660 Q_UNUSED(enable);
1661#endif
1662}
1663
1664bool Server::reusePort() const
1665{
1666 Q_D(const Server);
1667 return d->reusePort;
1668}
1669
1670void Server::setLazy(bool enable)
1671{
1672 Q_D(Server);
1673 d->lazy = enable;
1674 Q_EMIT changed();
1675}
1676
1677bool Server::lazy() const
1678{
1679 Q_D(const Server);
1680 return d->lazy;
1681}
1682
1683void Server::setUsingFrontendProxy(bool enable)
1684{
1685 Q_D(Server);
1686 d->usingFrontendProxy = enable;
1687 Q_EMIT changed();
1688}
1689
1690bool Server::usingFrontendProxy() const
1691{
1692 Q_D(const Server);
1693 return d->usingFrontendProxy;
1694}
1695
1696QVariantMap Server::config() const noexcept
1697{
1698 Q_D(const Server);
1699 return d->config;
1700}
1701
1702bool ServerPrivate::setupApplication()
1703{
1704 Cutelyst::Application *localApp = app;
1705
1706 Q_Q(Server);
1707
1708 if (userEventLoop) {
1709 qDeleteAll(engines);
1710 engines.clear();
1711 mainEngine = nullptr;
1712 for (ServerEngine *engine : q->findChildren<ServerEngine *>(Qt::FindDirectChildrenOnly)) {
1713 delete engine;
1714 }
1715 } else if (!engines.empty() || mainEngine) {
1716 qDeleteAll(engines);
1717 engines.clear();
1718 mainEngine = nullptr;
1719 }
1720
1721 if (!localApp) {
1722 std::cout << "Loading application: " << application.toLatin1().constData() << '\n';
1723 QPluginLoader loader(application);
1725 if (!loader.load()) {
1726 qCCritical(CUTELYST_SERVER) << "Could not load application:" << loader.errorString();
1727 return false;
1728 }
1729
1730 QObject *instance = loader.instance();
1731 if (!instance) {
1732 qCCritical(CUTELYST_SERVER) << "Could not get a QObject instance: %s\n"
1733 << loader.errorString();
1734 return false;
1735 }
1736
1737 localApp = qobject_cast<Cutelyst::Application *>(instance);
1738 if (!localApp) {
1739 qCCritical(CUTELYST_SERVER)
1740 << "Could not cast Cutelyst::Application from instance: %s\n"
1741 << loader.errorString();
1742 return false;
1743 }
1744
1745 // Sets the application name with the name from our library
1746 // if (QCoreApplication::applicationName() == applicationName) {
1747 // QCoreApplication::setApplicationName(QString::fromLatin1(app->metaObject()->className()));
1748 // }
1749 qCDebug(CUTELYST_SERVER) << "Loaded application: " << QCoreApplication::applicationName();
1750 }
1751
1752 if (!chdir2.isEmpty()) {
1753 std::cout << "Changing directory2 to: " << chdir2.toLatin1().constData() << '\n';
1754 if (!QDir::setCurrent(chdir2)) {
1755 Q_EMIT q->errorOccured(QString::fromLatin1("Failed to chdir2 to: '%s'")
1756 .arg(QString::fromLatin1(chdir2.toLatin1().constData())));
1757 return false;
1758 }
1759 }
1760
1761 if (threads > 1) {
1762 mainEngine = createEngine(localApp, 0);
1763 for (int i = 1; i < threads; ++i) {
1764 if (createEngine(localApp, i)) {
1765 ++workersNotRunning;
1766 }
1767 }
1768 } else {
1769 mainEngine = createEngine(localApp, 0);
1770 workersNotRunning = 1;
1771 }
1772
1773 if (!mainEngine) {
1774 std::cerr << "Application failed to init, cheaping..." << '\n';
1775 return false;
1776 }
1777
1778 return true;
1779}
1780
1781void ServerPrivate::engineShutdown(ServerEngine *engine)
1782{
1783 if (mainEngine == engine) {
1784 mainEngine = nullptr;
1785 }
1786
1787 const auto engineThread = engine->thread();
1788 if (QThread::currentThread() != engineThread) {
1789 connect(engineThread, &QThread::finished, this, [this, engine] {
1790 auto [first, last] = std::ranges::remove(engines, engine);
1791 engines.erase(first, last);
1792 if (userEventLoop) {
1793 delete engine;
1794 }
1795 checkEngineShutdown();
1796 });
1797 engineThread->quit();
1798 return;
1799 }
1800
1801 auto [first, last] = std::ranges::remove(engines, engine);
1802 engines.erase(first, last);
1803
1804 if (userEventLoop) {
1805 delete engine;
1806 }
1807
1808 checkEngineShutdown();
1809}
1810
1811void ServerPrivate::checkEngineShutdown()
1812{
1813 if (engines.empty()) {
1814 if (userEventLoop) {
1815 Q_Q(Server);
1816 Q_EMIT q->stopped();
1817 } else {
1818 QTimer::singleShot(std::chrono::seconds{0}, this, [] { qApp->exit(15); });
1819 }
1820 }
1821}
1822
1823void ServerPrivate::workerStarted()
1824{
1825 Q_Q(Server);
1826
1827 // All workers have started
1828 if (--workersNotRunning == 0) {
1829 Q_EMIT q->ready();
1830 }
1831}
1832
1833bool ServerPrivate::postFork(int workerId)
1834{
1835 Q_Q(Server);
1836
1837 if (lazy) {
1838 if (!setupApplication()) {
1839 Q_EMIT q->errorOccured(qtTrId("cutelystd-err-fail-setup-app"));
1840 return false;
1841 }
1842 }
1843
1844 if (engines.size() > 1) {
1845 qCDebug(CUTELYST_SERVER) << "Starting threads";
1846 }
1847
1848 for (ServerEngine *engine : engines) {
1849 QThread *thread = engine->thread();
1850 if (thread != qApp->thread()) {
1851#ifdef Q_OS_LINUX
1852 if (!qEnvironmentVariableIsSet("CUTELYST_QT_EVENT_LOOP")) {
1853 // NOLINTNEXTLINE
1854 thread->setEventDispatcher(new EventDispatcherEPoll);
1855 }
1856#endif
1857
1858 thread->start();
1859 }
1860 }
1861
1862 Q_EMIT postForked(workerId);
1863
1864 QTimer::singleShot(std::chrono::seconds{1}, this, [=]() {
1865 // THIS IS NEEDED when
1866 // --master --threads N --experimental-thread-balancer
1867 // for some reason sometimes the balancer doesn't get
1868 // the ready signal (which stays on event loop queue)
1869 // from TcpServer and doesn't starts listening.
1870 qApp->processEvents();
1871 });
1872
1873 return true;
1874}
1875
1876bool ServerPrivate::writePidFile(const QString &filename)
1877{
1878 if (filename.isEmpty()) {
1879 return true;
1880 }
1881
1882 QFile file(filename);
1883 if (!file.open(QFile::WriteOnly | QFile::Text)) {
1884 std::cerr << "Failed write pid file " << qPrintable(filename) << '\n';
1885 return false;
1886 }
1887
1888 std::cout << "Writing pidfile to " << qPrintable(filename) << '\n';
1890
1891 return true;
1892}
1893
1894ServerEngine *ServerPrivate::createEngine(Application *app, int workerCore)
1895{
1896 Q_Q(Server);
1897
1898 // If threads is greater than 1 we need a new application instance
1899 if (workerCore > 0) {
1900 app = qobject_cast<Application *>(app->metaObject()->newInstance());
1901 if (!app) {
1902 qFatal("*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
1903 "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
1904 }
1905 }
1906
1907 auto engine = new ServerEngine(app, workerCore, opt, q);
1908 const Qt::ConnectionType forkConnection =
1910 connect(this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1911 connect(this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, forkConnection);
1912 connect(engine,
1913 &ServerEngine::shutdownCompleted,
1914 this,
1915 &ServerPrivate::engineShutdown,
1917 connect(engine, &ServerEngine::started, this, &ServerPrivate::workerStarted, forkConnection);
1918
1919 engine->setConfig(config);
1920 engine->setServers(servers);
1921 if (!engine->init()) {
1922 std::cerr << "Application failed to init(), cheaping core: " << workerCore << '\n';
1923 delete engine;
1924 return nullptr;
1925 }
1926
1927 engines.push_back(engine);
1928
1929 // If threads is greater than 1 we need a new thread
1930 if (workerCore > 0) {
1931 // To make easier for engines to clean up
1932 // the NEW app must be a child of it
1933 app->setParent(engine);
1934
1935 auto thread = new QThread(this);
1936 engine->moveToThread(thread);
1937 } else {
1938 engine->setParent(this);
1939 }
1940
1941 return engine;
1942}
1943
1944void ServerPrivate::loadConfig()
1945{
1946 if (loadingConfig) {
1947 return;
1948 }
1949
1950 loadingConfig = true;
1951
1952 if (configToLoad.isEmpty()) {
1953 loadingConfig = false;
1954 return;
1955 }
1956
1957 auto fileToLoad = configToLoad.dequeue();
1958
1959 if (fileToLoad.first.isEmpty()) {
1960 qCWarning(CUTELYST_SERVER) << "Can not load config from empty config file name";
1961 loadingConfig = false;
1962 return;
1963 }
1964
1965 if (configLoaded.contains(fileToLoad.first)) {
1966 loadingConfig = false;
1967 return;
1968 }
1969
1970 configLoaded.append(fileToLoad.first);
1971
1972 QVariantMap loadedConfig;
1973 switch (fileToLoad.second) {
1974 case ConfigFormat::Ini:
1975 qCInfo(CUTELYST_SERVER) << "Loading INI configuratin:" << fileToLoad.first;
1976 loadedConfig = Engine::loadIniConfig(fileToLoad.first);
1977 break;
1978 case ConfigFormat::Json:
1979 qCInfo(CUTELYST_SERVER) << "Loading JSON configuration:" << fileToLoad.first;
1980 loadedConfig = Engine::loadJsonConfig(fileToLoad.first);
1981 break;
1982 }
1983
1984 for (const auto &[key, value] : std::as_const(loadedConfig).asKeyValueRange()) {
1985 if (config.contains(key)) {
1986 QVariantMap currentMap = config.value(key).toMap();
1987 const QVariantMap loadedMap = value.toMap();
1988 for (const auto &[mapKey, mapValue] : loadedMap.asKeyValueRange()) {
1989 currentMap.insert(mapKey, mapValue);
1990 }
1991 config.insert(key, currentMap);
1992 } else {
1993 config.insert(key, value);
1994 }
1995 }
1996
1997 QVariantMap sessionConfig = loadedConfig.value(u"server"_s).toMap();
1998
1999 applyConfig(sessionConfig);
2000
2001 opt.insert(sessionConfig);
2002
2003 loadingConfig = false;
2004
2005 if (!configToLoad.empty()) {
2006 loadConfig();
2007 }
2008}
2009
2010void ServerPrivate::applyConfig(const QVariantMap &config)
2011{
2012 Q_Q(Server);
2013
2014 for (const auto &[key, value] : config.asKeyValueRange()) {
2015 QString normKey = key;
2016 normKey.replace(u'-', u'_');
2017
2018 int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
2019 if (ix == -1) {
2020 continue;
2021 }
2022
2023 const QMetaProperty prop = q->metaObject()->property(ix);
2024 if (prop.userType() == value.userType()) {
2025 if (prop.userType() == QMetaType::QStringList) {
2026 const QStringList currentValues = prop.read(q).toStringList();
2027 prop.write(q, currentValues + value.toStringList());
2028 } else {
2029 prop.write(q, value);
2030 }
2031 } else if (prop.userType() == QMetaType::QStringList) {
2032 const QStringList currentValues = prop.read(q).toStringList();
2033 prop.write(q, currentValues + QStringList{value.toString()});
2034 } else {
2035 prop.write(q, value);
2036 }
2037 }
2038}
2039
2040Protocol *ServerPrivate::getHttpProto()
2041{
2042 Q_Q(Server);
2043 if (!protoHTTP) {
2044 if (upgradeH2c) {
2045 protoHTTP = new ProtocolHttp(q, getHttp2Proto());
2046 } else {
2047 protoHTTP = new ProtocolHttp(q);
2048 }
2049 }
2050 return protoHTTP;
2051}
2052
2053ProtocolHttp2 *ServerPrivate::getHttp2Proto()
2054{
2055 Q_Q(Server);
2056 if (!protoHTTP2) {
2057 protoHTTP2 = new ProtocolHttp2(q);
2058 }
2059 return protoHTTP2;
2060}
2061
2062Protocol *ServerPrivate::getFastCgiProto()
2063{
2064 Q_Q(Server);
2065 if (!protoFCGI) {
2066 protoFCGI = new ProtocolFastCGI(q);
2067 }
2068 return protoFCGI;
2069}
2070
2071#include "moc_server.cpp"
2072#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:848
virtual ~Server()
Definition server.cpp:98
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:687
QString processes
Definition server.h:159
void parseCommandLine(const QStringList &args)
Definition server.cpp:104
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:1696
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
FindDirectChildrenOnly
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