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