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