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]() {
70 d->protoHTTP =
nullptr;
73 d->protoHTTP2 =
nullptr;
76 d->protoFCGI =
nullptr;
81 qDeleteAll(d->servers);
92 std::cout <<
"Cutelyst-Server terminated" <<
'\n';
99 QCommandLineParser parser;
100 parser.setApplicationDescription(
103 qtTrId(
"cutelystd-cli-desc"));
104 parser.addHelpOption();
105 parser.addVersionOption();
107 QCommandLineOption iniOpt(QStringLiteral(
"ini"),
112 qtTrId(
"cutelystd-opt-ini-desc"),
115 qtTrId(
"cutelystd-opt-value-file"));
116 parser.addOption(iniOpt);
118 QCommandLineOption jsonOpt({QStringLiteral(
"j"), QStringLiteral(
"json")},
123 qtTrId(
"cutelystd-opt-json-desc"),
124 qtTrId(
"cutelystd-opt-value-file"));
125 parser.addOption(jsonOpt);
127 QCommandLineOption
chdir(
128 QStringLiteral(
"chdir"),
131 qtTrId(
"cutelystd-opt-chdir-desc"),
134 qtTrId(
"cutelystd-opt-value-directory"));
135 parser.addOption(
chdir);
137 QCommandLineOption
chdir2(
138 QStringLiteral(
"chdir2"),
141 qtTrId(
"cutelystd-opt-chdir2-desc"),
142 qtTrId(
"cutelystd-opt-value-directory"));
145 QCommandLineOption lazyOption(
146 QStringLiteral(
"lazy"),
149 qtTrId(
"cutelystd-opt-lazy-desc"));
150 parser.addOption(lazyOption);
152 QCommandLineOption
application({QStringLiteral(
"application"), QStringLiteral(
"a")},
155 qtTrId(
"cutelystd-opt-application-desc"),
156 qtTrId(
"cutelystd-opt-value-file"));
159 QCommandLineOption
threads({QStringLiteral(
"threads"), QStringLiteral(
"t")},
163 qtTrId(
"cutelystd-opt-threads-desc"),
166 qtTrId(
"cutelystd-opt-threads-value"));
170 QCommandLineOption
processes({QStringLiteral(
"processes"), QStringLiteral(
"p")},
174 qtTrId(
"cutelystd-opt-processes-desc"),
177 qtTrId(
"cutelystd-opt-processes-value"));
181 QCommandLineOption
master({QStringLiteral(
"master"), QStringLiteral(
"M")},
184 qtTrId(
"cutelystd-opt-master-desc"));
187 QCommandLineOption listenQueue({QStringLiteral(
"listen"), QStringLiteral(
"l")},
190 qtTrId(
"cutelystd-opt-listen-desc"),
193 qtTrId(
"cutelystd-opt-value-size"));
194 parser.addOption(listenQueue);
196 QCommandLineOption bufferSize({QStringLiteral(
"buffer-size"), QStringLiteral(
"b")},
199 qtTrId(
"cutelystd-opt-buffer-size-desc"),
202 qtTrId(
"cutelystd-opt-value-bytes"));
203 parser.addOption(bufferSize);
205 QCommandLineOption postBuffering(QStringLiteral(
"post-buffering"),
210 qtTrId(
"cutelystd-opt-post-buffering-desc"),
211 qtTrId(
"cutelystd-opt-value-bytes"));
212 parser.addOption(postBuffering);
214 QCommandLineOption postBufferingBufsize(
215 QStringLiteral(
"post-buffering-bufsize"),
218 qtTrId(
"cutelystd-opt-post-buffering-bufsize-desc"),
219 qtTrId(
"cutelystd-opt-value-bytes"));
220 parser.addOption(postBufferingBufsize);
222 QCommandLineOption httpSocketOpt({QStringLiteral(
"http-socket"), QStringLiteral(
"h1")},
225 qtTrId(
"cutelystd-opt-http-socket-desc"),
228 qtTrId(
"cutelystd-opt-value-address"));
229 parser.addOption(httpSocketOpt);
231 QCommandLineOption http2SocketOpt(
232 {QStringLiteral(
"http2-socket"), QStringLiteral(
"h2")},
235 qtTrId(
"cutelystd-opt-http2-socket-desc"),
236 qtTrId(
"cutelystd-opt-value-address"));
237 parser.addOption(http2SocketOpt);
239 QCommandLineOption http2HeaderTableSizeOpt(QStringLiteral(
"http2-header-table-size"),
242 qtTrId(
"cutelystd-opt-http2-header-table-size-desc"),
243 qtTrId(
"cutelystd-opt-value-size"));
244 parser.addOption(http2HeaderTableSizeOpt);
246 QCommandLineOption upgradeH2cOpt(QStringLiteral(
"upgrade-h2c"),
249 qtTrId(
"cutelystd-opt-upgrade-h2c-desc"));
250 parser.addOption(upgradeH2cOpt);
252 QCommandLineOption httpsH2Opt(QStringLiteral(
"https-h2"),
255 qtTrId(
"cutelystd-opt-https-h2-desc"));
256 parser.addOption(httpsH2Opt);
258 QCommandLineOption httpsSocketOpt({QStringLiteral(
"https-socket"), QStringLiteral(
"hs1")},
261 qtTrId(
"cutelystd-opt-https-socket-desc"),
263 qtTrId(
"cutelystd-opt-value-httpsaddress"));
264 parser.addOption(httpsSocketOpt);
266 QCommandLineOption fastcgiSocketOpt(
267 QStringLiteral(
"fastcgi-socket"),
270 qtTrId(
"cutelystd-opt-fastcgi-socket-desc"),
271 qtTrId(
"cutelystd-opt-value-address"));
272 parser.addOption(fastcgiSocketOpt);
274 QCommandLineOption socketAccess(
275 QStringLiteral(
"socket-access"),
278 qtTrId(
"cutelystd-opt-socket-access-desc"),
281 qtTrId(
"cutelystd-opt-socket-access-value"));
282 parser.addOption(socketAccess);
284 QCommandLineOption socketTimeout({QStringLiteral(
"socket-timeout"), QStringLiteral(
"z")},
287 qtTrId(
"cutelystd-opt-socket-timeout-desc"),
290 qtTrId(
"cutelystd-opt-socket-timeout-value"));
291 parser.addOption(socketTimeout);
293 QCommandLineOption staticMapOpt(QStringLiteral(
"static-map"),
299 qtTrId(
"cutelystd-opt-static-map-desc"),
302 qtTrId(
"cutelystd-opt-value-static-map"));
303 parser.addOption(staticMapOpt);
305 QCommandLineOption staticMap2Opt(QStringLiteral(
"static-map2"),
309 qtTrId(
"cutelystd-opt-static-map2-desc"),
312 qtTrId(
"cutelystd-opt-value-static-map"));
313 parser.addOption(staticMap2Opt);
315 QCommandLineOption autoReload({QStringLiteral(
"auto-restart"), QStringLiteral(
"r")},
319 qtTrId(
"cutelystd-opt-auto-restart-desc"));
320 parser.addOption(autoReload);
322 QCommandLineOption touchReloadOpt(
323 QStringLiteral(
"touch-reload"),
327 qtTrId(
"cutelystd-opt-touch-reload-desc"),
328 qtTrId(
"cutelystd-opt-value-file"));
329 parser.addOption(touchReloadOpt);
331 QCommandLineOption tcpNoDelay(QStringLiteral(
"tcp-nodelay"),
334 qtTrId(
"cutelystd-opt-tcp-nodelay-desc"));
335 parser.addOption(tcpNoDelay);
337 QCommandLineOption soKeepAlive(QStringLiteral(
"so-keepalive"),
340 qtTrId(
"cutelystd-opt-so-keepalive-desc"));
341 parser.addOption(soKeepAlive);
343 QCommandLineOption socketSndbuf(QStringLiteral(
"socket-sndbuf"),
347 qtTrId(
"cutelystd-opt-socket-sndbuf-desc"),
348 qtTrId(
"cutelystd-opt-value-bytes"));
349 parser.addOption(socketSndbuf);
351 QCommandLineOption socketRcvbuf(QStringLiteral(
"socket-rcvbuf"),
355 qtTrId(
"cutelystd-opt-socket-rcvbuf-desc"),
356 qtTrId(
"cutelystd-opt-value-bytes"));
357 parser.addOption(socketRcvbuf);
359 QCommandLineOption wsMaxSize(QStringLiteral(
"websocket-max-size"),
363 qtTrId(
"cutelystd-opt-websocket-max-size-desc"),
366 qtTrId(
"cutelystd-opt-websocket-max-size-value"));
367 parser.addOption(wsMaxSize);
369 QCommandLineOption pidfileOpt(QStringLiteral(
"pidfile"),
372 qtTrId(
"cutelystd-opt-pidfile-desc"),
375 qtTrId(
"cutelystd-opt-value-pidfile"));
376 parser.addOption(pidfileOpt);
378 QCommandLineOption pidfile2Opt(QStringLiteral(
"pidfile2"),
381 qtTrId(
"cutelystd-opt-pidfile2-desc"),
382 qtTrId(
"cutelystd-opt-value-pidfile"));
383 parser.addOption(pidfile2Opt);
386 QCommandLineOption stopOption(QStringLiteral(
"stop"),
389 qtTrId(
"cutelystd-opt-stop-desc"),
390 qtTrId(
"cutelystd-opt-value-pidfile"));
391 parser.addOption(stopOption);
393 QCommandLineOption uidOption(QStringLiteral(
"uid"),
396 qtTrId(
"cutelystd-opt-uid-desc"),
399 qtTrId(
"cutelystd-opt-uid-value"));
400 parser.addOption(uidOption);
402 QCommandLineOption gidOption(QStringLiteral(
"gid"),
405 qtTrId(
"cutelystd-opt-gid-desc"),
408 qtTrId(
"cutelystd-opt-gid-value"));
409 parser.addOption(gidOption);
411 QCommandLineOption noInitgroupsOption(QStringLiteral(
"no-initgroups"),
414 qtTrId(
"cutelystd-opt-no-init-groups-desc"));
415 parser.addOption(noInitgroupsOption);
417 QCommandLineOption chownSocketOption(QStringLiteral(
"chown-socket"),
420 qtTrId(
"cutelystd-opt-chown-socket-desc"),
423 qtTrId(
"cutelystd-opt-chown-socket-value"));
424 parser.addOption(chownSocketOption);
426 QCommandLineOption umaskOption(QStringLiteral(
"umask"),
429 qtTrId(
"cutelystd-opt-umask-desc"),
432 qtTrId(
"cutelystd-opt-umask-value"));
433 parser.addOption(umaskOption);
435 QCommandLineOption cpuAffinityOption(
436 QStringLiteral(
"cpu-affinity"),
439 qtTrId(
"cutelystd-opt-cpu-affinity-desc"),
442 qtTrId(
"cutelystd-opt-cpu-affinity-value"));
443 parser.addOption(cpuAffinityOption);
447 QCommandLineOption reusePortOption(QStringLiteral(
"reuse-port"),
450 qtTrId(
"cutelystd-opt-reuse-port-desc"));
451 parser.addOption(reusePortOption);
454 QCommandLineOption threadBalancerOpt(
455 QStringLiteral(
"experimental-thread-balancer"),
458 qtTrId(
"cutelystd-opt-experimental-thread-balancer-desc"));
459 parser.addOption(threadBalancerOpt);
461 QCommandLineOption frontendProxy(QStringLiteral(
"using-frontend-proxy"),
464 qtTrId(
"cutelystd-opt-using-frontend-proxy-desc"));
465 parser.addOption(frontendProxy);
468 parser.process(arguments);
470 setIni(parser.values(iniOpt));
472 setJson(parser.values(jsonOpt));
474 if (parser.isSet(
chdir)) {
475 setChdir(parser.value(
chdir));
478 if (parser.isSet(
chdir2)) {
479 setChdir2(parser.value(
chdir2));
483 setThreads(parser.value(
threads));
486 if (parser.isSet(socketAccess)) {
487 setSocketAccess(parser.value(socketAccess));
490 if (parser.isSet(socketTimeout)) {
492 auto size = parser.value(socketTimeout).toInt(&ok);
493 setSocketTimeout(size);
494 if (!ok || size < 0) {
499 if (parser.isSet(pidfileOpt)) {
500 setPidfile(parser.value(pidfileOpt));
503 if (parser.isSet(pidfile2Opt)) {
504 setPidfile2(parser.value(pidfile2Opt));
508 if (parser.isSet(stopOption)) {
509 UnixFork::stopWSGI(parser.value(stopOption));
516 if (parser.isSet(uidOption)) {
517 setUid(parser.value(uidOption));
520 if (parser.isSet(gidOption)) {
521 setGid(parser.value(gidOption));
524 if (parser.isSet(noInitgroupsOption)) {
525 setNoInitgroups(
true);
528 if (parser.isSet(chownSocketOption)) {
529 setChownSocket(parser.value(chownSocketOption));
532 if (parser.isSet(umaskOption)) {
533 setUmask(parser.value(umaskOption));
536 if (parser.isSet(cpuAffinityOption)) {
538 auto value = parser.value(cpuAffinityOption).toInt(&ok);
539 setCpuAffinity(value);
540 if (!ok || value < 0) {
547 if (parser.isSet(reusePortOption)) {
552 if (parser.isSet(lazyOption)) {
556 if (parser.isSet(listenQueue)) {
558 auto size = parser.value(listenQueue).toInt(&ok);
559 setListenQueue(size);
560 if (!ok || size < 1) {
565 if (parser.isSet(bufferSize)) {
567 auto size = parser.value(bufferSize).toInt(&ok);
569 if (!ok || size < 1) {
574 if (parser.isSet(postBuffering)) {
576 auto size = parser.value(postBuffering).toLongLong(&ok);
577 setPostBuffering(size);
578 if (!ok || size < 1) {
583 if (parser.isSet(postBufferingBufsize)) {
585 auto size = parser.value(postBufferingBufsize).toLongLong(&ok);
586 setPostBufferingBufsize(size);
587 if (!ok || size < 1) {
596 if (parser.isSet(
master)) {
600 if (parser.isSet(autoReload)) {
604 if (parser.isSet(tcpNoDelay)) {
608 if (parser.isSet(soKeepAlive)) {
609 setSoKeepalive(
true);
612 if (parser.isSet(upgradeH2cOpt)) {
616 if (parser.isSet(httpsH2Opt)) {
620 if (parser.isSet(socketSndbuf)) {
622 auto size = parser.value(socketSndbuf).toInt(&ok);
623 setSocketSndbuf(size);
624 if (!ok || size < 1) {
629 if (parser.isSet(socketRcvbuf)) {
631 auto size = parser.value(socketRcvbuf).toInt(&ok);
632 setSocketRcvbuf(size);
633 if (!ok || size < 1) {
638 if (parser.isSet(wsMaxSize)) {
640 auto size = parser.value(wsMaxSize).toInt(&ok);
641 setWebsocketMaxSize(size);
642 if (!ok || size < 1) {
647 if (parser.isSet(http2HeaderTableSizeOpt)) {
649 auto size = parser.value(http2HeaderTableSizeOpt).toUInt(&ok);
650 setHttp2HeaderTableSize(size);
651 if (!ok || size < 1) {
656 if (parser.isSet(frontendProxy)) {
657 setUsingFrontendProxy(
true);
660 setHttpSocket(httpSocket() + parser.values(httpSocketOpt));
662 setHttp2Socket(http2Socket() + parser.values(http2SocketOpt));
664 setHttpsSocket(httpsSocket() + parser.values(httpsSocketOpt));
666 setFastcgiSocket(fastcgiSocket() + parser.values(fastcgiSocketOpt));
668 setStaticMap(staticMap() + parser.values(staticMapOpt));
670 setStaticMap2(staticMap2() + parser.values(staticMap2Opt));
672 setTouchReload(touchReload() + parser.values(touchReloadOpt));
674 d->threadBalancer = parser.isSet(threadBalancerOpt);
680 std::cout <<
"Cutelyst-Server starting" <<
'\n';
682 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER") && !d->master) {
684 <<
"*** WARNING: you are running Cutelyst-Server without its master process manager ***"
689 if (d->processes == -1 && d->threads == -1) {
690 d->processes = UnixFork::idealProcessCount();
691 d->threads = UnixFork::idealThreadCount() / d->processes;
692 }
else if (d->processes == -1) {
693 d->processes = UnixFork::idealThreadCount();
694 }
else if (d->threads == -1) {
695 d->threads = UnixFork::idealThreadCount();
698 if (d->processes == 0 && d->master) {
701 d->genericFork =
new UnixFork(d->processes, qMax(d->threads, 1), !d->userEventLoop,
this);
703 if (d->processes == -1) {
706 if (d->threads == -1) {
707 d->threads = QThread::idealThreadCount();
713 d->genericFork, &AbstractFork::forked, d, &ServerPrivate::postFork, Qt::DirectConnection);
715 d->genericFork, &AbstractFork::shutdown, d, &ServerPrivate::shutdown, Qt::DirectConnection);
717 if (d->master && d->lazy) {
718 if (d->autoReload && !d->application.isEmpty()) {
719 d->touchReload.append(d->application);
721 d->genericFork->setTouchReload(d->touchReload);
725 if (d->master && !d->genericFork->continueMaster(&ret)) {
730 if (systemdNotify::is_systemd_notify_available()) {
732 sd->setWatchdog(
true, systemdNotify::sd_watchdog_enabled(
true));
734 sd->sendStatus(qApp->applicationName().toLatin1() +
" is ready");
737 connect(d, &ServerPrivate::postForked, sd, [sd] { sd->setWatchdog(
false); });
738 qInfo(CUTELYST_SERVER) <<
"systemd notify detected";
746 if (!d->listenTcpSockets()) {
748 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-opened"));
753 if (!d->writePidFile(d->pidfile)) {
755 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-write-pidfile").arg(d->pidfile));
759 bool isListeningLocalSockets =
false;
760 if (!d->chownSocket.isEmpty()) {
761 if (!d->listenLocalSockets()) {
763 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-open-local-socket"));
766 isListeningLocalSockets =
true;
769 if (!d->umask.isEmpty() && !UnixFork::setUmask(d->umask.toLatin1())) {
773 if (!UnixFork::setGidUid(d->gid, d->uid, d->noInitgroups)) {
779 if (!isListeningLocalSockets) {
781 d->listenLocalSockets();
787 if (!d->listenTcpSockets()) {
788 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-opened"));
793 if (d->servers.empty()) {
794 std::cout <<
"Please specify a socket to listen to" <<
'\n';
796 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-specified"));
800 d->writePidFile(d->pidfile2);
802 if (!d->chdir.isEmpty()) {
803 std::cout <<
"Changing directory to: " << d->chdir.toLatin1().constData() <<
'\n';
804 if (!QDir::setCurrent(d->chdir)) {
805 Q_EMIT
errorOccured(QString::fromLatin1(
"Failed to chdir to: '%s'")
806 .arg(QString::fromLatin1(d->chdir.toLatin1().constData())));
814 if (!d->setupApplication()) {
816 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
821 if (d->userEventLoop) {
826 ret = d->genericFork->exec(d->lazy, d->master);
837 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-server-not-fully-stopped"));
844 d->userEventLoop =
true;
849 qputenv(
"CUTELYST_SERVER_IGNORE_MASTER", QByteArrayLiteral(
"1"));
851 if (
exec(app) == 0) {
861 if (d->userEventLoop) {
862 Q_EMIT d->shutdown();
866ServerPrivate::~ServerPrivate()
873bool ServerPrivate::listenTcpSockets()
875 if (httpSockets.isEmpty() && httpsSockets.isEmpty() && http2Sockets.isEmpty() &&
876 fastcgiSockets.isEmpty()) {
882 for (
const auto &socket : std::as_const(httpSockets)) {
883 if (!listenTcp(socket, getHttpProto(),
false)) {
889 for (
const auto &socket : std::as_const(httpsSockets)) {
890 if (!listenTcp(socket, getHttpProto(),
true)) {
896 for (
const auto &socket : std::as_const(http2Sockets)) {
897 if (!listenTcp(socket, getHttp2Proto(),
false)) {
903 bool allOk = std::ranges::all_of(fastcgiSockets, [
this](
const QString &socket) {
904 return listenTcp(socket, getFastCgiProto(),
false);
910bool ServerPrivate::listenTcp(
const QString &line,
Protocol *protocol,
bool secure)
915 if (!line.startsWith(u
'/')) {
917 server->setBalancer(threadBalancer);
918 ret = server->listen(line, protocol, secure);
920 if (ret && server->socketDescriptor()) {
921 auto qEnum = Protocol::staticMetaObject.enumerator(0);
922 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
923 << QByteArray::number(
static_cast<int>(servers.size())).constData()
924 <<
" bound to TCP address " << server->serverName().constData() <<
" fd "
925 << QByteArray::number(server->socketDescriptor()).constData() <<
'\n';
926 servers.emplace_back(server);
933bool ServerPrivate::listenLocalSockets()
935 QStringList http = httpSockets;
936 QStringList http2 = http2Sockets;
937 QStringList fastcgi = fastcgiSockets;
942 std::vector<int> fds = systemdNotify::listenFds();
945 if (server->listen(fd)) {
946 const QString name = server->serverName();
947 const QString fullName = server->fullServerName();
950 if (http.removeOne(fullName) || http.removeOne(name)) {
951 protocol = getHttpProto();
952 }
else if (http2.removeOne(fullName) || http2.removeOne(name)) {
953 protocol = getHttp2Proto();
954 }
else if (fastcgi.removeOne(fullName) || fastcgi.removeOne(name)) {
955 protocol = getFastCgiProto();
957 std::cerr <<
"systemd activated socket does not match any configured socket"
961 server->setProtocol(protocol);
962 server->pauseAccepting();
964 auto qEnum = Protocol::staticMetaObject.enumerator(0);
965 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
966 << QByteArray::number(
static_cast<int>(servers.size())).constData()
967 <<
" bound to LOCAL address " << qPrintable(fullName) <<
" fd "
968 << QByteArray::number(server->socket()).constData() <<
'\n';
969 servers.push_back(server);
971 std::cerr <<
"Failed to listen on activated LOCAL FD: "
972 << QByteArray::number(fd).constData() <<
" : "
973 << qPrintable(server->errorString()) <<
'\n';
980 const auto httpConst = http;
981 for (
const auto &socket : httpConst) {
982 ret |= listenLocal(socket, getHttpProto());
985 const auto http2Const = http2;
986 for (
const auto &socket : http2Const) {
987 ret |= listenLocal(socket, getHttp2Proto());
990 const auto fastcgiConst = fastcgi;
991 for (
const auto &socket : fastcgiConst) {
992 ret |= listenLocal(socket, getFastCgiProto());
998bool ServerPrivate::listenLocal(
const QString &line,
Protocol *protocol)
1003 if (line.startsWith(QLatin1Char(
'/'))) {
1005 server->setProtocol(protocol);
1006 if (!socketAccess.isEmpty()) {
1007 QLocalServer::SocketOptions options;
1008 if (socketAccess.contains(u
'u')) {
1009 options |= QLocalServer::UserAccessOption;
1012 if (socketAccess.contains(u
'g')) {
1013 options |= QLocalServer::GroupAccessOption;
1016 if (socketAccess.contains(u
'o')) {
1017 options |= QLocalServer::OtherAccessOption;
1019 server->setSocketOptions(options);
1022 QLocalServer::removeServer(line);
1023 server->setListenBacklogSize(listenQueue);
1024 ret = server->listen(line);
1025 server->pauseAccepting();
1027 if (!ret || !server->socket()) {
1028 std::cerr <<
"Failed to listen on LOCAL: " << qPrintable(line) <<
" : "
1029 << qPrintable(server->errorString()) <<
'\n';
1034 if (!chownSocket.isEmpty()) {
1035 UnixFork::chownSocket(line, chownSocket);
1038 auto qEnum = Protocol::staticMetaObject.enumerator(0);
1039 std::cout << qEnum.valueToKey(
static_cast<int>(protocol->type())) <<
" socket "
1040 << QByteArray::number(
static_cast<int>(servers.size())).constData()
1041 <<
" bound to LOCAL address " << qPrintable(line) <<
" fd "
1042 << QByteArray::number(server->socket()).constData() <<
'\n';
1043 servers.push_back(server);
1049void Server::setApplication(
const QString &application)
1054 if (loader.fileName().isEmpty()) {
1059 d->application = loader.fileName();
1067 return d->application;
1070void Server::setThreads(
const QString &threads)
1073 if (
threads.compare(u
"auto", Qt::CaseInsensitive) == 0) {
1076 d->threads = qMax(1,
threads.toInt());
1084 if (d->threads == -1) {
1085 return QStringLiteral(
"auto");
1087 return QString::number(d->threads);
1090void Server::setProcesses(
const QString &process)
1094 if (process.compare(QLatin1String(
"auto"), Qt::CaseInsensitive) == 0) {
1097 d->processes = process.toInt();
1106 if (d->processes == -1) {
1107 return QStringLiteral(
"auto");
1109 return QString::number(d->processes);
1112void Server::setChdir(
const QString &chdir)
1125void Server::setHttpSocket(
const QStringList &httpSocket)
1128 d->httpSockets = httpSocket;
1132QStringList Server::httpSocket()
const
1135 return d->httpSockets;
1138void Server::setHttp2Socket(
const QStringList &http2Socket)
1141 d->http2Sockets = http2Socket;
1145QStringList Server::http2Socket()
const
1148 return d->http2Sockets;
1151void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1154 d->http2HeaderTableSize = headerTableSize;
1158quint32 Server::http2HeaderTableSize()
const
1161 return d->http2HeaderTableSize;
1164void Server::setUpgradeH2c(
bool enable)
1167 d->upgradeH2c = enable;
1171bool Server::upgradeH2c()
const
1174 return d->upgradeH2c;
1177void Server::setHttpsH2(
bool enable)
1180 d->httpsH2 = enable;
1184bool Server::httpsH2()
const
1190void Server::setHttpsSocket(
const QStringList &httpsSocket)
1193 d->httpsSockets = httpsSocket;
1197QStringList Server::httpsSocket()
const
1200 return d->httpsSockets;
1203void Server::setFastcgiSocket(
const QStringList &fastcgiSocket)
1206 d->fastcgiSockets = fastcgiSocket;
1210QStringList Server::fastcgiSocket()
const
1213 return d->fastcgiSockets;
1216void Server::setSocketAccess(
const QString &socketAccess)
1219 d->socketAccess = socketAccess;
1223QString Server::socketAccess()
const
1226 return d->socketAccess;
1229void Server::setSocketTimeout(
int timeout)
1232 d->socketTimeout = timeout;
1236int Server::socketTimeout()
const
1239 return d->socketTimeout;
1242void Server::setChdir2(
const QString &chdir2)
1255void Server::setIni(
const QStringList &files)
1258 d->ini.append(files);
1259 d->ini.removeDuplicates();
1262 for (
const QString &file : files) {
1263 if (!d->configLoaded.contains(file)) {
1264 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Ini);
1265 if (!d->configToLoad.contains(fileToLoad)) {
1266 qCDebug(CUTELYST_SERVER) <<
"Enqueue INI config file:" << file;
1267 d->configToLoad.enqueue(fileToLoad);
1281void Server::setJson(
const QStringList &files)
1284 d->json.append(files);
1285 d->json.removeDuplicates();
1288 for (
const QString &file : files) {
1289 if (!d->configLoaded.contains(file)) {
1290 auto fileToLoad = std::make_pair(file, ServerPrivate::ConfigFormat::Json);
1291 if (!d->configToLoad.contains(fileToLoad)) {
1292 qCDebug(CUTELYST_SERVER) <<
"Enqueue JSON config file:" << file;
1293 d->configToLoad.enqueue(fileToLoad);
1307void Server::setStaticMap(
const QStringList &staticMap)
1310 d->staticMaps = staticMap;
1314QStringList Server::staticMap()
const
1317 return d->staticMaps;
1320void Server::setStaticMap2(
const QStringList &staticMap)
1323 d->staticMaps2 = staticMap;
1327QStringList Server::staticMap2()
const
1330 return d->staticMaps2;
1333void Server::setMaster(
bool enable)
1336 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER")) {
1348void Server::setAutoReload(
bool enable)
1352 d->autoReload =
true;
1357bool Server::autoReload()
const
1360 return d->autoReload;
1363void Server::setTouchReload(
const QStringList &files)
1366 d->touchReload = files;
1370QStringList Server::touchReload()
const
1373 return d->touchReload;
1376void Server::setListenQueue(
int size)
1379 d->listenQueue = size;
1383int Server::listenQueue()
const
1386 return d->listenQueue;
1389void Server::setBufferSize(
int size)
1393 qCWarning(CUTELYST_SERVER) <<
"Buffer size must be at least 4096 bytes, ignoring";
1396 d->bufferSize = size;
1400int Server::bufferSize()
const
1403 return d->bufferSize;
1406void Server::setPostBuffering(qint64 size)
1409 d->postBuffering = size;
1413qint64 Server::postBuffering()
const
1416 return d->postBuffering;
1419void Server::setPostBufferingBufsize(qint64 size)
1423 qCWarning(CUTELYST_SERVER) <<
"Post buffer size must be at least 4096 bytes, ignoring";
1426 d->postBufferingBufsize = size;
1430qint64 Server::postBufferingBufsize()
const
1433 return d->postBufferingBufsize;
1436void Server::setTcpNodelay(
bool enable)
1439 d->tcpNodelay = enable;
1443bool Server::tcpNodelay()
const
1446 return d->tcpNodelay;
1449void Server::setSoKeepalive(
bool enable)
1452 d->soKeepalive = enable;
1456bool Server::soKeepalive()
const
1459 return d->soKeepalive;
1462void Server::setSocketSndbuf(
int value)
1465 d->socketSendBuf = value;
1469int Server::socketSndbuf()
const
1472 return d->socketSendBuf;
1475void Server::setSocketRcvbuf(
int value)
1478 d->socketReceiveBuf = value;
1482int Server::socketRcvbuf()
const
1485 return d->socketReceiveBuf;
1488void Server::setWebsocketMaxSize(
int value)
1491 d->websocketMaxSize = value * 1024;
1495int Server::websocketMaxSize()
const
1498 return d->websocketMaxSize / 1024;
1501void Server::setPidfile(
const QString &file)
1514void Server::setPidfile2(
const QString &file)
1527void Server::setUid(
const QString &uid)
1542void Server::setGid(
const QString &gid)
1557void Server::setNoInitgroups(
bool enable)
1561 d->noInitgroups = enable;
1566bool Server::noInitgroups()
const
1569 return d->noInitgroups;
1572void Server::setChownSocket(
const QString &chownSocket)
1576 d->chownSocket = chownSocket;
1581QString Server::chownSocket()
const
1584 return d->chownSocket;
1587void Server::setUmask(
const QString &value)
1602void Server::setCpuAffinity(
int value)
1606 d->cpuAffinity = value;
1611int Server::cpuAffinity()
const
1614 return d->cpuAffinity;
1617void Server::setReusePort(
bool enable)
1621 d->reusePort = enable;
1628bool Server::reusePort()
const
1631 return d->reusePort;
1634void Server::setLazy(
bool enable)
1647void Server::setUsingFrontendProxy(
bool enable)
1650 d->usingFrontendProxy = enable;
1654bool Server::usingFrontendProxy()
const
1657 return d->usingFrontendProxy;
1666bool ServerPrivate::setupApplication()
1673 std::cout <<
"Loading application: " << application.toLatin1().constData() <<
'\n';
1674 QPluginLoader loader(application);
1675 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::PreventUnloadHint);
1676 if (!loader.load()) {
1677 qCCritical(CUTELYST_SERVER) <<
"Could not load application:" << loader.errorString();
1681 QObject *instance = loader.instance();
1683 qCCritical(CUTELYST_SERVER) <<
"Could not get a QObject instance: %s\n"
1684 << loader.errorString();
1688 localApp = qobject_cast<Cutelyst::Application *>(instance);
1690 qCCritical(CUTELYST_SERVER)
1691 <<
"Could not cast Cutelyst::Application from instance: %s\n"
1692 << loader.errorString();
1700 qCDebug(CUTELYST_SERVER) <<
"Loaded application: " << QCoreApplication::applicationName();
1703 if (!chdir2.isEmpty()) {
1704 std::cout <<
"Changing directory2 to: " << chdir2.toLatin1().constData() <<
'\n';
1705 if (!QDir::setCurrent(chdir2)) {
1706 Q_EMIT q->errorOccured(QString::fromLatin1(
"Failed to chdir2 to: '%s'")
1707 .arg(QString::fromLatin1(chdir2.toLatin1().constData())));
1713 engine = createEngine(localApp, 0);
1714 for (
int i = 1; i < threads; ++i) {
1715 if (createEngine(localApp, i)) {
1716 ++workersNotRunning;
1720 engine = createEngine(localApp, 0);
1721 workersNotRunning = 1;
1725 std::cerr <<
"Application failed to init, cheaping..." <<
'\n';
1732void ServerPrivate::engineShutdown(
ServerEngine *engine)
1734 const auto engineThread = engine->thread();
1735 if (QThread::currentThread() != engineThread) {
1736 connect(engineThread, &QThread::finished,
this, [
this, engine] {
1737 engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1738 checkEngineShutdown();
1740 engineThread->quit();
1742 engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1745 checkEngineShutdown();
1748void ServerPrivate::checkEngineShutdown()
1750 if (engines.empty()) {
1751 if (userEventLoop) {
1753 Q_EMIT q->stopped();
1755 QTimer::singleShot(std::chrono::seconds{0},
this, [] { qApp->exit(15); });
1760void ServerPrivate::workerStarted()
1765 if (--workersNotRunning == 0) {
1770bool ServerPrivate::postFork(
int workerId)
1775 if (!setupApplication()) {
1776 Q_EMIT q->errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
1781 if (engines.size() > 1) {
1782 qCDebug(CUTELYST_SERVER) <<
"Starting threads";
1786 QThread *thread = engine->thread();
1787 if (thread != qApp->thread()) {
1789 if (!qEnvironmentVariableIsSet(
"CUTELYST_QT_EVENT_LOOP")) {
1791 thread->setEventDispatcher(
new EventDispatcherEPoll);
1799 Q_EMIT postForked(workerId);
1801 QTimer::singleShot(std::chrono::seconds{1},
this, [=]() {
1807 qApp->processEvents();
1813bool ServerPrivate::writePidFile(
const QString &filename)
1815 if (filename.isEmpty()) {
1819 QFile file(filename);
1820 if (!file.open(QFile::WriteOnly | QFile::Text)) {
1821 std::cerr <<
"Failed write pid file " << qPrintable(filename) <<
'\n';
1825 std::cout <<
"Writing pidfile to " << qPrintable(filename) <<
'\n';
1826 file.write(QByteArray::number(QCoreApplication::applicationPid()) +
'\n');
1836 if (workerCore > 0) {
1837 app = qobject_cast<Application *>(app->metaObject()->newInstance());
1839 qFatal(
"*** FATAL *** Could not create a NEW instance of your Cutelyst::Application, "
1840 "make sure your constructor has Q_INVOKABLE macro or disable threaded mode.");
1844 auto engine =
new ServerEngine(app, workerCore, opt, q);
1845 connect(
this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1847 this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, Qt::QueuedConnection);
1849 &ServerEngine::shutdownCompleted,
1851 &ServerPrivate::engineShutdown,
1852 Qt::QueuedConnection);
1854 engine, &ServerEngine::started,
this, &ServerPrivate::workerStarted, Qt::QueuedConnection);
1857 engine->setServers(servers);
1858 if (!engine->
init()) {
1859 std::cerr <<
"Application failed to init(), cheaping core: " << workerCore <<
'\n';
1864 engines.push_back(engine);
1867 if (workerCore > 0) {
1870 app->setParent(engine);
1872 auto thread =
new QThread(
this);
1873 engine->moveToThread(thread);
1875 engine->setParent(
this);
1881void ServerPrivate::loadConfig()
1883 if (loadingConfig) {
1887 loadingConfig =
true;
1889 if (configToLoad.isEmpty()) {
1890 loadingConfig =
false;
1894 auto fileToLoad = configToLoad.dequeue();
1896 if (fileToLoad.first.isEmpty()) {
1897 qCWarning(CUTELYST_SERVER) <<
"Can not load config from empty config file name";
1898 loadingConfig =
false;
1902 if (configLoaded.contains(fileToLoad.first)) {
1903 loadingConfig =
false;
1907 configLoaded.append(fileToLoad.first);
1909 QVariantMap loadedConfig;
1910 switch (fileToLoad.second) {
1911 case ConfigFormat::Ini:
1912 qCInfo(CUTELYST_SERVER) <<
"Loading INI configuratin:" << fileToLoad.first;
1915 case ConfigFormat::Json:
1916 qCInfo(CUTELYST_SERVER) <<
"Loading JSON configuration:" << fileToLoad.first;
1921 for (
const auto &[key, value] : std::as_const(loadedConfig).asKeyValueRange()) {
1922 if (config.contains(key)) {
1923 QVariantMap currentMap = config.value(key).toMap();
1924 const QVariantMap loadedMap = value.toMap();
1925 for (
const auto &[key, value] : loadedMap.asKeyValueRange()) {
1926 currentMap.insert(key, value);
1928 config.insert(key, currentMap);
1930 config.insert(key, value);
1934 QVariantMap sessionConfig = loadedConfig.value(u
"server"_s).toMap();
1936 applyConfig(sessionConfig);
1938 opt.insert(sessionConfig);
1940 loadingConfig =
false;
1942 if (!configToLoad.empty()) {
1947void ServerPrivate::applyConfig(
const QVariantMap &config)
1951 for (
const auto &[key, value] : config.asKeyValueRange()) {
1952 QString normKey = key;
1953 normKey.replace(u
'-', u
'_');
1955 int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
1960 const QMetaProperty prop = q->metaObject()->property(ix);
1961 if (prop.userType() == value.userType()) {
1962 if (prop.userType() == QMetaType::QStringList) {
1963 const QStringList currentValues = prop.read(q).toStringList();
1964 prop.write(q, currentValues + value.toStringList());
1966 prop.write(q, value);
1968 }
else if (prop.userType() == QMetaType::QStringList) {
1969 const QStringList currentValues = prop.read(q).toStringList();
1970 prop.write(q, currentValues + QStringList{value.toString()});
1972 prop.write(q, value);
1977Protocol *ServerPrivate::getHttpProto()
1999Protocol *ServerPrivate::getFastCgiProto()
2008#include "moc_server.cpp"
2009#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.