cutelyst  4.9.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  server->setListenBacklogSize(listenQueue);
1025  ret = server->listen(line);
1026  server->pauseAccepting();
1027 
1028  if (!ret || !server->socket()) {
1029  std::cerr << "Failed to listen on LOCAL: " << qPrintable(line) << " : "
1030  << qPrintable(server->errorString()) << std::endl;
1031  return false;
1032  }
1033 
1034 #ifdef Q_OS_UNIX
1035  if (!chownSocket.isEmpty()) {
1036  UnixFork::chownSocket(line, chownSocket);
1037  }
1038 #endif
1039  auto qEnum = protocol->staticMetaObject.enumerator(0);
1040  std::cout << qEnum.valueToKey(static_cast<int>(protocol->type())) << " socket "
1041  << QByteArray::number(static_cast<int>(servers.size())).constData()
1042  << " bound to LOCAL address " << qPrintable(line) << " fd "
1043  << QByteArray::number(server->socket()).constData() << std::endl;
1044  servers.push_back(server);
1045  }
1046 
1047  return ret;
1048 }
1049 
1050 void Server::setApplication(const QString &application)
1051 {
1052  Q_D(Server);
1053 
1054  QPluginLoader loader(application);
1055  if (loader.fileName().isEmpty()) {
1056  d->application = application;
1057  } else {
1058  // We use the loader filename since it can provide
1059  // the suffix for the file watcher
1060  d->application = loader.fileName();
1061  }
1062  Q_EMIT changed();
1063 }
1064 
1065 QString Server::application() const
1066 {
1067  Q_D(const Server);
1068  return d->application;
1069 }
1070 
1071 void Server::setThreads(const QString &threads)
1072 {
1073  Q_D(Server);
1074  if (threads.compare(u"auto", Qt::CaseInsensitive) == 0) {
1075  d->threads = -1;
1076  } else {
1077  d->threads = qMax(1, threads.toInt());
1078  }
1079  Q_EMIT changed();
1080 }
1081 
1082 QString Server::threads() const
1083 {
1084  Q_D(const Server);
1085  if (d->threads == -1) {
1086  return QStringLiteral("auto");
1087  }
1088  return QString::number(d->threads);
1089 }
1090 
1091 void Server::setProcesses(const QString &process)
1092 {
1093 #ifdef Q_OS_UNIX
1094  Q_D(Server);
1095  if (process.compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0) {
1096  d->processes = -1;
1097  } else {
1098  d->processes = process.toInt();
1099  }
1100  Q_EMIT changed();
1101 #endif
1102 }
1103 
1104 QString Server::processes() const
1105 {
1106  Q_D(const Server);
1107  if (d->processes == -1) {
1108  return QStringLiteral("auto");
1109  }
1110  return QString::number(d->processes);
1111 }
1112 
1113 void Server::setChdir(const QString &chdir)
1114 {
1115  Q_D(Server);
1116  d->chdir = chdir;
1117  Q_EMIT changed();
1118 }
1119 
1120 QString Server::chdir() const
1121 {
1122  Q_D(const Server);
1123  return d->chdir;
1124 }
1125 
1126 void Server::setHttpSocket(const QStringList &httpSocket)
1127 {
1128  Q_D(Server);
1129  d->httpSockets = httpSocket;
1130  Q_EMIT changed();
1131 }
1132 
1133 QStringList Server::httpSocket() const
1134 {
1135  Q_D(const Server);
1136  return d->httpSockets;
1137 }
1138 
1139 void Server::setHttp2Socket(const QStringList &http2Socket)
1140 {
1141  Q_D(Server);
1142  d->http2Sockets = http2Socket;
1143  Q_EMIT changed();
1144 }
1145 
1146 QStringList Server::http2Socket() const
1147 {
1148  Q_D(const Server);
1149  return d->http2Sockets;
1150 }
1151 
1152 void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1153 {
1154  Q_D(Server);
1155  d->http2HeaderTableSize = headerTableSize;
1156  Q_EMIT changed();
1157 }
1158 
1159 quint32 Server::http2HeaderTableSize() const
1160 {
1161  Q_D(const Server);
1162  return d->http2HeaderTableSize;
1163 }
1164 
1165 void Server::setUpgradeH2c(bool enable)
1166 {
1167  Q_D(Server);
1168  d->upgradeH2c = enable;
1169  Q_EMIT changed();
1170 }
1171 
1172 bool Server::upgradeH2c() const
1173 {
1174  Q_D(const Server);
1175  return d->upgradeH2c;
1176 }
1177 
1178 void Server::setHttpsH2(bool enable)
1179 {
1180  Q_D(Server);
1181  d->httpsH2 = enable;
1182  Q_EMIT changed();
1183 }
1184 
1185 bool Server::httpsH2() const
1186 {
1187  Q_D(const Server);
1188  return d->httpsH2;
1189 }
1190 
1191 void Server::setHttpsSocket(const QStringList &httpsSocket)
1192 {
1193  Q_D(Server);
1194  d->httpsSockets = httpsSocket;
1195  Q_EMIT changed();
1196 }
1197 
1198 QStringList Server::httpsSocket() const
1199 {
1200  Q_D(const Server);
1201  return d->httpsSockets;
1202 }
1203 
1204 void Server::setFastcgiSocket(const QStringList &fastcgiSocket)
1205 {
1206  Q_D(Server);
1207  d->fastcgiSockets = fastcgiSocket;
1208  Q_EMIT changed();
1209 }
1210 
1211 QStringList Server::fastcgiSocket() const
1212 {
1213  Q_D(const Server);
1214  return d->fastcgiSockets;
1215 }
1216 
1217 void Server::setSocketAccess(const QString &socketAccess)
1218 {
1219  Q_D(Server);
1220  d->socketAccess = socketAccess;
1221  Q_EMIT changed();
1222 }
1223 
1224 QString Server::socketAccess() const
1225 {
1226  Q_D(const Server);
1227  return d->socketAccess;
1228 }
1229 
1230 void Server::setSocketTimeout(int timeout)
1231 {
1232  Q_D(Server);
1233  d->socketTimeout = timeout;
1234  Q_EMIT changed();
1235 }
1236 
1237 int Server::socketTimeout() const
1238 {
1239  Q_D(const Server);
1240  return d->socketTimeout;
1241 }
1242 
1243 void Server::setChdir2(const QString &chdir2)
1244 {
1245  Q_D(Server);
1246  d->chdir2 = chdir2;
1247  Q_EMIT changed();
1248 }
1249 
1250 QString Server::chdir2() const
1251 {
1252  Q_D(const Server);
1253  return d->chdir2;
1254 }
1255 
1256 void Server::setIni(const QStringList &files)
1257 {
1258  Q_D(Server);
1259  d->ini.append(files);
1260  d->ini.removeDuplicates();
1261  Q_EMIT changed();
1262 
1263  for (const QString &file : files) {
1264  if (!d->configLoaded.contains(file)) {
1265  auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Ini);
1266  if (!d->configToLoad.contains(fileToLoad)) {
1267  qCDebug(CUTELYST_SERVER) << "Enqueue INI config file:" << file;
1268  d->configToLoad.enqueue(fileToLoad);
1269  }
1270  }
1271  }
1272 
1273  d->loadConfig();
1274 }
1275 
1276 QStringList Server::ini() const
1277 {
1278  Q_D(const Server);
1279  return d->ini;
1280 }
1281 
1282 void Server::setJson(const QStringList &files)
1283 {
1284  Q_D(Server);
1285  d->json.append(files);
1286  d->json.removeDuplicates();
1287  Q_EMIT changed();
1288 
1289  for (const QString &file : files) {
1290  if (!d->configLoaded.contains(file)) {
1291  auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Json);
1292  if (!d->configToLoad.contains(fileToLoad)) {
1293  qCDebug(CUTELYST_SERVER) << "Enqueue JSON config file:" << file;
1294  d->configToLoad.enqueue(fileToLoad);
1295  }
1296  }
1297  }
1298 
1299  d->loadConfig();
1300 }
1301 
1302 QStringList Server::json() const
1303 {
1304  Q_D(const Server);
1305  return d->json;
1306 }
1307 
1308 void Server::setStaticMap(const QStringList &staticMap)
1309 {
1310  Q_D(Server);
1311  d->staticMaps = staticMap;
1312  Q_EMIT changed();
1313 }
1314 
1315 QStringList Server::staticMap() const
1316 {
1317  Q_D(const Server);
1318  return d->staticMaps;
1319 }
1320 
1321 void Server::setStaticMap2(const QStringList &staticMap)
1322 {
1323  Q_D(Server);
1324  d->staticMaps2 = staticMap;
1325  Q_EMIT changed();
1326 }
1327 
1328 QStringList Server::staticMap2() const
1329 {
1330  Q_D(const Server);
1331  return d->staticMaps2;
1332 }
1333 
1334 void Server::setMaster(bool enable)
1335 {
1336  Q_D(Server);
1337  if (!qEnvironmentVariableIsSet("CUTELYST_SERVER_IGNORE_MASTER")) {
1338  d->master = enable;
1339  }
1340  Q_EMIT changed();
1341 }
1342 
1343 bool Server::master() const
1344 {
1345  Q_D(const Server);
1346  return d->master;
1347 }
1348 
1349 void Server::setAutoReload(bool enable)
1350 {
1351  Q_D(Server);
1352  if (enable) {
1353  d->autoReload = true;
1354  }
1355  Q_EMIT changed();
1356 }
1357 
1358 bool Server::autoReload() const
1359 {
1360  Q_D(const Server);
1361  return d->autoReload;
1362 }
1363 
1364 void Server::setTouchReload(const QStringList &files)
1365 {
1366  Q_D(Server);
1367  d->touchReload = files;
1368  Q_EMIT changed();
1369 }
1370 
1371 QStringList Server::touchReload() const
1372 {
1373  Q_D(const Server);
1374  return d->touchReload;
1375 }
1376 
1377 void Server::setListenQueue(int size)
1378 {
1379  Q_D(Server);
1380  d->listenQueue = size;
1381  Q_EMIT changed();
1382 }
1383 
1384 int Server::listenQueue() const
1385 {
1386  Q_D(const Server);
1387  return d->listenQueue;
1388 }
1389 
1390 void Server::setBufferSize(int size)
1391 {
1392  Q_D(Server);
1393  if (size < 4096) {
1394  qCWarning(CUTELYST_SERVER) << "Buffer size must be at least 4096 bytes, ignoring";
1395  return;
1396  }
1397  d->bufferSize = size;
1398  Q_EMIT changed();
1399 }
1400 
1401 int Server::bufferSize() const
1402 {
1403  Q_D(const Server);
1404  return d->bufferSize;
1405 }
1406 
1407 void Server::setPostBuffering(qint64 size)
1408 {
1409  Q_D(Server);
1410  d->postBuffering = size;
1411  Q_EMIT changed();
1412 }
1413 
1414 qint64 Server::postBuffering() const
1415 {
1416  Q_D(const Server);
1417  return d->postBuffering;
1418 }
1419 
1420 void Server::setPostBufferingBufsize(qint64 size)
1421 {
1422  Q_D(Server);
1423  if (size < 4096) {
1424  qCWarning(CUTELYST_SERVER) << "Post buffer size must be at least 4096 bytes, ignoring";
1425  return;
1426  }
1427  d->postBufferingBufsize = size;
1428  Q_EMIT changed();
1429 }
1430 
1431 qint64 Server::postBufferingBufsize() const
1432 {
1433  Q_D(const Server);
1434  return d->postBufferingBufsize;
1435 }
1436 
1437 void Server::setTcpNodelay(bool enable)
1438 {
1439  Q_D(Server);
1440  d->tcpNodelay = enable;
1441  Q_EMIT changed();
1442 }
1443 
1444 bool Server::tcpNodelay() const
1445 {
1446  Q_D(const Server);
1447  return d->tcpNodelay;
1448 }
1449 
1450 void Server::setSoKeepalive(bool enable)
1451 {
1452  Q_D(Server);
1453  d->soKeepalive = enable;
1454  Q_EMIT changed();
1455 }
1456 
1457 bool Server::soKeepalive() const
1458 {
1459  Q_D(const Server);
1460  return d->soKeepalive;
1461 }
1462 
1463 void Server::setSocketSndbuf(int value)
1464 {
1465  Q_D(Server);
1466  d->socketSendBuf = value;
1467  Q_EMIT changed();
1468 }
1469 
1470 int Server::socketSndbuf() const
1471 {
1472  Q_D(const Server);
1473  return d->socketSendBuf;
1474 }
1475 
1476 void Server::setSocketRcvbuf(int value)
1477 {
1478  Q_D(Server);
1479  d->socketReceiveBuf = value;
1480  Q_EMIT changed();
1481 }
1482 
1483 int Server::socketRcvbuf() const
1484 {
1485  Q_D(const Server);
1486  return d->socketReceiveBuf;
1487 }
1488 
1489 void Server::setWebsocketMaxSize(int value)
1490 {
1491  Q_D(Server);
1492  d->websocketMaxSize = value * 1024;
1493  Q_EMIT changed();
1494 }
1495 
1496 int Server::websocketMaxSize() const
1497 {
1498  Q_D(const Server);
1499  return d->websocketMaxSize / 1024;
1500 }
1501 
1502 void Server::setPidfile(const QString &file)
1503 {
1504  Q_D(Server);
1505  d->pidfile = file;
1506  Q_EMIT changed();
1507 }
1508 
1509 QString Server::pidfile() const
1510 {
1511  Q_D(const Server);
1512  return d->pidfile;
1513 }
1514 
1515 void Server::setPidfile2(const QString &file)
1516 {
1517  Q_D(Server);
1518  d->pidfile2 = file;
1519  Q_EMIT changed();
1520 }
1521 
1522 QString Server::pidfile2() const
1523 {
1524  Q_D(const Server);
1525  return d->pidfile2;
1526 }
1527 
1528 void Server::setUid(const QString &uid)
1529 {
1530 #ifdef Q_OS_UNIX
1531  Q_D(Server);
1532  d->uid = uid;
1533  Q_EMIT changed();
1534 #endif
1535 }
1536 
1537 QString Server::uid() const
1538 {
1539  Q_D(const Server);
1540  return d->uid;
1541 }
1542 
1543 void Server::setGid(const QString &gid)
1544 {
1545 #ifdef Q_OS_UNIX
1546  Q_D(Server);
1547  d->gid = gid;
1548  Q_EMIT changed();
1549 #endif
1550 }
1551 
1552 QString Server::gid() const
1553 {
1554  Q_D(const Server);
1555  return d->gid;
1556 }
1557 
1558 void Server::setNoInitgroups(bool enable)
1559 {
1560 #ifdef Q_OS_UNIX
1561  Q_D(Server);
1562  d->noInitgroups = enable;
1563  Q_EMIT changed();
1564 #endif
1565 }
1566 
1567 bool Server::noInitgroups() const
1568 {
1569  Q_D(const Server);
1570  return d->noInitgroups;
1571 }
1572 
1573 void Server::setChownSocket(const QString &chownSocket)
1574 {
1575 #ifdef Q_OS_UNIX
1576  Q_D(Server);
1577  d->chownSocket = chownSocket;
1578  Q_EMIT changed();
1579 #endif
1580 }
1581 
1582 QString Server::chownSocket() const
1583 {
1584  Q_D(const Server);
1585  return d->chownSocket;
1586 }
1587 
1588 void Server::setUmask(const QString &value)
1589 {
1590 #ifdef Q_OS_UNIX
1591  Q_D(Server);
1592  d->umask = value;
1593  Q_EMIT changed();
1594 #endif
1595 }
1596 
1597 QString Server::umask() const
1598 {
1599  Q_D(const Server);
1600  return d->umask;
1601 }
1602 
1603 void Server::setCpuAffinity(int value)
1604 {
1605 #ifdef Q_OS_UNIX
1606  Q_D(Server);
1607  d->cpuAffinity = value;
1608  Q_EMIT changed();
1609 #endif
1610 }
1611 
1612 int Server::cpuAffinity() const
1613 {
1614  Q_D(const Server);
1615  return d->cpuAffinity;
1616 }
1617 
1618 void Server::setReusePort(bool enable)
1619 {
1620 #ifdef Q_OS_LINUX
1621  Q_D(Server);
1622  d->reusePort = enable;
1623  Q_EMIT changed();
1624 #else
1625  Q_UNUSED(enable);
1626 #endif
1627 }
1628 
1629 bool Server::reusePort() const
1630 {
1631  Q_D(const Server);
1632  return d->reusePort;
1633 }
1634 
1635 void Server::setLazy(bool enable)
1636 {
1637  Q_D(Server);
1638  d->lazy = enable;
1639  Q_EMIT changed();
1640 }
1641 
1642 bool Server::lazy() const
1643 {
1644  Q_D(const Server);
1645  return d->lazy;
1646 }
1647 
1648 void Server::setUsingFrontendProxy(bool enable)
1649 {
1650  Q_D(Server);
1651  d->usingFrontendProxy = enable;
1652  Q_EMIT changed();
1653 }
1654 
1655 bool Server::usingFrontendProxy() const
1656 {
1657  Q_D(const Server);
1658  return d->usingFrontendProxy;
1659 }
1660 
1661 QVariantMap Server::config() const noexcept
1662 {
1663  Q_D(const Server);
1664  return d->config;
1665 }
1666 
1667 bool ServerPrivate::setupApplication()
1668 {
1669  Cutelyst::Application *localApp = app;
1670 
1671  Q_Q(Server);
1672 
1673  if (!localApp) {
1674  std::cout << "Loading application: " << application.toLatin1().constData() << std::endl;
1675  QPluginLoader loader(application);
1677  if (!loader.load()) {
1678  qCCritical(CUTELYST_SERVER) << "Could not load application:" << loader.errorString();
1679  return false;
1680  }
1681 
1682  QObject *instance = loader.instance();
1683  if (!instance) {
1684  qCCritical(CUTELYST_SERVER) << "Could not get a QObject instance: %s\n"
1685  << loader.errorString();
1686  return false;
1687  }
1688 
1689  localApp = qobject_cast<Cutelyst::Application *>(instance);
1690  if (!localApp) {
1691  qCCritical(CUTELYST_SERVER)
1692  << "Could not cast Cutelyst::Application from instance: %s\n"
1693  << loader.errorString();
1694  return false;
1695  }
1696 
1697  // Sets the application name with the name from our library
1698  // if (QCoreApplication::applicationName() == applicationName) {
1699  // QCoreApplication::setApplicationName(QString::fromLatin1(app->metaObject()->className()));
1700  // }
1701  qCDebug(CUTELYST_SERVER) << "Loaded application: " << QCoreApplication::applicationName();
1702  }
1703 
1704  if (!chdir2.isEmpty()) {
1705  std::cout << "Changing directory2 to: " << chdir2.toLatin1().constData() << std::endl;
1706  if (!QDir::setCurrent(chdir2)) {
1707  Q_EMIT q->errorOccured(QString::fromLatin1("Failed to chdir2 to: '%s'")
1708  .arg(QString::fromLatin1(chdir2.toLatin1().constData())));
1709  return false;
1710  }
1711  }
1712 
1713  if (threads > 1) {
1714  engine = createEngine(localApp, 0);
1715  for (int i = 1; i < threads; ++i) {
1716  if (createEngine(localApp, i)) {
1717  ++workersNotRunning;
1718  }
1719  }
1720  } else {
1721  engine = createEngine(localApp, 0);
1722  workersNotRunning = 1;
1723  }
1724 
1725  if (!engine) {
1726  std::cerr << "Application failed to init, cheaping..." << std::endl;
1727  return false;
1728  }
1729 
1730  return true;
1731 }
1732 
1733 void ServerPrivate::engineShutdown(ServerEngine *engine)
1734 {
1735  const auto engineThread = engine->thread();
1736  if (QThread::currentThread() != engineThread) {
1737  connect(engineThread, &QThread::finished, this, [this, engine] {
1738  engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1739  checkEngineShutdown();
1740  });
1741  engineThread->quit();
1742  } else {
1743  engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1744  }
1745 
1746  checkEngineShutdown();
1747 }
1748 
1749 void ServerPrivate::checkEngineShutdown()
1750 {
1751  if (engines.empty()) {
1752  if (userEventLoop) {
1753  Q_Q(Server);
1754  Q_EMIT q->stopped();
1755  } else {
1756  QTimer::singleShot(std::chrono::seconds{0}, this, [] { qApp->exit(15); });
1757  }
1758  }
1759 }
1760 
1761 void ServerPrivate::workerStarted()
1762 {
1763  Q_Q(Server);
1764 
1765  // All workers have started
1766  if (--workersNotRunning == 0) {
1767  Q_EMIT q->ready();
1768  }
1769 }
1770 
1771 bool ServerPrivate::postFork(int workerId)
1772 {
1773  Q_Q(Server);
1774 
1775  if (lazy) {
1776  if (!setupApplication()) {
1777  Q_EMIT q->errorOccured(qtTrId("cutelystd-err-fail-setup-app"));
1778  return false;
1779  }
1780  }
1781 
1782  if (engines.size() > 1) {
1783  qCDebug(CUTELYST_SERVER) << "Starting threads";
1784  }
1785 
1786  for (ServerEngine *engine : engines) {
1787  QThread *thread = engine->thread();
1788  if (thread != qApp->thread()) {
1789 #ifdef Q_OS_LINUX
1790  if (!qEnvironmentVariableIsSet("CUTELYST_QT_EVENT_LOOP")) {
1791  thread->setEventDispatcher(new EventDispatcherEPoll);
1792  }
1793 #endif
1794 
1795  thread->start();
1796  }
1797  }
1798 
1799  Q_EMIT postForked(workerId);
1800 
1801  QTimer::singleShot(std::chrono::seconds{1}, this, [=]() {
1802  // THIS IS NEEDED when
1803  // --master --threads N --experimental-thread-balancer
1804  // for some reason sometimes the balancer doesn't get
1805  // the ready signal (which stays on event loop queue)
1806  // from TcpServer and doesn't starts listening.
1807  qApp->processEvents();
1808  });
1809 
1810  return true;
1811 }
1812 
1813 bool ServerPrivate::writePidFile(const QString &filename)
1814 {
1815  if (filename.isEmpty()) {
1816  return true;
1817  }
1818 
1819  QFile file(filename);
1820  if (!file.open(QFile::WriteOnly | QFile::Text)) {
1821  std::cerr << "Failed write pid file " << qPrintable(filename) << std::endl;
1822  return false;
1823  }
1824 
1825  std::cout << "Writing pidfile to " << qPrintable(filename) << std::endl;
1827 
1828  return true;
1829 }
1830 
1831 ServerEngine *ServerPrivate::createEngine(Application *app, int workerCore)
1832 {
1833  Q_Q(Server);
1834 
1835  // If threads is greater than 1 we need a new application instance
1836  if (workerCore > 0) {
1837  app = qobject_cast<Application *>(app->metaObject()->newInstance());
1838  if (!app) {
1839  qFatal("*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
1840  "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
1841  }
1842  }
1843 
1844  auto engine = new ServerEngine(app, workerCore, opt, q);
1845  connect(this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1846  connect(
1847  this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, Qt::QueuedConnection);
1848  connect(engine,
1849  &ServerEngine::shutdownCompleted,
1850  this,
1851  &ServerPrivate::engineShutdown,
1853  connect(
1854  engine, &ServerEngine::started, this, &ServerPrivate::workerStarted, Qt::QueuedConnection);
1855 
1856  engine->setConfig(config);
1857  engine->setServers(servers);
1858  if (!engine->init()) {
1859  std::cerr << "Application failed to init(), cheaping core: " << workerCore << std::endl;
1860  delete engine;
1861  return nullptr;
1862  }
1863 
1864  engines.push_back(engine);
1865 
1866  // If threads is greater than 1 we need a new thread
1867  if (workerCore > 0) {
1868  // To make easier for engines to clean up
1869  // the NEW app must be a child of it
1870  app->setParent(engine);
1871 
1872  auto thread = new QThread(this);
1873  engine->moveToThread(thread);
1874  } else {
1875  engine->setParent(this);
1876  }
1877 
1878  return engine;
1879 }
1880 
1881 void ServerPrivate::loadConfig()
1882 {
1883  if (loadingConfig) {
1884  return;
1885  }
1886 
1887  loadingConfig = true;
1888 
1889  if (configToLoad.isEmpty()) {
1890  loadingConfig = false;
1891  return;
1892  }
1893 
1894  auto fileToLoad = configToLoad.dequeue();
1895 
1896  if (fileToLoad.first.isEmpty()) {
1897  qCWarning(CUTELYST_SERVER) << "Can not load config from empty config file name";
1898  loadingConfig = false;
1899  return;
1900  }
1901 
1902  if (configLoaded.contains(fileToLoad.first)) {
1903  loadingConfig = false;
1904  return;
1905  }
1906 
1907  configLoaded.append(fileToLoad.first);
1908 
1909  QVariantMap loadedConfig;
1910  switch (fileToLoad.second) {
1911  case ConfigFormat::Ini:
1912  qCInfo(CUTELYST_SERVER) << "Loading INI configuratin:" << fileToLoad.first;
1913  loadedConfig = Engine::loadIniConfig(fileToLoad.first);
1914  break;
1915  case ConfigFormat::Json:
1916  qCInfo(CUTELYST_SERVER) << "Loading JSON configuration:" << fileToLoad.first;
1917  loadedConfig = Engine::loadJsonConfig(fileToLoad.first);
1918  break;
1919  }
1920 
1921  auto loadedIt = loadedConfig.cbegin();
1922  while (loadedIt != loadedConfig.cend()) {
1923  if (config.contains(loadedIt.key())) {
1924  QVariantMap currentMap = config.value(loadedIt.key()).toMap();
1925  const QVariantMap loadedMap = loadedIt.value().toMap();
1926  auto loadedMapIt = loadedMap.cbegin();
1927  while (loadedMapIt != loadedMap.cend()) {
1928  currentMap.insert(loadedMapIt.key(), loadedMapIt.value());
1929  ++loadedMapIt;
1930  }
1931  config.insert(loadedIt.key(), currentMap);
1932  } else {
1933  config.insert(loadedIt.key(), loadedIt.value());
1934  }
1935  ++loadedIt;
1936  }
1937 
1938  QVariantMap sessionConfig = loadedConfig.value(u"server"_s).toMap();
1939 
1940  applyConfig(sessionConfig);
1941 
1942  opt.insert(sessionConfig);
1943 
1944  loadingConfig = false;
1945 
1946  if (!configToLoad.empty()) {
1947  loadConfig();
1948  }
1949 }
1950 
1951 void ServerPrivate::applyConfig(const QVariantMap &config)
1952 {
1953  Q_Q(Server);
1954 
1955  auto it = config.constBegin();
1956  while (it != config.constEnd()) {
1957  QString normKey = it.key();
1958  normKey.replace(u'-', u'_');
1959 
1960  int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
1961  if (ix == -1) {
1962  ++it;
1963  continue;
1964  }
1965 
1966  const QVariant value = it.value();
1967  const QMetaProperty prop = q->metaObject()->property(ix);
1968  if (prop.userType() == value.userType()) {
1969  if (prop.userType() == QMetaType::QStringList) {
1970  const QStringList currentValues = prop.read(q).toStringList();
1971  prop.write(q, currentValues + value.toStringList());
1972  } else {
1973  prop.write(q, value);
1974  }
1975  } else if (prop.userType() == QMetaType::QStringList) {
1976  const QStringList currentValues = prop.read(q).toStringList();
1977  prop.write(q, currentValues + QStringList{value.toString()});
1978  } else {
1979  prop.write(q, value);
1980  }
1981 
1982  ++it;
1983  }
1984 }
1985 
1986 Protocol *ServerPrivate::getHttpProto()
1987 {
1988  Q_Q(Server);
1989  if (!protoHTTP) {
1990  if (upgradeH2c) {
1991  protoHTTP = new ProtocolHttp(q, getHttp2Proto());
1992  } else {
1993  protoHTTP = new ProtocolHttp(q);
1994  }
1995  }
1996  return protoHTTP;
1997 }
1998 
1999 ProtocolHttp2 *ServerPrivate::getHttp2Proto()
2000 {
2001  Q_Q(Server);
2002  if (!protoHTTP2) {
2003  protoHTTP2 = new ProtocolHttp2(q);
2004  }
2005  return protoHTTP2;
2006 }
2007 
2008 Protocol *ServerPrivate::getFastCgiProto()
2009 {
2010  Q_Q(Server);
2011  if (!protoFCGI) {
2012  protoFCGI = new ProtocolFastCGI(q);
2013  }
2014  return protoFCGI;
2015 }
2016 
2017 #include "moc_server.cpp"
2018 #include "moc_server_p.cpp"
QFuture< ArgsType< Signal >> connect(Sender *sender, Signal signal)
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
QObject * newInstance(Args &&... arguments) const const
virtual bool init() override
QThread * thread() const const
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
static QVariantMap loadIniConfig(const QString &filename)
Definition: engine.cpp:275
void addLibraryPath(const QString &path)
QVariantMap config() const noexcept
Definition: server.cpp:1661
QString chdir2
Definition: server.h:265
void start(Priority priority)
QString number(double n, char format, int precision)
QByteArray number(double n, char format, int precision)
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
bool isSet(const QCommandLineOption &option) const const
QStringList values(const QCommandLineOption &option) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString value(const QCommandLineOption &option) const const
CaseInsensitive
int toInt(bool *ok, int base) const const
bool isEmpty() const const
virtual ~Server()
Definition: server.cpp:89
bool start(Cutelyst::Application *app=nullptr)
Definition: server.cpp: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
bool write(QObject *object, QVariant &&v) const const
void showHelp(int exitCode)
QThread * currentThread()
bool addOption(const QCommandLineOption &option)
The Cutelyst application.
Definition: application.h:72
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)
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