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