5#include "localserver.h"
7#include "protocolfastcgi.h"
8#include "protocolhttp.h"
9#include "protocolhttp2.h"
11#include "serverengine.h"
13#include "tcpserverbalancer.h"
18# include "windowsfork.h"
22# include "../EventLoopEPoll/eventdispatcher_epoll.h"
23# include "systemdnotify.h"
28#include <QCommandLineParser>
29#include <QCoreApplication>
31#include <QLoggingCategory>
32#include <QMetaProperty>
33#include <QPluginLoader>
35#include <QSocketNotifier>
40Q_LOGGING_CATEGORY(CUTELYST_SERVER,
"cutelyst.server", QtWarningMsg)
43using namespace Qt::Literals::StringLiterals;
47 , d_ptr(new ServerPrivate(this))
49 QCoreApplication::addLibraryPath(QDir().absolutePath());
51 if (!qEnvironmentVariableIsSet(
"QT_MESSAGE_PATTERN")) {
52 if (qEnvironmentVariableIsSet(
"JOURNAL_STREAM")) {
54 qSetMessagePattern(u
"%{category}[%{type}] %{message}"_s);
56 qSetMessagePattern(u
"%{pid}:%{threadid} %{category}[%{type}] %{message}"_s);
61 if (!qEnvironmentVariableIsSet(
"CUTELYST_QT_EVENT_LOOP")) {
62 qCInfo(CUTELYST_SERVER) <<
"Trying to install EPoll event loop";
63 QCoreApplication::setEventDispatcher(
new EventDispatcherEPoll);
67 auto cleanUp = [
this]() {
71 d->protoHTTP =
nullptr;
74 d->protoHTTP2 =
nullptr;
77 d->protoFCGI =
nullptr;
79 qDeleteAll(d->engines);
81 d->mainEngine =
nullptr;
83 for (
ServerEngine *engine : findChildren<ServerEngine *>(Qt::FindDirectChildrenOnly)) {
87 qDeleteAll(d->servers);
90 delete d->genericFork;
91 d->genericFork =
nullptr;
101 std::cout <<
"Cutelyst-Server terminated" <<
'\n';
108 QCommandLineParser parser;
109 parser.setApplicationDescription(
112 qtTrId(
"cutelystd-cli-desc"));
113 parser.addHelpOption();
114 parser.addVersionOption();
116 QCommandLineOption iniOpt(u
"ini"_s,
121 qtTrId(
"cutelystd-opt-ini-desc"),
124 qtTrId(
"cutelystd-opt-value-file"));
125 parser.addOption(iniOpt);
127 QCommandLineOption jsonOpt({u
"j"_s, u
"json"_s},
132 qtTrId(
"cutelystd-opt-json-desc"),
133 qtTrId(
"cutelystd-opt-value-file"));
134 parser.addOption(jsonOpt);
136 QCommandLineOption chdirOpt(
140 qtTrId(
"cutelystd-opt-chdir-desc"),
143 qtTrId(
"cutelystd-opt-value-directory"));
144 parser.addOption(chdirOpt);
146 QCommandLineOption chdir2Opt(
150 qtTrId(
"cutelystd-opt-chdir2-desc"),
151 qtTrId(
"cutelystd-opt-value-directory"));
152 parser.addOption(chdir2Opt);
154 QCommandLineOption lazyOpt(
158 qtTrId(
"cutelystd-opt-lazy-desc"));
159 parser.addOption(lazyOpt);
161 QCommandLineOption applicationOpt({u
"application"_s, u
"a"_s},
164 qtTrId(
"cutelystd-opt-application-desc"),
165 qtTrId(
"cutelystd-opt-value-file"));
166 parser.addOption(applicationOpt);
168 QCommandLineOption threadsOpt({u
"threads"_s, u
"t"_s},
172 qtTrId(
"cutelystd-opt-threads-desc"),
175 qtTrId(
"cutelystd-opt-threads-value"));
176 parser.addOption(threadsOpt);
179 QCommandLineOption processesOpt({u
"processes"_s, u
"p"_s},
183 qtTrId(
"cutelystd-opt-processes-desc"),
186 qtTrId(
"cutelystd-opt-processes-value"));
187 parser.addOption(processesOpt);
190 QCommandLineOption masterOpt({u
"master"_s, u
"M"_s},
193 qtTrId(
"cutelystd-opt-master-desc"));
194 parser.addOption(masterOpt);
196 QCommandLineOption listenQueueOpt({u
"listen"_s, u
"l"_s},
199 qtTrId(
"cutelystd-opt-listen-desc"),
202 qtTrId(
"cutelystd-opt-value-size"));
203 parser.addOption(listenQueueOpt);
205 QCommandLineOption bufferSizeOpt({u
"buffer-size"_s, u
"b"_s},
208 qtTrId(
"cutelystd-opt-buffer-size-desc"),
211 qtTrId(
"cutelystd-opt-value-bytes"));
212 parser.addOption(bufferSizeOpt);
214 QCommandLineOption postBufferingOpt(
220 qtTrId(
"cutelystd-opt-post-buffering-desc"),
221 qtTrId(
"cutelystd-opt-value-bytes"));
222 parser.addOption(postBufferingOpt);
224 QCommandLineOption postBufferingBufsizeOpt(
225 u
"post-buffering-bufsize"_s,
228 qtTrId(
"cutelystd-opt-post-buffering-bufsize-desc"),
229 qtTrId(
"cutelystd-opt-value-bytes"));
230 parser.addOption(postBufferingBufsizeOpt);
232 QCommandLineOption httpSocketOpt({u
"http-socket"_s, u
"h1"_s},
235 qtTrId(
"cutelystd-opt-http-socket-desc"),
238 qtTrId(
"cutelystd-opt-value-address"));
239 parser.addOption(httpSocketOpt);
241 QCommandLineOption http2SocketOpt(
242 {u
"http2-socket"_s, u
"h2"_s},
245 qtTrId(
"cutelystd-opt-http2-socket-desc"),
246 qtTrId(
"cutelystd-opt-value-address"));
247 parser.addOption(http2SocketOpt);
249 QCommandLineOption http2HeaderTableSizeOpt(u
"http2-header-table-size"_s,
252 qtTrId(
"cutelystd-opt-http2-header-table-size-desc"),
253 qtTrId(
"cutelystd-opt-value-size"));
254 parser.addOption(http2HeaderTableSizeOpt);
256 QCommandLineOption upgradeH2cOpt(u
"upgrade-h2c"_s,
259 qtTrId(
"cutelystd-opt-upgrade-h2c-desc"));
260 parser.addOption(upgradeH2cOpt);
262 QCommandLineOption httpsH2Opt(u
"https-h2"_s,
265 qtTrId(
"cutelystd-opt-https-h2-desc"));
266 parser.addOption(httpsH2Opt);
268 QCommandLineOption httpsSocketOpt({u
"https-socket"_s, u
"hs1"_s},
271 qtTrId(
"cutelystd-opt-https-socket-desc"),
273 qtTrId(
"cutelystd-opt-value-httpsaddress"));
274 parser.addOption(httpsSocketOpt);
276 QCommandLineOption fastcgiSocketOpt(
280 qtTrId(
"cutelystd-opt-fastcgi-socket-desc"),
281 qtTrId(
"cutelystd-opt-value-address"));
282 parser.addOption(fastcgiSocketOpt);
284 QCommandLineOption socketAccessOpt(
288 qtTrId(
"cutelystd-opt-socket-access-desc"),
291 qtTrId(
"cutelystd-opt-socket-access-value"));
292 parser.addOption(socketAccessOpt);
294 QCommandLineOption socketTimeoutOpt({u
"socket-timeout"_s, u
"z"_s},
297 qtTrId(
"cutelystd-opt-socket-timeout-desc"),
300 qtTrId(
"cutelystd-opt-socket-timeout-value"));
301 parser.addOption(socketTimeoutOpt);
303 QCommandLineOption staticMapOpt(u
"static-map"_s,
309 qtTrId(
"cutelystd-opt-static-map-desc"),
312 qtTrId(
"cutelystd-opt-value-static-map"));
313 parser.addOption(staticMapOpt);
315 QCommandLineOption staticMap2Opt(u
"static-map2"_s,
319 qtTrId(
"cutelystd-opt-static-map2-desc"),
322 qtTrId(
"cutelystd-opt-value-static-map"));
323 parser.addOption(staticMap2Opt);
325 QCommandLineOption autoReloadOpt({u
"auto-restart"_s, u
"r"_s},
329 qtTrId(
"cutelystd-opt-auto-restart-desc"));
330 parser.addOption(autoReloadOpt);
332 QCommandLineOption touchReloadOpt(
337 qtTrId(
"cutelystd-opt-touch-reload-desc"),
338 qtTrId(
"cutelystd-opt-value-file"));
339 parser.addOption(touchReloadOpt);
341 QCommandLineOption tcpNoDelay(u
"tcp-nodelay"_s,
344 qtTrId(
"cutelystd-opt-tcp-nodelay-desc"));
345 parser.addOption(tcpNoDelay);
347 QCommandLineOption soKeepAlive(u
"so-keepalive"_s,
350 qtTrId(
"cutelystd-opt-so-keepalive-desc"));
351 parser.addOption(soKeepAlive);
353 QCommandLineOption socketSndbufOpt(u
"socket-sndbuf"_s,
357 qtTrId(
"cutelystd-opt-socket-sndbuf-desc"),
358 qtTrId(
"cutelystd-opt-value-bytes"));
359 parser.addOption(socketSndbufOpt);
361 QCommandLineOption socketRcvbufOpt(u
"socket-rcvbuf"_s,
365 qtTrId(
"cutelystd-opt-socket-rcvbuf-desc"),
366 qtTrId(
"cutelystd-opt-value-bytes"));
367 parser.addOption(socketRcvbufOpt);
369 QCommandLineOption wsMaxSize(u
"websocket-max-size"_s,
373 qtTrId(
"cutelystd-opt-websocket-max-size-desc"),
376 qtTrId(
"cutelystd-opt-websocket-max-size-value"));
377 parser.addOption(wsMaxSize);
379 QCommandLineOption pidfileOpt(u
"pidfile"_s,
382 qtTrId(
"cutelystd-opt-pidfile-desc"),
385 qtTrId(
"cutelystd-opt-value-pidfile"));
386 parser.addOption(pidfileOpt);
388 QCommandLineOption pidfile2Opt(u
"pidfile2"_s,
391 qtTrId(
"cutelystd-opt-pidfile2-desc"),
392 qtTrId(
"cutelystd-opt-value-pidfile"));
393 parser.addOption(pidfile2Opt);
396 QCommandLineOption stopOpt(u
"stop"_s,
399 qtTrId(
"cutelystd-opt-stop-desc"),
400 qtTrId(
"cutelystd-opt-value-pidfile"));
401 parser.addOption(stopOpt);
403 QCommandLineOption uidOpt(u
"uid"_s,
406 qtTrId(
"cutelystd-opt-uid-desc"),
409 qtTrId(
"cutelystd-opt-uid-value"));
410 parser.addOption(uidOpt);
412 QCommandLineOption gidOpt(u
"gid"_s,
415 qtTrId(
"cutelystd-opt-gid-desc"),
418 qtTrId(
"cutelystd-opt-gid-value"));
419 parser.addOption(gidOpt);
421 QCommandLineOption noInitgroupsOpt(u
"no-initgroups"_s,
424 qtTrId(
"cutelystd-opt-no-init-groups-desc"));
425 parser.addOption(noInitgroupsOpt);
427 QCommandLineOption chownSocketOpt(u
"chown-socket"_s,
430 qtTrId(
"cutelystd-opt-chown-socket-desc"),
433 qtTrId(
"cutelystd-opt-chown-socket-value"));
434 parser.addOption(chownSocketOpt);
436 QCommandLineOption umaskOpt(u
"umask"_s,
439 qtTrId(
"cutelystd-opt-umask-desc"),
442 qtTrId(
"cutelystd-opt-umask-value"));
443 parser.addOption(umaskOpt);
445 QCommandLineOption cpuAffinityOpt(
449 qtTrId(
"cutelystd-opt-cpu-affinity-desc"),
452 qtTrId(
"cutelystd-opt-cpu-affinity-value"));
453 parser.addOption(cpuAffinityOpt);
457 QCommandLineOption reusePortOpt(u
"reuse-port"_s,
460 qtTrId(
"cutelystd-opt-reuse-port-desc"));
461 parser.addOption(reusePortOpt);
464 QCommandLineOption threadBalancerOpt(
465 u
"experimental-thread-balancer"_s,
468 qtTrId(
"cutelystd-opt-experimental-thread-balancer-desc"));
469 parser.addOption(threadBalancerOpt);
471 QCommandLineOption frontendProxy(u
"using-frontend-proxy"_s,
474 qtTrId(
"cutelystd-opt-using-frontend-proxy-desc"));
475 parser.addOption(frontendProxy);
478 parser.process(arguments);
480 setIni(parser.values(iniOpt));
482 setJson(parser.values(jsonOpt));
484 if (parser.isSet(chdirOpt)) {
485 setChdir(parser.value(chdirOpt));
488 if (parser.isSet(chdir2Opt)) {
489 setChdir2(parser.value(chdir2Opt));
492 if (parser.isSet(threadsOpt)) {
493 setThreads(parser.value(threadsOpt));
496 if (parser.isSet(socketAccessOpt)) {
497 setSocketAccess(parser.value(socketAccessOpt));
500 if (parser.isSet(socketTimeoutOpt)) {
502 auto size = parser.value(socketTimeoutOpt).toInt(&ok);
503 setSocketTimeout(size);
504 if (!ok || size < 0) {
509 if (parser.isSet(pidfileOpt)) {
510 setPidfile(parser.value(pidfileOpt));
513 if (parser.isSet(pidfile2Opt)) {
514 setPidfile2(parser.value(pidfile2Opt));
518 if (parser.isSet(stopOpt)) {
519 UnixFork::stopSERVER(parser.value(stopOpt));
522 if (parser.isSet(processesOpt)) {
523 setProcesses(parser.value(processesOpt));
526 if (parser.isSet(uidOpt)) {
527 setUid(parser.value(uidOpt));
530 if (parser.isSet(gidOpt)) {
531 setGid(parser.value(gidOpt));
534 if (parser.isSet(noInitgroupsOpt)) {
535 setNoInitgroups(
true);
538 if (parser.isSet(chownSocketOpt)) {
539 setChownSocket(parser.value(chownSocketOpt));
542 if (parser.isSet(umaskOpt)) {
543 setUmask(parser.value(umaskOpt));
546 if (parser.isSet(cpuAffinityOpt)) {
548 auto value = parser.value(cpuAffinityOpt).toInt(&ok);
549 setCpuAffinity(value);
550 if (!ok || value < 0) {
557 if (parser.isSet(reusePortOpt)) {
562 if (parser.isSet(lazyOpt)) {
566 if (parser.isSet(listenQueueOpt)) {
568 auto size = parser.value(listenQueueOpt).toInt(&ok);
569 setListenQueue(size);
570 if (!ok || size < 1) {
575 if (parser.isSet(bufferSizeOpt)) {
577 auto size = parser.value(bufferSizeOpt).toInt(&ok);
579 if (!ok || size < 1) {
584 if (parser.isSet(postBufferingOpt)) {
586 auto size = parser.value(postBufferingOpt).toLongLong(&ok);
587 setPostBuffering(size);
588 if (!ok || size < 1) {
593 if (parser.isSet(postBufferingBufsizeOpt)) {
595 auto size = parser.value(postBufferingBufsizeOpt).toLongLong(&ok);
596 setPostBufferingBufsize(size);
597 if (!ok || size < 1) {
602 if (parser.isSet(applicationOpt)) {
603 setApplication(parser.value(applicationOpt));
606 if (parser.isSet(masterOpt)) {
610 if (parser.isSet(autoReloadOpt)) {
614 if (parser.isSet(tcpNoDelay)) {
618 if (parser.isSet(soKeepAlive)) {
619 setSoKeepalive(
true);
622 if (parser.isSet(upgradeH2cOpt)) {
626 if (parser.isSet(httpsH2Opt)) {
630 if (parser.isSet(socketSndbufOpt)) {
632 auto size = parser.value(socketSndbufOpt).toInt(&ok);
633 setSocketSndbuf(size);
634 if (!ok || size < 1) {
639 if (parser.isSet(socketRcvbufOpt)) {
641 auto size = parser.value(socketRcvbufOpt).toInt(&ok);
642 setSocketRcvbuf(size);
643 if (!ok || size < 1) {
648 if (parser.isSet(wsMaxSize)) {
650 auto size = parser.value(wsMaxSize).toInt(&ok);
651 setWebsocketMaxSize(size);
652 if (!ok || size < 1) {
657 if (parser.isSet(http2HeaderTableSizeOpt)) {
659 auto size = parser.value(http2HeaderTableSizeOpt).toUInt(&ok);
660 setHttp2HeaderTableSize(size);
661 if (!ok || size < 1) {
666 if (parser.isSet(frontendProxy)) {
667 setUsingFrontendProxy(
true);
670 setHttpSocket(httpSocket() + parser.values(httpSocketOpt));
672 setHttp2Socket(http2Socket() + parser.values(http2SocketOpt));
674 setHttpsSocket(httpsSocket() + parser.values(httpsSocketOpt));
676 setFastcgiSocket(fastcgiSocket() + parser.values(fastcgiSocketOpt));
678 setStaticMap(staticMap() + parser.values(staticMapOpt));
680 setStaticMap2(staticMap2() + parser.values(staticMap2Opt));
682 setTouchReload(touchReload() + parser.values(touchReloadOpt));
684 d->threadBalancer = parser.isSet(threadBalancerOpt);
690 std::cout <<
"Cutelyst-Server starting" <<
'\n';
692 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER") && !d->master) {
694 <<
"*** WARNING: you are running Cutelyst-Server without its master process manager ***"
699 if (d->processes == -1 && d->threads == -1) {
700 d->processes = UnixFork::idealProcessCount();
701 d->threads = UnixFork::idealThreadCount() / d->processes;
702 }
else if (d->processes == -1) {
703 d->processes = UnixFork::idealThreadCount();
704 }
else if (d->threads == -1) {
705 d->threads = UnixFork::idealThreadCount();
708 if (d->processes == 0 && d->master) {
711 delete d->genericFork;
712 d->genericFork =
new UnixFork(d->processes, qMax(d->threads, 1), !d->userEventLoop,
this);
714 if (d->processes == -1) {
717 if (d->threads == -1) {
718 d->threads = QThread::idealThreadCount();
720 delete d->genericFork;
725 d->genericFork, &AbstractFork::forked, d, &ServerPrivate::postFork, Qt::DirectConnection);
727 d->genericFork, &AbstractFork::shutdown, d, &ServerPrivate::shutdown, Qt::DirectConnection);
729 if (d->master && d->lazy) {
730 if (d->autoReload && !d->application.isEmpty()) {
731 d->touchReload.append(d->application);
733 d->genericFork->setTouchReload(d->touchReload);
737 if (d->master && !d->genericFork->continueMaster(&ret)) {
742 if (systemdNotify::is_systemd_notify_available()) {
744 sd->setWatchdog(
true, systemdNotify::sd_watchdog_enabled(
true));
746 sd->sendStatus(qApp->applicationName().toLatin1() +
" is ready");
749 connect(d, &ServerPrivate::postForked, sd, [sd] { sd->setWatchdog(
false); });
750 qInfo(CUTELYST_SERVER) <<
"systemd notify detected";
758 if (!d->listenTcpSockets()) {
759 const QString error =
760 d->lastListenError.isEmpty()
761 ? QStringLiteral(
"No specified sockets were able to be opened")
762 : d->lastListenError;
768 if (!d->writePidFile(d->pidfile)) {
770 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-write-pidfile").arg(d->pidfile));
774 bool isListeningLocalSockets =
false;
775 if (!d->chownSocket.isEmpty()) {
776 if (!d->listenLocalSockets()) {
778 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-open-local-socket"));
781 isListeningLocalSockets =
true;
784 if (!d->umask.isEmpty() && !UnixFork::setUmask(d->umask.toLatin1())) {
788 if (!UnixFork::setGidUid(d->gid, d->uid, d->noInitgroups)) {
794 if (!isListeningLocalSockets) {
796 d->listenLocalSockets();
802 if (!d->listenTcpSockets()) {
803 const QString error =
804 d->lastListenError.isEmpty()
805 ? QStringLiteral(
"No specified sockets were able to be opened")
806 : d->lastListenError;
812 if (d->servers.empty()) {
813 std::cout <<
"Please specify a socket to listen to" <<
'\n';
815 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-specified"));
819 d->writePidFile(d->pidfile2);
821 if (!d->chdir.isEmpty()) {
822 std::cout <<
"Changing directory to: " << d->chdir.toLatin1().constData() <<
'\n';
823 if (!QDir::setCurrent(d->chdir)) {
824 Q_EMIT
errorOccured(QString::fromLatin1(
"Failed to chdir to: '%s'")
825 .arg(QString::fromLatin1(d->chdir.toLatin1().constData())));
833 if (!d->setupApplication()) {
835 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
840 if (d->userEventLoop) {
845 ret = d->genericFork->exec(d->lazy, d->master);
856 QStringLiteral(
"Server not fully stopped. Wait for shutdown to complete."));
863 d->userEventLoop =
true;
864 d->workersNotRunning = 1;
865 d->lastListenError.clear();
870 qputenv(
"CUTELYST_SERVER_IGNORE_MASTER", QByteArrayLiteral(
"1"));
872 if (
exec(app) == 0) {
882 if (d->userEventLoop) {
883 Q_EMIT d->shutdown();
887ServerPrivate::~ServerPrivate()
894bool ServerPrivate::listenTcpSockets()
896 lastListenError.clear();
898 if (httpSockets.isEmpty() && httpsSockets.isEmpty() && http2Sockets.isEmpty() &&
899 fastcgiSockets.isEmpty()) {
905 bool httpOk = std::ranges::all_of(httpSockets, [
this](
const auto &socket) {
906 return listenTcp(socket, getHttpProto(),
false);
913 bool httpsOk = std::ranges::all_of(httpsSockets, [
this](
const auto &socket) {
914 return listenTcp(socket, getHttpProto(),
true);
921 bool http2Ok = std::ranges::all_of(http2Sockets, [
this](
const auto &socket) {
922 return listenTcp(socket, getHttp2Proto(),
false);
929 bool allOk = std::ranges::all_of(fastcgiSockets, [
this](
const QString &socket) {
930 return listenTcp(socket, getFastCgiProto(),
false);
936bool ServerPrivate::listenTcp(
const QString &line,
Protocol *protocol,
bool secure)
940 if (line.startsWith(u
'/')) {
945 server->setBalancer(threadBalancer);
946 const bool ret = server->listen(line, protocol, secure);
948 if (!ret || !server->socketDescriptor()) {
950 server->bindError().isEmpty() ? server->errorString() : server->bindError();
952 lastListenError = QStringLiteral(
"Failed to listen on %1: %2").arg(line, err);
955 QStringLiteral(
"Failed to listen on %1: no socket descriptor").arg(line);
957 qCWarning(CUTELYST_SERVER) << lastListenError;
962 auto qEnum = Protocol::staticMetaObject.enumerator(0);
963 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
964 << QByteArray::number(
static_cast<int>(servers.size())).constData()
965 <<
" bound to TCP address " << server->serverName().constData() <<
" fd "
966 << QByteArray::number(server->socketDescriptor()).constData() <<
'\n';
967 servers.emplace_back(server);
971bool ServerPrivate::listenLocalSockets()
973 QStringList http = httpSockets;
974 QStringList http2 = http2Sockets;
975 QStringList fastcgi = fastcgiSockets;
980 std::vector<int> fds = systemdNotify::listenFds();
983 if (server->listen(fd)) {
984 const QString name = server->serverName();
985 const QString fullName = server->fullServerName();
988 if (http.removeOne(fullName) || http.removeOne(name)) {
989 protocol = getHttpProto();
990 }
else if (http2.removeOne(fullName) || http2.removeOne(name)) {
991 protocol = getHttp2Proto();
992 }
else if (fastcgi.removeOne(fullName) || fastcgi.removeOne(name)) {
993 protocol = getFastCgiProto();
995 std::cerr <<
"systemd activated socket does not match any configured socket"
999 server->setProtocol(protocol);
1000 server->pauseAccepting();
1002 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1003 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
1004 << QByteArray::number(
static_cast<int>(servers.size())).constData()
1005 <<
" bound to LOCAL address " << qPrintable(fullName) <<
" fd "
1006 << QByteArray::number(server->socket()).constData() <<
'\n';
1007 servers.push_back(server);
1009 std::cerr <<
"Failed to listen on activated LOCAL FD: "
1010 << QByteArray::number(fd).constData() <<
" : "
1011 << qPrintable(server->errorString()) <<
'\n';
1018 const auto httpConst = http;
1019 for (
const auto &socket : httpConst) {
1020 ret |= listenLocal(socket, getHttpProto());
1023 const auto http2Const = http2;
1024 for (
const auto &socket : http2Const) {
1025 ret |= listenLocal(socket, getHttp2Proto());
1028 const auto fastcgiConst = fastcgi;
1029 for (
const auto &socket : fastcgiConst) {
1030 ret |= listenLocal(socket, getFastCgiProto());
1036bool ServerPrivate::listenLocal(
const QString &line,
Protocol *protocol)
1041 if (line.startsWith(u
'/')) {
1043 server->setProtocol(protocol);
1044 if (!socketAccess.isEmpty()) {
1045 QLocalServer::SocketOptions options;
1046 if (socketAccess.contains(u
'u')) {
1047 options |= QLocalServer::UserAccessOption;
1050 if (socketAccess.contains(u
'g')) {
1051 options |= QLocalServer::GroupAccessOption;
1054 if (socketAccess.contains(u
'o')) {
1055 options |= QLocalServer::OtherAccessOption;
1057 server->setSocketOptions(options);
1060 QLocalServer::removeServer(line);
1061 server->setListenBacklogSize(listenQueue);
1062 ret = server->listen(line);
1063 server->pauseAccepting();
1065 if (!ret || !server->socket()) {
1066 std::cerr <<
"Failed to listen on LOCAL: " << qPrintable(line) <<
" : "
1067 << qPrintable(server->errorString()) <<
'\n';
1072 if (!chownSocket.isEmpty()) {
1073 UnixFork::chownSocket(line, chownSocket);
1076 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1077 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
1078 << QByteArray::number(
static_cast<int>(servers.size())).constData()
1079 <<
" bound to LOCAL address " << qPrintable(line) <<
" fd "
1080 << QByteArray::number(server->socket()).constData() <<
'\n';
1081 servers.push_back(server);
1087void Server::setApplication(
const QString &application)
1092 if (loader.fileName().isEmpty()) {
1097 d->application = loader.fileName();
1105 return d->application;
1108void Server::setThreads(
const QString &threads)
1111 if (
threads.compare(u
"auto", Qt::CaseInsensitive) == 0) {
1114 d->threads = qMax(1,
threads.toInt());
1122 if (d->threads == -1) {
1125 return QString::number(d->threads);
1128void Server::setProcesses(
const QString &process)
1132 if (process.compare(u
"auto", Qt::CaseInsensitive) == 0) {
1135 d->processes = process.toInt();
1144 if (d->processes == -1) {
1147 return QString::number(d->processes);
1150void Server::setChdir(
const QString &chdir)
1163void Server::setHttpSocket(
const QStringList &httpSocket)
1166 d->httpSockets = httpSocket;
1170QStringList Server::httpSocket()
const
1173 return d->httpSockets;
1176void Server::setHttp2Socket(
const QStringList &http2Socket)
1179 d->http2Sockets = http2Socket;
1183QStringList Server::http2Socket()
const
1186 return d->http2Sockets;
1189void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1192 d->http2HeaderTableSize = headerTableSize;
1196quint32 Server::http2HeaderTableSize()
const
1199 return d->http2HeaderTableSize;
1202void Server::setUpgradeH2c(
bool enable)
1205 d->upgradeH2c = enable;
1209bool Server::upgradeH2c()
const
1212 return d->upgradeH2c;
1215void Server::setHttpsH2(
bool enable)
1218 d->httpsH2 = enable;
1222bool Server::httpsH2()
const
1228void Server::setHttpsSocket(
const QStringList &httpsSocket)
1231 d->httpsSockets = httpsSocket;
1235QStringList Server::httpsSocket()
const
1238 return d->httpsSockets;
1241void Server::setFastcgiSocket(
const QStringList &fastcgiSocket)
1244 d->fastcgiSockets = fastcgiSocket;
1248QStringList Server::fastcgiSocket()
const
1251 return d->fastcgiSockets;
1254void Server::setSocketAccess(
const QString &socketAccess)
1257 d->socketAccess = socketAccess;
1261QString Server::socketAccess()
const
1264 return d->socketAccess;
1267void Server::setSocketTimeout(
int timeout)
1270 d->socketTimeout = timeout;
1274int Server::socketTimeout()
const
1277 return d->socketTimeout;
1280void Server::setChdir2(
const QString &chdir2)
1293void Server::setIni(
const QStringList &files)
1296 d->ini.append(files);
1297 d->ini.removeDuplicates();
1300 for (
const QString &file : files) {
1301 if (!d->configLoaded.contains(file)) {
1302 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Ini);
1303 if (!d->configToLoad.contains(fileToLoad)) {
1304 qCDebug(CUTELYST_SERVER) <<
"Enqueue INI config file:" << file;
1305 d->configToLoad.enqueue(fileToLoad);
1319void Server::setJson(
const QStringList &files)
1322 d->json.append(files);
1323 d->json.removeDuplicates();
1326 for (
const QString &file : files) {
1327 if (!d->configLoaded.contains(file)) {
1328 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Json);
1329 if (!d->configToLoad.contains(fileToLoad)) {
1330 qCDebug(CUTELYST_SERVER) <<
"Enqueue JSON config file:" << file;
1331 d->configToLoad.enqueue(fileToLoad);
1345void Server::setStaticMap(
const QStringList &staticMap)
1348 d->staticMaps = staticMap;
1352QStringList Server::staticMap()
const
1355 return d->staticMaps;
1358void Server::setStaticMap2(
const QStringList &staticMap)
1361 d->staticMaps2 = staticMap;
1365QStringList Server::staticMap2()
const
1368 return d->staticMaps2;
1371void Server::setMaster(
bool enable)
1374 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER")) {
1386void Server::setAutoReload(
bool enable)
1390 d->autoReload =
true;
1395bool Server::autoReload()
const
1398 return d->autoReload;
1401void Server::setTouchReload(
const QStringList &files)
1404 d->touchReload = files;
1408QStringList Server::touchReload()
const
1411 return d->touchReload;
1414void Server::setListenQueue(
int size)
1417 d->listenQueue = size;
1421int Server::listenQueue()
const
1424 return d->listenQueue;
1427void Server::setBufferSize(
int size)
1431 qCWarning(CUTELYST_SERVER) <<
"Buffer size must be at least 4096 bytes, ignoring";
1434 d->bufferSize = size;
1438int Server::bufferSize()
const
1441 return d->bufferSize;
1444void Server::setPostBuffering(qint64 size)
1447 d->postBuffering = size;
1451qint64 Server::postBuffering()
const
1454 return d->postBuffering;
1457void Server::setPostBufferingBufsize(qint64 size)
1461 qCWarning(CUTELYST_SERVER) <<
"Post buffer size must be at least 4096 bytes, ignoring";
1464 d->postBufferingBufsize = size;
1468qint64 Server::postBufferingBufsize()
const
1471 return d->postBufferingBufsize;
1474void Server::setTcpNodelay(
bool enable)
1477 d->tcpNodelay = enable;
1481bool Server::tcpNodelay()
const
1484 return d->tcpNodelay;
1487void Server::setSoKeepalive(
bool enable)
1490 d->soKeepalive = enable;
1494bool Server::soKeepalive()
const
1497 return d->soKeepalive;
1500void Server::setSocketSndbuf(
int value)
1503 d->socketSendBuf = value;
1507int Server::socketSndbuf()
const
1510 return d->socketSendBuf;
1513void Server::setSocketRcvbuf(
int value)
1516 d->socketReceiveBuf = value;
1520int Server::socketRcvbuf()
const
1523 return d->socketReceiveBuf;
1526void Server::setWebsocketMaxSize(
int value)
1529 d->websocketMaxSize = value * 1024;
1533int Server::websocketMaxSize()
const
1536 return d->websocketMaxSize / 1024;
1539void Server::setPidfile(
const QString &file)
1552void Server::setPidfile2(
const QString &file)
1565void Server::setUid(
const QString &uid)
1580void Server::setGid(
const QString &gid)
1595void Server::setNoInitgroups(
bool enable)
1599 d->noInitgroups = enable;
1604bool Server::noInitgroups()
const
1607 return d->noInitgroups;
1610void Server::setChownSocket(
const QString &chownSocket)
1614 d->chownSocket = chownSocket;
1619QString Server::chownSocket()
const
1622 return d->chownSocket;
1625void Server::setUmask(
const QString &value)
1640void Server::setCpuAffinity(
int value)
1644 d->cpuAffinity = value;
1649int Server::cpuAffinity()
const
1652 return d->cpuAffinity;
1655void Server::setReusePort(
bool enable)
1659 d->reusePort = enable;
1666bool Server::reusePort()
const
1669 return d->reusePort;
1672void Server::setLazy(
bool enable)
1685void Server::setUsingFrontendProxy(
bool enable)
1688 d->usingFrontendProxy = enable;
1692bool Server::usingFrontendProxy()
const
1695 return d->usingFrontendProxy;
1704bool ServerPrivate::setupApplication()
1710 if (userEventLoop) {
1711 qDeleteAll(engines);
1713 mainEngine =
nullptr;
1717 }
else if (!engines.empty() || mainEngine) {
1718 qDeleteAll(engines);
1720 mainEngine =
nullptr;
1724 std::cout <<
"Loading application: " << application.toLatin1().constData() <<
'\n';
1725 QPluginLoader loader(application);
1726 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::PreventUnloadHint);
1727 if (!loader.load()) {
1728 qCCritical(CUTELYST_SERVER) <<
"Could not load application:" << loader.errorString();
1732 QObject *instance = loader.instance();
1734 qCCritical(CUTELYST_SERVER) <<
"Could not get a QObject instance: %s\n"
1735 << loader.errorString();
1739 localApp = qobject_cast<Cutelyst::Application *>(instance);
1741 qCCritical(CUTELYST_SERVER)
1742 <<
"Could not cast Cutelyst::Application from instance: %s\n"
1743 << loader.errorString();
1751 qCDebug(CUTELYST_SERVER) <<
"Loaded application: " << QCoreApplication::applicationName();
1754 if (!chdir2.isEmpty()) {
1755 std::cout <<
"Changing directory2 to: " << chdir2.toLatin1().constData() <<
'\n';
1756 if (!QDir::setCurrent(chdir2)) {
1757 Q_EMIT q->errorOccured(QString::fromLatin1(
"Failed to chdir2 to: '%s'")
1758 .arg(QString::fromLatin1(chdir2.toLatin1().constData())));
1764 mainEngine = createEngine(localApp, 0);
1765 for (
int i = 1; i < threads; ++i) {
1766 if (createEngine(localApp, i)) {
1767 ++workersNotRunning;
1771 mainEngine = createEngine(localApp, 0);
1772 workersNotRunning = 1;
1776 std::cerr <<
"Application failed to init, cheaping..." <<
'\n';
1783void ServerPrivate::engineShutdown(
ServerEngine *engine)
1785 if (mainEngine == engine) {
1786 mainEngine =
nullptr;
1789 const auto engineThread = engine->thread();
1790 if (QThread::currentThread() != engineThread) {
1791 connect(engineThread, &QThread::finished,
this, [
this, engine] {
1792 auto [first, last] = std::ranges::remove(engines, engine);
1793 engines.erase(first, last);
1794 if (userEventLoop) {
1797 checkEngineShutdown();
1799 engineThread->quit();
1803 auto [first, last] = std::ranges::remove(engines, engine);
1804 engines.erase(first, last);
1806 if (userEventLoop) {
1810 checkEngineShutdown();
1813void ServerPrivate::checkEngineShutdown()
1815 if (engines.empty()) {
1816 if (userEventLoop) {
1818 Q_EMIT q->stopped();
1820 QTimer::singleShot(std::chrono::seconds{0},
this, [] { qApp->exit(15); });
1825void ServerPrivate::workerStarted()
1830 if (--workersNotRunning == 0) {
1835bool ServerPrivate::postFork(
int workerId)
1840 if (!setupApplication()) {
1841 Q_EMIT q->errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
1846 if (engines.size() > 1) {
1847 qCDebug(CUTELYST_SERVER) <<
"Starting threads";
1851 QThread *thread = engine->thread();
1852 if (thread != qApp->thread()) {
1854 if (!qEnvironmentVariableIsSet(
"CUTELYST_QT_EVENT_LOOP")) {
1856 thread->setEventDispatcher(
new EventDispatcherEPoll);
1864 Q_EMIT postForked(workerId);
1866 QTimer::singleShot(std::chrono::seconds{1},
this, [=]() {
1872 qApp->processEvents();
1878bool ServerPrivate::writePidFile(
const QString &filename)
1880 if (filename.isEmpty()) {
1884 QFile file(filename);
1885 if (!file.open(QFile::WriteOnly | QFile::Text)) {
1886 std::cerr <<
"Failed write pid file " << qPrintable(filename) <<
'\n';
1890 std::cout <<
"Writing pidfile to " << qPrintable(filename) <<
'\n';
1891 file.write(QByteArray::number(QCoreApplication::applicationPid()) +
'\n');
1901 if (workerCore > 0) {
1902 app = qobject_cast<Application *>(app->metaObject()->newInstance());
1904 qFatal(
"*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
1905 "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
1909 auto engine =
new ServerEngine(app, workerCore, opt, q);
1910 const Qt::ConnectionType forkConnection =
1911 userEventLoop ? Qt::DirectConnection : Qt::QueuedConnection;
1912 connect(
this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1913 connect(
this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, forkConnection);
1915 &ServerEngine::shutdownCompleted,
1917 &ServerPrivate::engineShutdown,
1918 Qt::QueuedConnection);
1919 connect(engine, &ServerEngine::started,
this, &ServerPrivate::workerStarted, forkConnection);
1922 engine->setServers(servers);
1923 if (!engine->
init()) {
1924 std::cerr <<
"Application failed to init(), cheaping core: " << workerCore <<
'\n';
1929 engines.push_back(engine);
1932 if (workerCore > 0) {
1935 app->setParent(engine);
1937 auto thread =
new QThread(
this);
1938 engine->moveToThread(thread);
1940 engine->setParent(
this);
1946void ServerPrivate::loadConfig()
1948 if (loadingConfig) {
1952 loadingConfig =
true;
1954 if (configToLoad.isEmpty()) {
1955 loadingConfig =
false;
1959 auto fileToLoad = configToLoad.dequeue();
1961 if (fileToLoad.first.isEmpty()) {
1962 qCWarning(CUTELYST_SERVER) <<
"Can not load config from empty config file name";
1963 loadingConfig =
false;
1967 if (configLoaded.contains(fileToLoad.first)) {
1968 loadingConfig =
false;
1972 configLoaded.append(fileToLoad.first);
1974 QVariantMap loadedConfig;
1975 switch (fileToLoad.second) {
1976 case ConfigFormat::Ini:
1977 qCInfo(CUTELYST_SERVER) <<
"Loading INI configuratin:" << fileToLoad.first;
1980 case ConfigFormat::Json:
1981 qCInfo(CUTELYST_SERVER) <<
"Loading JSON configuration:" << fileToLoad.first;
1986 for (
const auto &[key, value] : std::as_const(loadedConfig).asKeyValueRange()) {
1987 if (config.contains(key)) {
1988 QVariantMap currentMap = config.value(key).toMap();
1989 const QVariantMap loadedMap = value.toMap();
1990 for (
const auto &[mapKey, mapValue] : loadedMap.asKeyValueRange()) {
1991 currentMap.insert(mapKey, mapValue);
1993 config.insert(key, currentMap);
1995 config.insert(key, value);
1999 QVariantMap sessionConfig = loadedConfig.value(u
"server"_s).toMap();
2001 applyConfig(sessionConfig);
2003 opt.insert(sessionConfig);
2005 loadingConfig =
false;
2007 if (!configToLoad.empty()) {
2012void ServerPrivate::applyConfig(
const QVariantMap &config)
2016 for (
const auto &[key, value] : config.asKeyValueRange()) {
2017 QString normKey = key;
2018 normKey.replace(u
'-', u
'_');
2020 int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
2025 const QMetaProperty prop = q->metaObject()->property(ix);
2026 if (prop.userType() == value.userType()) {
2027 if (prop.userType() == QMetaType::QStringList) {
2028 const QStringList currentValues = prop.read(q).toStringList();
2029 prop.write(q, currentValues + value.toStringList());
2031 prop.write(q, value);
2033 }
else if (prop.userType() == QMetaType::QStringList) {
2034 const QStringList currentValues = prop.read(q).toStringList();
2035 prop.write(q, currentValues + QStringList{value.toString()});
2037 prop.write(q, value);
2042Protocol *ServerPrivate::getHttpProto()
2064Protocol *ServerPrivate::getFastCgiProto()
2073#include "moc_server.cpp"
2074#include "moc_server_p.cpp"
The Cutelyst application.
static QVariantMap loadJsonConfig(const QString &filename)
void setConfig(const QVariantMap &config)
static QVariantMap loadIniConfig(const QString &filename)
virtual bool init() override
void errorOccured(const QString &error)
bool start(Cutelyst::Application *app=nullptr)
int exec(Cutelyst::Application *app=nullptr)
void parseCommandLine(const QStringList &args)
Server(QObject *parent=nullptr)
QVariantMap config() const noexcept
The Cutelyst namespace holds all public Cutelyst API.