cutelyst  5.0.1
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 
40 Q_LOGGING_CATEGORY(CUTELYST_SERVER, "cutelyst.server", QtWarningMsg)
41 
42 using namespace Cutelyst;
43 using 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 
95 void Server::parseCommandLine(const QStringList &arguments)
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 
867 ServerPrivate::~ServerPrivate()
868 {
869  delete protoHTTP;
870  delete protoHTTP2;
871  delete protoFCGI;
872 }
873 
874 bool 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 
914 bool 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 
937 bool 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 
1002 bool 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')) {
1013  options |= QLocalServer::UserAccessOption;
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 
1053 void 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 
1068 QString Server::application() const
1069 {
1070  Q_D(const Server);
1071  return d->application;
1072 }
1073 
1074 void 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 
1085 QString Server::threads() const
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 
1094 void 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 
1107 QString Server::processes() const
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 
1116 void Server::setChdir(const QString &chdir)
1117 {
1118  Q_D(Server);
1119  d->chdir = chdir;
1120  Q_EMIT changed();
1121 }
1122 
1123 QString Server::chdir() const
1124 {
1125  Q_D(const Server);
1126  return d->chdir;
1127 }
1128 
1129 void Server::setHttpSocket(const QStringList &httpSocket)
1130 {
1131  Q_D(Server);
1132  d->httpSockets = httpSocket;
1133  Q_EMIT changed();
1134 }
1135 
1136 QStringList Server::httpSocket() const
1137 {
1138  Q_D(const Server);
1139  return d->httpSockets;
1140 }
1141 
1142 void Server::setHttp2Socket(const QStringList &http2Socket)
1143 {
1144  Q_D(Server);
1145  d->http2Sockets = http2Socket;
1146  Q_EMIT changed();
1147 }
1148 
1149 QStringList Server::http2Socket() const
1150 {
1151  Q_D(const Server);
1152  return d->http2Sockets;
1153 }
1154 
1155 void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1156 {
1157  Q_D(Server);
1158  d->http2HeaderTableSize = headerTableSize;
1159  Q_EMIT changed();
1160 }
1161 
1162 quint32 Server::http2HeaderTableSize() const
1163 {
1164  Q_D(const Server);
1165  return d->http2HeaderTableSize;
1166 }
1167 
1168 void Server::setUpgradeH2c(bool enable)
1169 {
1170  Q_D(Server);
1171  d->upgradeH2c = enable;
1172  Q_EMIT changed();
1173 }
1174 
1175 bool Server::upgradeH2c() const
1176 {
1177  Q_D(const Server);
1178  return d->upgradeH2c;
1179 }
1180 
1181 void Server::setHttpsH2(bool enable)
1182 {
1183  Q_D(Server);
1184  d->httpsH2 = enable;
1185  Q_EMIT changed();
1186 }
1187 
1188 bool Server::httpsH2() const
1189 {
1190  Q_D(const Server);
1191  return d->httpsH2;
1192 }
1193 
1194 void Server::setHttpsSocket(const QStringList &httpsSocket)
1195 {
1196  Q_D(Server);
1197  d->httpsSockets = httpsSocket;
1198  Q_EMIT changed();
1199 }
1200 
1201 QStringList Server::httpsSocket() const
1202 {
1203  Q_D(const Server);
1204  return d->httpsSockets;
1205 }
1206 
1207 void Server::setFastcgiSocket(const QStringList &fastcgiSocket)
1208 {
1209  Q_D(Server);
1210  d->fastcgiSockets = fastcgiSocket;
1211  Q_EMIT changed();
1212 }
1213 
1214 QStringList Server::fastcgiSocket() const
1215 {
1216  Q_D(const Server);
1217  return d->fastcgiSockets;
1218 }
1219 
1220 void Server::setSocketAccess(const QString &socketAccess)
1221 {
1222  Q_D(Server);
1223  d->socketAccess = socketAccess;
1224  Q_EMIT changed();
1225 }
1226 
1227 QString Server::socketAccess() const
1228 {
1229  Q_D(const Server);
1230  return d->socketAccess;
1231 }
1232 
1233 void Server::setSocketTimeout(int timeout)
1234 {
1235  Q_D(Server);
1236  d->socketTimeout = timeout;
1237  Q_EMIT changed();
1238 }
1239 
1240 int Server::socketTimeout() const
1241 {
1242  Q_D(const Server);
1243  return d->socketTimeout;
1244 }
1245 
1246 void Server::setChdir2(const QString &chdir2)
1247 {
1248  Q_D(Server);
1249  d->chdir2 = chdir2;
1250  Q_EMIT changed();
1251 }
1252 
1253 QString Server::chdir2() const
1254 {
1255  Q_D(const Server);
1256  return d->chdir2;
1257 }
1258 
1259 void 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 
1279 QStringList Server::ini() const
1280 {
1281  Q_D(const Server);
1282  return d->ini;
1283 }
1284 
1285 void 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 
1305 QStringList Server::json() const
1306 {
1307  Q_D(const Server);
1308  return d->json;
1309 }
1310 
1311 void Server::setStaticMap(const QStringList &staticMap)
1312 {
1313  Q_D(Server);
1314  d->staticMaps = staticMap;
1315  Q_EMIT changed();
1316 }
1317 
1318 QStringList Server::staticMap() const
1319 {
1320  Q_D(const Server);
1321  return d->staticMaps;
1322 }
1323 
1324 void Server::setStaticMap2(const QStringList &staticMap)
1325 {
1326  Q_D(Server);
1327  d->staticMaps2 = staticMap;
1328  Q_EMIT changed();
1329 }
1330 
1331 QStringList Server::staticMap2() const
1332 {
1333  Q_D(const Server);
1334  return d->staticMaps2;
1335 }
1336 
1337 void 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 
1346 bool Server::master() const
1347 {
1348  Q_D(const Server);
1349  return d->master;
1350 }
1351 
1352 void Server::setAutoReload(bool enable)
1353 {
1354  Q_D(Server);
1355  if (enable) {
1356  d->autoReload = true;
1357  }
1358  Q_EMIT changed();
1359 }
1360 
1361 bool Server::autoReload() const
1362 {
1363  Q_D(const Server);
1364  return d->autoReload;
1365 }
1366 
1367 void Server::setTouchReload(const QStringList &files)
1368 {
1369  Q_D(Server);
1370  d->touchReload = files;
1371  Q_EMIT changed();
1372 }
1373 
1374 QStringList Server::touchReload() const
1375 {
1376  Q_D(const Server);
1377  return d->touchReload;
1378 }
1379 
1380 void Server::setListenQueue(int size)
1381 {
1382  Q_D(Server);
1383  d->listenQueue = size;
1384  Q_EMIT changed();
1385 }
1386 
1387 int Server::listenQueue() const
1388 {
1389  Q_D(const Server);
1390  return d->listenQueue;
1391 }
1392 
1393 void 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 
1404 int Server::bufferSize() const
1405 {
1406  Q_D(const Server);
1407  return d->bufferSize;
1408 }
1409 
1410 void Server::setPostBuffering(qint64 size)
1411 {
1412  Q_D(Server);
1413  d->postBuffering = size;
1414  Q_EMIT changed();
1415 }
1416 
1417 qint64 Server::postBuffering() const
1418 {
1419  Q_D(const Server);
1420  return d->postBuffering;
1421 }
1422 
1423 void 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 
1434 qint64 Server::postBufferingBufsize() const
1435 {
1436  Q_D(const Server);
1437  return d->postBufferingBufsize;
1438 }
1439 
1440 void Server::setTcpNodelay(bool enable)
1441 {
1442  Q_D(Server);
1443  d->tcpNodelay = enable;
1444  Q_EMIT changed();
1445 }
1446 
1447 bool Server::tcpNodelay() const
1448 {
1449  Q_D(const Server);
1450  return d->tcpNodelay;
1451 }
1452 
1453 void Server::setSoKeepalive(bool enable)
1454 {
1455  Q_D(Server);
1456  d->soKeepalive = enable;
1457  Q_EMIT changed();
1458 }
1459 
1460 bool Server::soKeepalive() const
1461 {
1462  Q_D(const Server);
1463  return d->soKeepalive;
1464 }
1465 
1466 void Server::setSocketSndbuf(int value)
1467 {
1468  Q_D(Server);
1469  d->socketSendBuf = value;
1470  Q_EMIT changed();
1471 }
1472 
1473 int Server::socketSndbuf() const
1474 {
1475  Q_D(const Server);
1476  return d->socketSendBuf;
1477 }
1478 
1479 void Server::setSocketRcvbuf(int value)
1480 {
1481  Q_D(Server);
1482  d->socketReceiveBuf = value;
1483  Q_EMIT changed();
1484 }
1485 
1486 int Server::socketRcvbuf() const
1487 {
1488  Q_D(const Server);
1489  return d->socketReceiveBuf;
1490 }
1491 
1492 void Server::setWebsocketMaxSize(int value)
1493 {
1494  Q_D(Server);
1495  d->websocketMaxSize = value * 1024;
1496  Q_EMIT changed();
1497 }
1498 
1499 int Server::websocketMaxSize() const
1500 {
1501  Q_D(const Server);
1502  return d->websocketMaxSize / 1024;
1503 }
1504 
1505 void Server::setPidfile(const QString &file)
1506 {
1507  Q_D(Server);
1508  d->pidfile = file;
1509  Q_EMIT changed();
1510 }
1511 
1512 QString Server::pidfile() const
1513 {
1514  Q_D(const Server);
1515  return d->pidfile;
1516 }
1517 
1518 void Server::setPidfile2(const QString &file)
1519 {
1520  Q_D(Server);
1521  d->pidfile2 = file;
1522  Q_EMIT changed();
1523 }
1524 
1525 QString Server::pidfile2() const
1526 {
1527  Q_D(const Server);
1528  return d->pidfile2;
1529 }
1530 
1531 void 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 
1540 QString Server::uid() const
1541 {
1542  Q_D(const Server);
1543  return d->uid;
1544 }
1545 
1546 void 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 
1555 QString Server::gid() const
1556 {
1557  Q_D(const Server);
1558  return d->gid;
1559 }
1560 
1561 void 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 
1570 bool Server::noInitgroups() const
1571 {
1572  Q_D(const Server);
1573  return d->noInitgroups;
1574 }
1575 
1576 void 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 
1585 QString Server::chownSocket() const
1586 {
1587  Q_D(const Server);
1588  return d->chownSocket;
1589 }
1590 
1591 void 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 
1600 QString Server::umask() const
1601 {
1602  Q_D(const Server);
1603  return d->umask;
1604 }
1605 
1606 void 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 
1615 int Server::cpuAffinity() const
1616 {
1617  Q_D(const Server);
1618  return d->cpuAffinity;
1619 }
1620 
1621 void 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 
1632 bool Server::reusePort() const
1633 {
1634  Q_D(const Server);
1635  return d->reusePort;
1636 }
1637 
1638 void Server::setLazy(bool enable)
1639 {
1640  Q_D(Server);
1641  d->lazy = enable;
1642  Q_EMIT changed();
1643 }
1644 
1645 bool Server::lazy() const
1646 {
1647  Q_D(const Server);
1648  return d->lazy;
1649 }
1650 
1651 void Server::setUsingFrontendProxy(bool enable)
1652 {
1653  Q_D(Server);
1654  d->usingFrontendProxy = enable;
1655  Q_EMIT changed();
1656 }
1657 
1658 bool Server::usingFrontendProxy() const
1659 {
1660  Q_D(const Server);
1661  return d->usingFrontendProxy;
1662 }
1663 
1664 QVariantMap Server::config() const noexcept
1665 {
1666  Q_D(const Server);
1667  return d->config;
1668 }
1669 
1670 bool 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 
1736 void 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 
1754 void 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 
1766 void 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 
1776 bool 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 
1819 bool 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 
1837 ServerEngine *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 
1887 void 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 
1953 void 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 
1983 Protocol *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 
1996 ProtocolHttp2 *ServerPrivate::getHttp2Proto()
1997 {
1998  Q_Q(Server);
1999  if (!protoHTTP2) {
2000  protoHTTP2 = new ProtocolHttp2(q);
2001  }
2002  return protoHTTP2;
2003 }
2004 
2005 Protocol *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"
QFuture< ArgsType< Signal >> connect(Sender *sender, Signal signal)
void setConfig(const QVariantMap &config)
Definition: engine.cpp:128
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
int exec(Cutelyst::Application *app=nullptr)
Definition: server.cpp:678
void moveToThread(QThread *targetThread)
int userType() const const
QCommandLineOption addVersionOption()
virtual const QMetaObject * metaObject() const const
Implements a web server.
Definition: server.h:59
QObject * newInstance(Args &&... arguments) const const
virtual bool init() override
QThread * thread() const const
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
static QVariantMap loadIniConfig(const QString &filename)
Definition: engine.cpp:134
void addLibraryPath(const QString &path)
QVariantMap config() const noexcept
Definition: server.cpp:1664
void start(Priority priority)
QString number(double n, char format, int precision)
QByteArray number(double n, char format, int precision)
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
bool isSet(const QCommandLineOption &option) const const
QStringList values(const QCommandLineOption &option) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString value(const QCommandLineOption &option) const const
CaseInsensitive
int toInt(bool *ok, int base) const const
bool isEmpty() const const
virtual ~Server()
Definition: server.cpp:89
bool start(Cutelyst::Application *app=nullptr)
Definition: server.cpp:832
const char * constData() const const
QCommandLineOption addHelpOption()
bool setCurrent(const QString &path)
void setApplicationDescription(const QString &description)
qint64 applicationPid()
static QVariantMap loadJsonConfig(const QString &filename)
Definition: engine.cpp:158
ResolveAllSymbolsHint
The Cutelyst namespace holds all public Cutelyst API.
void errorOccured(const QString &error)
bool removeServer(const QString &name)
QVariant read(const QObject *object) const const
void push_back(QByteArrayView str)
void parseCommandLine(const QStringList &args)
Definition: server.cpp:95
void setParent(QObject *parent)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
QByteArray toLatin1() const const
int idealThreadCount()
QStringList toStringList() const const
bool write(QObject *object, QVariant &&v) const const
void showHelp(int exitCode)
QThread * currentThread()
bool addOption(const QCommandLineOption &option)
The Cutelyst application.
Definition: application.h:72
DirectConnection
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
void process(const QCoreApplication &app)
bool removeOne(const AT &t)
qlonglong toLongLong(bool *ok, int base) const const
Q_EMITQ_EMIT
QString applicationName()
void finished()
typedef SocketOptions
uint toUInt(bool *ok, int base) const const
Server(QObject *parent=nullptr)
Definition: server.cpp:45