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