/**************************************************************************** ** ** This file is part of the KD Soap project. ** ** SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company ** ** SPDX-License-Identifier: MIT ** ****************************************************************************/ #include "KDSoapServer.h" #include "KDSoapSocketList_p.h" #include "KDSoapThreadPool.h" #include #include #ifdef Q_OS_UNIX #include #include #include #include #endif class KDSoapServer::Private { public: Private() : m_threadPool(nullptr) , m_mainThreadSocketList(nullptr) , m_use(KDSoapMessage::LiteralUse) , m_logLevel(KDSoapServer::LogNothing) , m_path(QString::fromLatin1("/")) , m_maxConnections(-1) , m_portBeforeSuspend(0) { } ~Private() { delete m_mainThreadSocketList; } KDSoapThreadPool *m_threadPool; KDSoapSocketList *m_mainThreadSocketList; KDSoapMessage::Use m_use; KDSoapServer::Features m_features; QMutex m_logMutex; KDSoapServer::LogLevel m_logLevel; QString m_logFileName; QFile m_logFile; QMutex m_serverDataMutex; QString m_wsdlFile; QString m_wsdlPathInUrl; QString m_path; int m_maxConnections; QHostAddress m_addressBeforeSuspend; quint16 m_portBeforeSuspend; #ifndef QT_NO_SSL QSslConfiguration m_sslConfiguration; #endif }; KDSoapServer::KDSoapServer(QObject *parent) : QTcpServer(parent) , d(new KDSoapServer::Private) { // Probably not very useful since we handle them immediately, but cannot hurt. setMaxPendingConnections(1000); } KDSoapServer::~KDSoapServer() { delete d; } void KDSoapServer::incomingConnection(qintptr socketDescriptor) { const int max = maxConnections(); const int numSockets = numConnectedSockets(); if (max > -1 && numSockets >= max) { emit connectionRejected(); log(QByteArray("ERROR Too many connections (") + QByteArray::number(numSockets) + "), incoming connection rejected\n"); } else if (d->m_threadPool) { // qDebug() << "incomingConnection: using thread pool"; d->m_threadPool->handleIncomingConnection(socketDescriptor, this); } else { // qDebug() << "incomingConnection: using main-thread socketlist"; if (!d->m_mainThreadSocketList) { d->m_mainThreadSocketList = new KDSoapSocketList(this /*server*/); } d->m_mainThreadSocketList->handleIncomingConnection(socketDescriptor); } } int KDSoapServer::numConnectedSockets() const { if (d->m_threadPool) { return d->m_threadPool->numConnectedSockets(this); } else if (d->m_mainThreadSocketList) { return d->m_mainThreadSocketList->socketCount(); } else { return 0; } } int KDSoapServer::totalConnectionCount() const { if (d->m_threadPool) { return d->m_threadPool->totalConnectionCount(this); } else if (d->m_mainThreadSocketList) { return d->m_mainThreadSocketList->totalConnectionCount(); } else { return 0; } } void KDSoapServer::resetTotalConnectionCount() { if (d->m_threadPool) { d->m_threadPool->resetTotalConnectionCount(this); } else if (d->m_mainThreadSocketList) { d->m_mainThreadSocketList->resetTotalConnectionCount(); } } void KDSoapServer::setThreadPool(KDSoapThreadPool *threadPool) { d->m_threadPool = threadPool; } KDSoapThreadPool *KDSoapServer::threadPool() const { return d->m_threadPool; } QString KDSoapServer::endPoint() const { QMutexLocker lock(&d->m_serverDataMutex); const QHostAddress address = serverAddress(); if (address == QHostAddress::Null) { return QString(); } const QString addressStr = address == QHostAddress::Any ? QString::fromLatin1("127.0.0.1") : address.toString(); return QString::fromLatin1("%1://%2:%3%4") .arg(QString::fromLatin1((d->m_features & Ssl) ? "https" : "http"), addressStr) .arg(serverPort()) .arg(d->m_path); } void KDSoapServer::setUse(KDSoapMessage::Use use) { d->m_use = use; } KDSoapMessage::Use KDSoapServer::use() const { return d->m_use; } void KDSoapServer::setLogLevel(KDSoapServer::LogLevel level) { QMutexLocker lock(&d->m_logMutex); d->m_logLevel = level; } KDSoapServer::LogLevel KDSoapServer::logLevel() const { QMutexLocker lock(&d->m_logMutex); return d->m_logLevel; } void KDSoapServer::setLogFileName(const QString &fileName) { QMutexLocker lock(&d->m_logMutex); d->m_logFileName = fileName; } QString KDSoapServer::logFileName() const { QMutexLocker lock(&d->m_logMutex); return d->m_logFileName; } void KDSoapServer::log(const QByteArray &text) { if (d->m_logLevel == KDSoapServer::LogNothing) { return; } QMutexLocker lock(&d->m_logMutex); if (d->m_logFileName.isEmpty()) { return; } if (!d->m_logFile.isOpen()) { d->m_logFile.setFileName(d->m_logFileName); if (!d->m_logFile.open(QIODevice::Append)) { qCritical("Could not open log file for writing: %s", qPrintable(d->m_logFileName)); d->m_logFileName.clear(); // don't retry every time log() is called return; } } d->m_logFile.write(text); } void KDSoapServer::flushLogFile() { if (d->m_logFile.isOpen()) { d->m_logFile.flush(); } } void KDSoapServer::closeLogFile() { if (d->m_logFile.isOpen()) { d->m_logFile.close(); } } bool KDSoapServer::setExpectedSocketCount(int sockets) { // I hit a system limit when trying to connect more than 1024 sockets in the same process. // strace said: socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = -1 EMFILE (Too many open files) // Solution: ulimit -n 4096 // Or in C code, below. #ifdef Q_OS_UNIX struct rlimit lim; if (getrlimit(RLIMIT_NOFILE, &lim) != 0) { qDebug() << "error calling getrlimit:" << strerror(errno); return false; } bool changingHardLimit = false; if (sockets > -1) { qDebug() << "Current limit" << lim.rlim_cur << lim.rlim_max; sockets += 20; // we need some file descriptors too if (rlim_t(sockets) <= lim.rlim_cur) { return true; // nothing to do } if (rlim_t(sockets) > lim.rlim_max) { // Seems we need to run as root then lim.rlim_max = sockets; qDebug() << "Setting rlim_max to" << sockets; changingHardLimit = true; } } #ifdef OPEN_MAX // Mac OSX: setrlimit() no longer accepts "rlim_cur = RLIM_INFINITY" for RLIM_NOFILE. Use "rlim_cur = min(OPEN_MAX, rlim_max)". lim.rlim_cur = qMin(rlim_t(OPEN_MAX), lim.rlim_max); #else // Linux: does not define OPEN_MAX anymore, since it's "configurable at runtime". lim.rlim_cur = lim.rlim_max; #endif if (setrlimit(RLIMIT_NOFILE, &lim) == 0) { qDebug() << "limit set to" << lim.rlim_cur; } else { if (changingHardLimit) { qDebug() << "WARNING: hard limit is not high enough"; } qDebug() << "error calling setrlimit(" << lim.rlim_cur << "," << lim.rlim_max << ") :" << strerror(errno); return false; } #else Q_UNUSED(sockets); #endif return true; } void KDSoapServer::suspend() { d->m_portBeforeSuspend = serverPort(); d->m_addressBeforeSuspend = serverAddress(); close(); // Disconnect connected sockets, otherwise they could still make calls if (d->m_threadPool) { d->m_threadPool->disconnectSockets(this); } else if (d->m_mainThreadSocketList) { d->m_mainThreadSocketList->disconnectAll(); } } void KDSoapServer::resume() { if (d->m_portBeforeSuspend == 0) { qWarning("KDSoapServer: resume() called without calling suspend() first"); } else { if (!listen(d->m_addressBeforeSuspend, d->m_portBeforeSuspend)) { qWarning("KDSoapServer: failed to listen on %s port %d", qPrintable(d->m_addressBeforeSuspend.toString()), d->m_portBeforeSuspend); } d->m_portBeforeSuspend = 0; } } void KDSoapServer::setWsdlFile(const QString &file, const QString &pathInUrl) { QMutexLocker lock(&d->m_serverDataMutex); d->m_wsdlFile = file; d->m_wsdlPathInUrl = pathInUrl; } QString KDSoapServer::wsdlFile() const { QMutexLocker lock(&d->m_serverDataMutex); return d->m_wsdlFile; } QString KDSoapServer::wsdlPathInUrl() const { QMutexLocker lock(&d->m_serverDataMutex); return d->m_wsdlPathInUrl; } void KDSoapServer::setPath(const QString &path) { QMutexLocker lock(&d->m_serverDataMutex); d->m_path = path; } QString KDSoapServer::path() const { QMutexLocker lock(&d->m_serverDataMutex); return d->m_path; } void KDSoapServer::setMaxConnections(int sockets) { QMutexLocker lock(&d->m_serverDataMutex); d->m_maxConnections = sockets; } int KDSoapServer::maxConnections() const { QMutexLocker lock(&d->m_serverDataMutex); return d->m_maxConnections; } void KDSoapServer::setFeatures(Features features) { QMutexLocker lock(&d->m_serverDataMutex); d->m_features = features; } KDSoapServer::Features KDSoapServer::features() const { QMutexLocker lock(&d->m_serverDataMutex); return d->m_features; } #ifndef QT_NO_SSL QSslConfiguration KDSoapServer::sslConfiguration() const { return d->m_sslConfiguration; } void KDSoapServer::setSslConfiguration(const QSslConfiguration &config) { d->m_sslConfiguration = config; } #endif #include "moc_KDSoapServer.cpp"