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)
46 , d_ptr(new ServerPrivate(this))
48 QCoreApplication::addLibraryPath(QDir().absolutePath());
50 if (!qEnvironmentVariableIsSet(
"QT_MESSAGE_PATTERN")) {
51 if (qEnvironmentVariableIsSet(
"JOURNAL_STREAM")) {
53 qSetMessagePattern(u
"%{category}[%{type}] %{message}"_qs);
55 qSetMessagePattern(u
"%{pid}:%{threadid} %{category}[%{type}] %{message}"_qs);
60 if (!qEnvironmentVariableIsSet(
"CUTELYST_QT_EVENT_LOOP")) {
61 qCInfo(CUTELYST_SERVER) <<
"Trying to install EPoll event loop";
62 QCoreApplication::setEventDispatcher(
new EventDispatcherEPoll);
66 auto cleanUp = [
this]() {
69 d->protoHTTP =
nullptr;
72 d->protoHTTP2 =
nullptr;
75 d->protoFCGI =
nullptr;
80 qDeleteAll(d->servers);
91 std::cout <<
"Cutelyst-Server terminated" << std::endl;
98 QCommandLineParser parser;
99 parser.setApplicationDescription(
102 qtTrId(
"cutelystd-cli-desc"));
103 parser.addHelpOption();
104 parser.addVersionOption();
106 QCommandLineOption iniOpt(QStringLiteral(
"ini"),
111 qtTrId(
"cutelystd-opt-ini-desc"),
114 qtTrId(
"cutelystd-opt-value-file"));
115 parser.addOption(iniOpt);
117 QCommandLineOption jsonOpt({QStringLiteral(
"j"), QStringLiteral(
"json")},
122 qtTrId(
"cutelystd-opt-json-desc"),
123 qtTrId(
"cutelystd-opt-value-file"));
124 parser.addOption(jsonOpt);
126 QCommandLineOption
chdir(
127 QStringLiteral(
"chdir"),
130 qtTrId(
"cutelystd-opt-chdir-desc"),
133 qtTrId(
"cutelystd-opt-value-directory"));
134 parser.addOption(
chdir);
136 QCommandLineOption
chdir2(
137 QStringLiteral(
"chdir2"),
140 qtTrId(
"cutelystd-opt-chdir2-desc"),
141 qtTrId(
"cutelystd-opt-value-directory"));
144 QCommandLineOption lazyOption(
145 QStringLiteral(
"lazy"),
148 qtTrId(
"cutelystd-opt-lazy-desc"));
149 parser.addOption(lazyOption);
151 QCommandLineOption
application({QStringLiteral(
"application"), QStringLiteral(
"a")},
154 qtTrId(
"cutelystd-opt-application-desc"),
155 qtTrId(
"cutelystd-opt-value-file"));
158 QCommandLineOption
threads({QStringLiteral(
"threads"), QStringLiteral(
"t")},
162 qtTrId(
"cutelystd-opt-threads-desc"),
165 qtTrId(
"cutelystd-opt-threads-value"));
169 QCommandLineOption
processes({QStringLiteral(
"processes"), QStringLiteral(
"p")},
173 qtTrId(
"cutelystd-opt-processes-desc"),
176 qtTrId(
"cutelystd-opt-processes-value"));
180 QCommandLineOption
master({QStringLiteral(
"master"), QStringLiteral(
"M")},
183 qtTrId(
"cutelystd-opt-master-desc"));
186 QCommandLineOption listenQueue({QStringLiteral(
"listen"), QStringLiteral(
"l")},
189 qtTrId(
"cutelystd-opt-listen-desc"),
192 qtTrId(
"cutelystd-opt-value-size"));
193 parser.addOption(listenQueue);
195 QCommandLineOption bufferSize({QStringLiteral(
"buffer-size"), QStringLiteral(
"b")},
198 qtTrId(
"cutelystd-opt-buffer-size-desc"),
201 qtTrId(
"cutelystd-opt-value-bytes"));
202 parser.addOption(bufferSize);
204 QCommandLineOption postBuffering(QStringLiteral(
"post-buffering"),
209 qtTrId(
"cutelystd-opt-post-buffering-desc"),
210 qtTrId(
"cutelystd-opt-value-bytes"));
211 parser.addOption(postBuffering);
213 QCommandLineOption postBufferingBufsize(
214 QStringLiteral(
"post-buffering-bufsize"),
217 qtTrId(
"cutelystd-opt-post-buffering-bufsize-desc"),
218 qtTrId(
"cutelystd-opt-value-bytes"));
219 parser.addOption(postBufferingBufsize);
221 QCommandLineOption httpSocketOpt({QStringLiteral(
"http-socket"), QStringLiteral(
"h1")},
224 qtTrId(
"cutelystd-opt-http-socket-desc"),
227 qtTrId(
"cutelystd-opt-value-address"));
228 parser.addOption(httpSocketOpt);
230 QCommandLineOption http2SocketOpt(
231 {QStringLiteral(
"http2-socket"), QStringLiteral(
"h2")},
234 qtTrId(
"cutelystd-opt-http2-socket-desc"),
235 qtTrId(
"cutelystd-opt-value-address"));
236 parser.addOption(http2SocketOpt);
238 QCommandLineOption http2HeaderTableSizeOpt(QStringLiteral(
"http2-header-table-size"),
241 qtTrId(
"cutelystd-opt-http2-header-table-size-desc"),
242 qtTrId(
"cutelystd-opt-value-size"));
243 parser.addOption(http2HeaderTableSizeOpt);
245 QCommandLineOption upgradeH2cOpt(QStringLiteral(
"upgrade-h2c"),
248 qtTrId(
"cutelystd-opt-upgrade-h2c-desc"));
249 parser.addOption(upgradeH2cOpt);
251 QCommandLineOption httpsH2Opt(QStringLiteral(
"https-h2"),
254 qtTrId(
"cutelystd-opt-https-h2-desc"));
255 parser.addOption(httpsH2Opt);
257 QCommandLineOption httpsSocketOpt({QStringLiteral(
"https-socket"), QStringLiteral(
"hs1")},
260 qtTrId(
"cutelystd-opt-https-socket-desc"),
262 qtTrId(
"cutelystd-opt-value-httpsaddress"));
263 parser.addOption(httpsSocketOpt);
265 QCommandLineOption fastcgiSocketOpt(
266 QStringLiteral(
"fastcgi-socket"),
269 qtTrId(
"cutelystd-opt-fastcgi-socket-desc"),
270 qtTrId(
"cutelystd-opt-value-address"));
271 parser.addOption(fastcgiSocketOpt);
273 QCommandLineOption socketAccess(
274 QStringLiteral(
"socket-access"),
277 qtTrId(
"cutelystd-opt-socket-access-desc"),
280 qtTrId(
"cutelystd-opt-socket-access-value"));
281 parser.addOption(socketAccess);
283 QCommandLineOption socketTimeout({QStringLiteral(
"socket-timeout"), QStringLiteral(
"z")},
286 qtTrId(
"cutelystd-opt-socket-timeout-desc"),
289 qtTrId(
"cutelystd-opt-socket-timeout-value"));
290 parser.addOption(socketTimeout);
292 QCommandLineOption staticMapOpt(QStringLiteral(
"static-map"),
298 qtTrId(
"cutelystd-opt-static-map-desc"),
301 qtTrId(
"cutelystd-opt-value-static-map"));
302 parser.addOption(staticMapOpt);
304 QCommandLineOption staticMap2Opt(QStringLiteral(
"static-map2"),
308 qtTrId(
"cutelystd-opt-static-map2-desc"),
311 qtTrId(
"cutelystd-opt-value-static-map"));
312 parser.addOption(staticMap2Opt);
314 QCommandLineOption autoReload({QStringLiteral(
"auto-restart"), QStringLiteral(
"r")},
318 qtTrId(
"cutelystd-opt-auto-restart-desc"));
319 parser.addOption(autoReload);
321 QCommandLineOption touchReloadOpt(
322 QStringLiteral(
"touch-reload"),
326 qtTrId(
"cutelystd-opt-touch-reload-desc"),
327 qtTrId(
"cutelystd-opt-value-file"));
328 parser.addOption(touchReloadOpt);
330 QCommandLineOption tcpNoDelay(QStringLiteral(
"tcp-nodelay"),
333 qtTrId(
"cutelystd-opt-tcp-nodelay-desc"));
334 parser.addOption(tcpNoDelay);
336 QCommandLineOption soKeepAlive(QStringLiteral(
"so-keepalive"),
339 qtTrId(
"cutelystd-opt-so-keepalive-desc"));
340 parser.addOption(soKeepAlive);
342 QCommandLineOption socketSndbuf(QStringLiteral(
"socket-sndbuf"),
346 qtTrId(
"cutelystd-opt-socket-sndbuf-desc"),
347 qtTrId(
"cutelystd-opt-value-bytes"));
348 parser.addOption(socketSndbuf);
350 QCommandLineOption socketRcvbuf(QStringLiteral(
"socket-rcvbuf"),
354 qtTrId(
"cutelystd-opt-socket-rcvbuf-desc"),
355 qtTrId(
"cutelystd-opt-value-bytes"));
356 parser.addOption(socketRcvbuf);
358 QCommandLineOption wsMaxSize(QStringLiteral(
"websocket-max-size"),
362 qtTrId(
"cutelystd-opt-websocket-max-size-desc"),
365 qtTrId(
"cutelystd-opt-websocket-max-size-value"));
366 parser.addOption(wsMaxSize);
368 QCommandLineOption pidfileOpt(QStringLiteral(
"pidfile"),
371 qtTrId(
"cutelystd-opt-pidfile-desc"),
374 qtTrId(
"cutelystd-opt-value-pidfile"));
375 parser.addOption(pidfileOpt);
377 QCommandLineOption pidfile2Opt(QStringLiteral(
"pidfile2"),
380 qtTrId(
"cutelystd-opt-pidfile2-desc"),
381 qtTrId(
"cutelystd-opt-value-pidfile"));
382 parser.addOption(pidfile2Opt);
385 QCommandLineOption stopOption(QStringLiteral(
"stop"),
388 qtTrId(
"cutelystd-opt-stop-desc"),
389 qtTrId(
"cutelystd-opt-value-pidfile"));
390 parser.addOption(stopOption);
392 QCommandLineOption uidOption(QStringLiteral(
"uid"),
395 qtTrId(
"cutelystd-opt-uid-desc"),
398 qtTrId(
"cutelystd-opt-uid-value"));
399 parser.addOption(uidOption);
401 QCommandLineOption gidOption(QStringLiteral(
"gid"),
404 qtTrId(
"cutelystd-opt-gid-desc"),
407 qtTrId(
"cutelystd-opt-gid-value"));
408 parser.addOption(gidOption);
410 QCommandLineOption noInitgroupsOption(QStringLiteral(
"no-initgroups"),
413 qtTrId(
"cutelystd-opt-no-init-groups-desc"));
414 parser.addOption(noInitgroupsOption);
416 QCommandLineOption chownSocketOption(QStringLiteral(
"chown-socket"),
419 qtTrId(
"cutelystd-opt-chown-socket-desc"),
422 qtTrId(
"cutelystd-opt-chown-socket-value"));
423 parser.addOption(chownSocketOption);
425 QCommandLineOption umaskOption(QStringLiteral(
"umask"),
428 qtTrId(
"cutelystd-opt-umask-desc"),
431 qtTrId(
"cutelystd-opt-umask-value"));
432 parser.addOption(umaskOption);
434 QCommandLineOption cpuAffinityOption(
435 QStringLiteral(
"cpu-affinity"),
438 qtTrId(
"cutelystd-opt-cpu-affinity-desc"),
441 qtTrId(
"cutelystd-opt-cpu-affinity-value"));
442 parser.addOption(cpuAffinityOption);
446 QCommandLineOption reusePortOption(QStringLiteral(
"reuse-port"),
449 qtTrId(
"cutelystd-opt-reuse-port-desc"));
450 parser.addOption(reusePortOption);
453 QCommandLineOption threadBalancerOpt(
454 QStringLiteral(
"experimental-thread-balancer"),
457 qtTrId(
"cutelystd-opt-experimental-thread-balancer-desc"));
458 parser.addOption(threadBalancerOpt);
460 QCommandLineOption frontendProxy(QStringLiteral(
"using-frontend-proxy"),
463 qtTrId(
"cutelystd-opt-using-frontend-proxy-desc"));
464 parser.addOption(frontendProxy);
467 parser.process(arguments);
469 setIni(parser.values(iniOpt));
471 setJson(parser.values(jsonOpt));
473 if (parser.isSet(
chdir)) {
474 setChdir(parser.value(
chdir));
477 if (parser.isSet(
chdir2)) {
478 setChdir2(parser.value(
chdir2));
482 setThreads(parser.value(
threads));
485 if (parser.isSet(socketAccess)) {
486 setSocketAccess(parser.value(socketAccess));
489 if (parser.isSet(socketTimeout)) {
491 auto size = parser.value(socketTimeout).toInt(&ok);
492 setSocketTimeout(size);
493 if (!ok || size < 0) {
498 if (parser.isSet(pidfileOpt)) {
499 setPidfile(parser.value(pidfileOpt));
502 if (parser.isSet(pidfile2Opt)) {
503 setPidfile2(parser.value(pidfile2Opt));
507 if (parser.isSet(stopOption)) {
508 UnixFork::stopWSGI(parser.value(stopOption));
515 if (parser.isSet(uidOption)) {
516 setUid(parser.value(uidOption));
519 if (parser.isSet(gidOption)) {
520 setGid(parser.value(gidOption));
523 if (parser.isSet(noInitgroupsOption)) {
524 setNoInitgroups(
true);
527 if (parser.isSet(chownSocketOption)) {
528 setChownSocket(parser.value(chownSocketOption));
531 if (parser.isSet(umaskOption)) {
532 setUmask(parser.value(umaskOption));
535 if (parser.isSet(cpuAffinityOption)) {
537 auto value = parser.value(cpuAffinityOption).toInt(&ok);
538 setCpuAffinity(value);
539 if (!ok || value < 0) {
546 if (parser.isSet(reusePortOption)) {
551 if (parser.isSet(lazyOption)) {
555 if (parser.isSet(listenQueue)) {
557 auto size = parser.value(listenQueue).toInt(&ok);
558 setListenQueue(size);
559 if (!ok || size < 1) {
564 if (parser.isSet(bufferSize)) {
566 auto size = parser.value(bufferSize).toInt(&ok);
568 if (!ok || size < 1) {
573 if (parser.isSet(postBuffering)) {
575 auto size = parser.value(postBuffering).toLongLong(&ok);
576 setPostBuffering(size);
577 if (!ok || size < 1) {
582 if (parser.isSet(postBufferingBufsize)) {
584 auto size = parser.value(postBufferingBufsize).toLongLong(&ok);
585 setPostBufferingBufsize(size);
586 if (!ok || size < 1) {
595 if (parser.isSet(
master)) {
599 if (parser.isSet(autoReload)) {
603 if (parser.isSet(tcpNoDelay)) {
607 if (parser.isSet(soKeepAlive)) {
608 setSoKeepalive(
true);
611 if (parser.isSet(upgradeH2cOpt)) {
615 if (parser.isSet(httpsH2Opt)) {
619 if (parser.isSet(socketSndbuf)) {
621 auto size = parser.value(socketSndbuf).toInt(&ok);
622 setSocketSndbuf(size);
623 if (!ok || size < 1) {
628 if (parser.isSet(socketRcvbuf)) {
630 auto size = parser.value(socketRcvbuf).toInt(&ok);
631 setSocketRcvbuf(size);
632 if (!ok || size < 1) {
637 if (parser.isSet(wsMaxSize)) {
639 auto size = parser.value(wsMaxSize).toInt(&ok);
640 setWebsocketMaxSize(size);
641 if (!ok || size < 1) {
646 if (parser.isSet(http2HeaderTableSizeOpt)) {
648 auto size = parser.value(http2HeaderTableSizeOpt).toUInt(&ok);
649 setHttp2HeaderTableSize(size);
650 if (!ok || size < 1) {
655 if (parser.isSet(frontendProxy)) {
656 setUsingFrontendProxy(
true);
659 setHttpSocket(httpSocket() + parser.values(httpSocketOpt));
661 setHttp2Socket(http2Socket() + parser.values(http2SocketOpt));
663 setHttpsSocket(httpsSocket() + parser.values(httpsSocketOpt));
665 setFastcgiSocket(fastcgiSocket() + parser.values(fastcgiSocketOpt));
667 setStaticMap(staticMap() + parser.values(staticMapOpt));
669 setStaticMap2(staticMap2() + parser.values(staticMap2Opt));
671 setTouchReload(touchReload() + parser.values(touchReloadOpt));
673 d->threadBalancer = parser.isSet(threadBalancerOpt);
679 std::cout <<
"Cutelyst-Server starting" << std::endl;
681 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER") && !d->master) {
683 <<
"*** WARNING: you are running Cutelyst-Server without its master process manager ***"
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();
697 if (d->processes == 0 && d->master) {
700 d->genericFork =
new UnixFork(d->processes, qMax(d->threads, 1), !d->userEventLoop,
this);
702 if (d->processes == -1) {
705 if (d->threads == -1) {
706 d->threads = QThread::idealThreadCount();
712 d->genericFork, &AbstractFork::forked, d, &ServerPrivate::postFork, Qt::DirectConnection);
714 d->genericFork, &AbstractFork::shutdown, d, &ServerPrivate::shutdown, Qt::DirectConnection);
716 if (d->master && d->lazy) {
717 if (d->autoReload && !d->application.isEmpty()) {
718 d->touchReload.append(d->application);
720 d->genericFork->setTouchReload(d->touchReload);
724 if (d->master && !d->genericFork->continueMaster(&ret)) {
729 if (systemdNotify::is_systemd_notify_available()) {
731 sd->setWatchdog(
true, systemdNotify::sd_watchdog_enabled(
true));
733 sd->sendStatus(qApp->applicationName().toLatin1() +
" is ready");
736 connect(d, &ServerPrivate::postForked, sd, [sd] { sd->setWatchdog(
false); });
737 qInfo(CUTELYST_SERVER) <<
"systemd notify detected";
745 if (!d->listenTcpSockets()) {
747 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-opened"));
752 if (!d->writePidFile(d->pidfile)) {
754 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-write-pidfile").arg(d->pidfile));
758 bool isListeningLocalSockets =
false;
759 if (!d->chownSocket.isEmpty()) {
760 if (!d->listenLocalSockets()) {
762 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-open-local-socket"));
765 isListeningLocalSockets =
true;
768 if (!d->umask.isEmpty() && !UnixFork::setUmask(d->umask.toLatin1())) {
772 if (!UnixFork::setGidUid(d->gid, d->uid, d->noInitgroups)) {
778 if (!isListeningLocalSockets) {
780 d->listenLocalSockets();
786 if (!d->listenTcpSockets()) {
787 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-opened"));
792 if (d->servers.empty()) {
793 std::cout <<
"Please specify a socket to listen to" << std::endl;
795 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-no-socket-specified"));
799 d->writePidFile(d->pidfile2);
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())));
813 if (!d->setupApplication()) {
815 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
820 if (d->userEventLoop) {
825 ret = d->genericFork->exec(d->lazy, d->master);
836 Q_EMIT
errorOccured(qtTrId(
"cutelystd-err-server-not-fully-stopped"));
843 d->userEventLoop =
true;
848 qputenv(
"CUTELYST_SERVER_IGNORE_MASTER", QByteArrayLiteral(
"1"));
850 if (
exec(app) == 0) {
860 if (d->userEventLoop) {
861 Q_EMIT d->shutdown();
865ServerPrivate::~ServerPrivate()
872bool ServerPrivate::listenTcpSockets()
874 if (httpSockets.isEmpty() && httpsSockets.isEmpty() && http2Sockets.isEmpty() &&
875 fastcgiSockets.isEmpty()) {
881 for (
const auto &socket : qAsConst(httpSockets)) {
882 if (!listenTcp(socket, getHttpProto(),
false)) {
888 for (
const auto &socket : qAsConst(httpsSockets)) {
889 if (!listenTcp(socket, getHttpProto(),
true)) {
895 for (
const auto &socket : qAsConst(http2Sockets)) {
896 if (!listenTcp(socket, getHttp2Proto(),
false)) {
902 for (
const auto &socket : qAsConst(fastcgiSockets)) {
903 if (!listenTcp(socket, getFastCgiProto(),
false)) {
911bool ServerPrivate::listenTcp(
const QString &line,
Protocol *protocol,
bool secure)
916 if (!line.startsWith(u
'/')) {
918 server->setBalancer(threadBalancer);
919 ret = server->listen(line, protocol, secure);
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);
934bool ServerPrivate::listenLocalSockets()
936 QStringList http = httpSockets;
937 QStringList http2 = http2Sockets;
938 QStringList fastcgi = fastcgiSockets;
943 std::vector<int> fds = systemdNotify::listenFds();
946 if (server->listen(fd)) {
947 const QString name = server->serverName();
948 const QString fullName = server->fullServerName();
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();
958 std::cerr <<
"systemd activated socket does not match any configured socket"
962 server->setProtocol(protocol);
963 server->pauseAccepting();
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);
972 std::cerr <<
"Failed to listen on activated LOCAL FD: "
973 << QByteArray::number(fd).constData() <<
" : "
974 << qPrintable(server->errorString()) << std::endl;
981 const auto httpConst = http;
982 for (
const auto &socket : httpConst) {
983 ret |= listenLocal(socket, getHttpProto());
986 const auto http2Const = http2;
987 for (
const auto &socket : http2Const) {
988 ret |= listenLocal(socket, getHttp2Proto());
991 const auto fastcgiConst = fastcgi;
992 for (
const auto &socket : fastcgiConst) {
993 ret |= listenLocal(socket, getFastCgiProto());
999bool ServerPrivate::listenLocal(
const QString &line,
Protocol *protocol)
1004 if (line.startsWith(QLatin1Char(
'/'))) {
1006 server->setProtocol(protocol);
1007 if (!socketAccess.isEmpty()) {
1008 QLocalServer::SocketOptions options;
1009 if (socketAccess.contains(u
'u')) {
1010 options |= QLocalServer::UserAccessOption;
1013 if (socketAccess.contains(u
'g')) {
1014 options |= QLocalServer::GroupAccessOption;
1017 if (socketAccess.contains(u
'o')) {
1018 options |= QLocalServer::OtherAccessOption;
1020 server->setSocketOptions(options);
1022 server->removeServer(line);
1023 ret = server->listen(line);
1024 server->pauseAccepting();
1026 if (!ret || !server->socket()) {
1027 std::cerr <<
"Failed to listen on LOCAL: " << qPrintable(line) <<
" : "
1028 << qPrintable(server->errorString()) << std::endl;
1033 if (!chownSocket.isEmpty()) {
1034 UnixFork::chownSocket(line, chownSocket);
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);
1048void Server::setApplication(
const QString &application)
1053 if (loader.fileName().isEmpty()) {
1058 d->application = loader.fileName();
1066 return d->application;
1069void Server::setThreads(
const QString &threads)
1072 if (
threads.compare(u
"auto", Qt::CaseInsensitive) == 0) {
1075 d->threads = qMax(1,
threads.toInt());
1083 if (d->threads == -1) {
1084 return QStringLiteral(
"auto");
1086 return QString::number(d->threads);
1089void Server::setProcesses(
const QString &process)
1093 if (process.compare(QLatin1String(
"auto"), Qt::CaseInsensitive) == 0) {
1096 d->processes = process.toInt();
1105 if (d->processes == -1) {
1106 return QStringLiteral(
"auto");
1108 return QString::number(d->processes);
1111void Server::setChdir(
const QString &chdir)
1124void Server::setHttpSocket(
const QStringList &httpSocket)
1127 d->httpSockets = httpSocket;
1131QStringList Server::httpSocket()
const
1134 return d->httpSockets;
1137void Server::setHttp2Socket(
const QStringList &http2Socket)
1140 d->http2Sockets = http2Socket;
1144QStringList Server::http2Socket()
const
1147 return d->http2Sockets;
1150void Server::setHttp2HeaderTableSize(quint32 headerTableSize)
1153 d->http2HeaderTableSize = headerTableSize;
1157quint32 Server::http2HeaderTableSize()
const
1160 return d->http2HeaderTableSize;
1163void Server::setUpgradeH2c(
bool enable)
1166 d->upgradeH2c = enable;
1170bool Server::upgradeH2c()
const
1173 return d->upgradeH2c;
1176void Server::setHttpsH2(
bool enable)
1179 d->httpsH2 = enable;
1183bool Server::httpsH2()
const
1189void Server::setHttpsSocket(
const QStringList &httpsSocket)
1192 d->httpsSockets = httpsSocket;
1196QStringList Server::httpsSocket()
const
1199 return d->httpsSockets;
1202void Server::setFastcgiSocket(
const QStringList &fastcgiSocket)
1205 d->fastcgiSockets = fastcgiSocket;
1209QStringList Server::fastcgiSocket()
const
1212 return d->fastcgiSockets;
1215void Server::setSocketAccess(
const QString &socketAccess)
1218 d->socketAccess = socketAccess;
1222QString Server::socketAccess()
const
1225 return d->socketAccess;
1228void Server::setSocketTimeout(
int timeout)
1231 d->socketTimeout = timeout;
1235int Server::socketTimeout()
const
1238 return d->socketTimeout;
1241void Server::setChdir2(
const QString &chdir2)
1254void Server::setIni(
const QStringList &files)
1257 d->ini.append(files);
1258 d->ini.removeDuplicates();
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);
1280void Server::setJson(
const QStringList &files)
1283 d->json.append(files);
1284 d->json.removeDuplicates();
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);
1306void Server::setStaticMap(
const QStringList &staticMap)
1309 d->staticMaps = staticMap;
1313QStringList Server::staticMap()
const
1316 return d->staticMaps;
1319void Server::setStaticMap2(
const QStringList &staticMap)
1322 d->staticMaps2 = staticMap;
1326QStringList Server::staticMap2()
const
1329 return d->staticMaps2;
1332void Server::setMaster(
bool enable)
1335 if (!qEnvironmentVariableIsSet(
"CUTELYST_SERVER_IGNORE_MASTER")) {
1347void Server::setAutoReload(
bool enable)
1351 d->autoReload =
true;
1356bool Server::autoReload()
const
1359 return d->autoReload;
1362void Server::setTouchReload(
const QStringList &files)
1365 d->touchReload = files;
1369QStringList Server::touchReload()
const
1372 return d->touchReload;
1375void Server::setListenQueue(
int size)
1378 d->listenQueue = size;
1382int Server::listenQueue()
const
1385 return d->listenQueue;
1388void Server::setBufferSize(
int size)
1392 qCWarning(CUTELYST_SERVER) <<
"Buffer size must be at least 4096 bytes, ignoring";
1395 d->bufferSize = size;
1399int Server::bufferSize()
const
1402 return d->bufferSize;
1405void Server::setPostBuffering(qint64 size)
1408 d->postBuffering = size;
1412qint64 Server::postBuffering()
const
1415 return d->postBuffering;
1418void Server::setPostBufferingBufsize(qint64 size)
1422 qCWarning(CUTELYST_SERVER) <<
"Post buffer size must be at least 4096 bytes, ignoring";
1425 d->postBufferingBufsize = size;
1429qint64 Server::postBufferingBufsize()
const
1432 return d->postBufferingBufsize;
1435void Server::setTcpNodelay(
bool enable)
1438 d->tcpNodelay = enable;
1442bool Server::tcpNodelay()
const
1445 return d->tcpNodelay;
1448void Server::setSoKeepalive(
bool enable)
1451 d->soKeepalive = enable;
1455bool Server::soKeepalive()
const
1458 return d->soKeepalive;
1461void Server::setSocketSndbuf(
int value)
1464 d->socketSendBuf = value;
1468int Server::socketSndbuf()
const
1471 return d->socketSendBuf;
1474void Server::setSocketRcvbuf(
int value)
1477 d->socketReceiveBuf = value;
1481int Server::socketRcvbuf()
const
1484 return d->socketReceiveBuf;
1487void Server::setWebsocketMaxSize(
int value)
1490 d->websocketMaxSize = value * 1024;
1494int Server::websocketMaxSize()
const
1497 return d->websocketMaxSize / 1024;
1500void Server::setPidfile(
const QString &file)
1513void Server::setPidfile2(
const QString &file)
1526void Server::setUid(
const QString &uid)
1541void Server::setGid(
const QString &gid)
1556void Server::setNoInitgroups(
bool enable)
1560 d->noInitgroups = enable;
1565bool Server::noInitgroups()
const
1568 return d->noInitgroups;
1571void Server::setChownSocket(
const QString &chownSocket)
1575 d->chownSocket = chownSocket;
1580QString Server::chownSocket()
const
1583 return d->chownSocket;
1586void Server::setUmask(
const QString &value)
1601void Server::setCpuAffinity(
int value)
1605 d->cpuAffinity = value;
1610int Server::cpuAffinity()
const
1613 return d->cpuAffinity;
1616void Server::setReusePort(
bool enable)
1620 d->reusePort = enable;
1627bool Server::reusePort()
const
1630 return d->reusePort;
1633void Server::setLazy(
bool enable)
1646void Server::setUsingFrontendProxy(
bool enable)
1649 d->usingFrontendProxy = enable;
1653bool Server::usingFrontendProxy()
const
1656 return d->usingFrontendProxy;
1665bool ServerPrivate::setupApplication()
1672 std::cout <<
"Loading application: " << application.toLatin1().constData() << std::endl;
1673 QPluginLoader loader(application);
1674 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::PreventUnloadHint);
1675 if (!loader.load()) {
1676 qCCritical(CUTELYST_SERVER) <<
"Could not load application:" << loader.errorString();
1680 QObject *instance = loader.instance();
1682 qCCritical(CUTELYST_SERVER) <<
"Could not get a QObject instance: %s\n"
1683 << loader.errorString();
1687 localApp = qobject_cast<Cutelyst::Application *>(instance);
1689 qCCritical(CUTELYST_SERVER)
1690 <<
"Could not cast Cutelyst::Application from instance: %s\n"
1691 << loader.errorString();
1699 qCDebug(CUTELYST_SERVER) <<
"Loaded application: " << QCoreApplication::applicationName();
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())));
1712 engine = createEngine(localApp, 0);
1713 for (
int i = 1; i < threads; ++i) {
1714 if (createEngine(localApp, i)) {
1715 ++workersNotRunning;
1719 engine = createEngine(localApp, 0);
1720 workersNotRunning = 1;
1724 std::cerr <<
"Application failed to init, cheaping..." << std::endl;
1731void ServerPrivate::engineShutdown(
ServerEngine *engine)
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();
1739 engineThread->quit();
1741 engines.erase(std::remove(engines.begin(), engines.end(), engine), engines.end());
1744 checkEngineShutdown();
1747void ServerPrivate::checkEngineShutdown()
1749 if (engines.empty()) {
1750 if (userEventLoop) {
1752 Q_EMIT q->stopped();
1754 QTimer::singleShot(std::chrono::seconds{0},
this, [] { qApp->exit(15); });
1759void ServerPrivate::workerStarted()
1764 if (--workersNotRunning == 0) {
1769bool ServerPrivate::postFork(
int workerId)
1774 if (!setupApplication()) {
1775 Q_EMIT q->errorOccured(qtTrId(
"cutelystd-err-fail-setup-app"));
1780 if (engines.size() > 1) {
1781 qCDebug(CUTELYST_SERVER) <<
"Starting threads";
1785 QThread *thread = engine->thread();
1786 if (thread != qApp->thread()) {
1788 if (!qEnvironmentVariableIsSet(
"CUTELYST_QT_EVENT_LOOP")) {
1789 thread->setEventDispatcher(
new EventDispatcherEPoll);
1797 Q_EMIT postForked(workerId);
1799 QTimer::singleShot(std::chrono::seconds{1},
this, [=]() {
1805 qApp->processEvents();
1811bool ServerPrivate::writePidFile(
const QString &filename)
1813 if (filename.isEmpty()) {
1817 QFile file(filename);
1818 if (!file.open(QFile::WriteOnly | QFile::Text)) {
1819 std::cerr <<
"Failed write pid file " << qPrintable(filename) << std::endl;
1823 std::cout <<
"Writing pidfile to " << qPrintable(filename) << std::endl;
1824 file.write(QByteArray::number(QCoreApplication::applicationPid()) +
'\n');
1834 if (workerCore > 0) {
1835 app = qobject_cast<Application *>(app->metaObject()->newInstance());
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.");
1842 auto engine =
new ServerEngine(app, workerCore, opt, q);
1843 connect(
this, &ServerPrivate::shutdown, engine, &ServerEngine::shutdown, Qt::QueuedConnection);
1845 this, &ServerPrivate::postForked, engine, &ServerEngine::postFork, Qt::QueuedConnection);
1847 &ServerEngine::shutdownCompleted,
1849 &ServerPrivate::engineShutdown,
1850 Qt::QueuedConnection);
1852 engine, &ServerEngine::started,
this, &ServerPrivate::workerStarted, Qt::QueuedConnection);
1855 engine->setServers(servers);
1856 if (!engine->
init()) {
1857 std::cerr <<
"Application failed to init(), cheaping core: " << workerCore << std::endl;
1862 engines.push_back(engine);
1865 if (workerCore > 0) {
1868 app->setParent(engine);
1870 auto thread =
new QThread(
this);
1871 engine->moveToThread(thread);
1873 engine->setParent(
this);
1879void ServerPrivate::loadConfig()
1881 if (loadingConfig) {
1885 loadingConfig =
true;
1887 if (configToLoad.isEmpty()) {
1888 loadingConfig =
false;
1892 auto fileToLoad = configToLoad.dequeue();
1894 if (fileToLoad.first.isEmpty()) {
1895 qCWarning(CUTELYST_SERVER) <<
"Can not load config from empty config file name";
1896 loadingConfig =
false;
1900 if (configLoaded.contains(fileToLoad.first)) {
1901 loadingConfig =
false;
1905 configLoaded.append(fileToLoad.first);
1907 QVariantMap loadedConfig;
1908 switch (fileToLoad.second) {
1909 case ConfigFormat::Ini:
1910 qCInfo(CUTELYST_SERVER) <<
"Loading INI configuratin:" << fileToLoad.first;
1913 case ConfigFormat::Json:
1914 qCInfo(CUTELYST_SERVER) <<
"Loading JSON configuration:" << fileToLoad.first;
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());
1929 config.insert(loadedIt.key(), currentMap);
1931 config.insert(loadedIt.key(), loadedIt.value());
1936 QVariantMap sessionConfig = loadedConfig.value(u
"server"_qs).toMap();
1938 applyConfig(sessionConfig);
1940 opt.insert(sessionConfig);
1942 loadingConfig =
false;
1944 if (!configToLoad.empty()) {
1949void ServerPrivate::applyConfig(
const QVariantMap &config)
1953 auto it = config.constBegin();
1954 while (it != config.constEnd()) {
1955 QString normKey = it.key();
1956 normKey.replace(u
'-', u
'_');
1958 int ix = q->metaObject()->indexOfProperty(normKey.toLatin1().constData());
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());
1971 prop.write(q, value);
1973 }
else if (prop.userType() == QMetaType::QStringList) {
1974 const QStringList currentValues = prop.read(q).toStringList();
1975 prop.write(q, currentValues + QStringList{value.toString()});
1977 prop.write(q, value);
1984Protocol *ServerPrivate::getHttpProto()
2006Protocol *ServerPrivate::getFastCgiProto()
2015#include "moc_server.cpp"
2016#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.