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