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