diff options
Diffstat (limited to 'kpf/src/WebServer.cpp')
-rw-r--r-- | kpf/src/WebServer.cpp | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/kpf/src/WebServer.cpp b/kpf/src/WebServer.cpp new file mode 100644 index 00000000..37301f09 --- /dev/null +++ b/kpf/src/WebServer.cpp @@ -0,0 +1,644 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@kde.org> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// System includes +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +// Qt includes +#include <qsocket.h> +#include <qdatetime.h> +#include <qtimer.h> + +// KDE includes +#include "config.h" +#include <kconfig.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <dnssd/publicservice.h> + +// Local includes +#include "Defines.h" +#include "Defaults.h" +#include "WebServerSocket.h" +#include "WebServer.h" +#include "Server.h" +#include "Utils.h" + +namespace KPF +{ + static const uint SamplesPerSecond = 10; + static const uint MaxBacklog = 1024; + + class WebServer::Private + { + public: + + Private() + : socket (0L), + listenPort (Config::DefaultListenPort), + connectionLimit (Config::DefaultConnectionLimit), + bandwidthLimit (Config::DefaultBandwidthLimit), + lastTotalOutput (0L), + totalOutput (0L), + portContention (true), + paused (false), + followSymlinks (Config::DefaultFollowSymlinks), + customErrorMessages (false) + { + } + + ~Private() + { + delete socket; + delete service; + service = 0; + socket = 0; + } + + WebServerSocket * socket; + uint listenPort; + uint connectionLimit; + QPtrList<Server> serverList; + QString root; + QString serverName; + QTimer writeTimer; + QTimer resetOutputTimer; + QTimer bindTimer; + QTimer backlogTimer; + ulong bandwidthLimit; + ulong lastTotalOutput; + ulong totalOutput; + bool portContention; + bool paused; + bool followSymlinks; + bool customErrorMessages; + QValueList<int> backlog; + DNSSD::PublicService* service; + + }; + + WebServer::WebServer(const QString & root) + : DCOPObject(QCString("WebServer_") + root.utf8()), + QObject() + { + d = new Private; + + d->root = root; + loadConfig(); + publish(); + + connect(&d->bindTimer, SIGNAL(timeout()), SLOT(slotBind())); + connect(&d->writeTimer, SIGNAL(timeout()), SLOT(slotWrite())); + connect(&d->resetOutputTimer, SIGNAL(timeout()), SLOT(slotCheckOutput())); + connect(&d->backlogTimer, SIGNAL(timeout()), SLOT(slotClearBacklog())); + + d->bindTimer .start( 0, true); + d->resetOutputTimer .start(1000 / SamplesPerSecond, false); + } + + + WebServer::WebServer + ( + const QString & root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString & serverName + ) + : DCOPObject(QCString("WebServer_") + root.utf8()), + QObject() + { + d = new Private; + + d->root = root; + + d->listenPort = listenPort; + d->bandwidthLimit = bandwidthLimit; + d->connectionLimit = connectionLimit; + d->followSymlinks = followSymlinks; + d->serverName = serverName; + + saveConfig(); + publish(); + connect(&d->bindTimer, SIGNAL(timeout()), SLOT(slotBind())); + connect(&d->writeTimer, SIGNAL(timeout()), SLOT(slotWrite())); + connect(&d->resetOutputTimer, SIGNAL(timeout()), SLOT(slotCheckOutput())); + connect(&d->backlogTimer, SIGNAL(timeout()), SLOT(slotClearBacklog())); + + d->bindTimer .start( 0, true); + d->resetOutputTimer .start(1000 / SamplesPerSecond, false); + } + + WebServer::~WebServer() + { + killAllConnections(); + + delete d; + d = 0; + } + + void WebServer::publish() + { + d->service = new DNSSD::PublicService(d->serverName,"_http._tcp",d->listenPort); + connect(d->service,SIGNAL(published(bool)),this,SLOT(wasPublished(bool))); + d->service->publishAsync(); + } + + void WebServer::wasPublished(bool ok) + { + if(ok) { + KMessageBox::information( NULL, i18n("Successfully published this new service to the network (ZeroConf)."), i18n( "Successfully Published the Service" ), "successfullypublished" ); + kpfDebug << "Published to dnssd successfully" << endl; + } + else { + KMessageBox::information( NULL, i18n("Failed to publish this new service to the network (ZeroConf). The server will work fine without this, however."), i18n( "Failed to Publish the Service" ), "failedtopublish" ); + } + } + + void + WebServer::slotBind() + { + if (0 != d->socket) + { + qWarning("Uhhh, socket isn't 0, but I'm told to bind ?"); + return; + } + + d->socket = new WebServerSocket(d->listenPort, d->connectionLimit); + + d->portContention = !d->socket->ok(); + + emit(contentionChange(d->portContention)); + + if (!d->portContention) + connect(d->socket, SIGNAL(connection(int)), SLOT(slotConnection(int))); + + else + { + delete d->socket; + d->socket = 0; + d->bindTimer.start(1000, true); + } + } + + void + WebServer::slotConnection(int fd) + { + if (!d->backlog.isEmpty()) + { + if (d->backlog.count() < MaxBacklog) + { + kpfDebug << "Adding this connection to the backlog." << endl; + d->backlog.append(fd); + } + else + { + kpfDebug << "Backlog full. Ignoring this connection." << endl; + } + return; + } + + if (!handleConnection(fd)) + { + if (d->backlog.count() < MaxBacklog) + { + kpfDebug << "Adding this connection to the backlog." << endl; + d->backlog.append(fd); + d->backlogTimer.start(10, true); + } + else + { + kpfDebug << "Backlog full. Ignoring this connection." << endl; + } + } + } + + bool + WebServer::handleConnection(int fd) + { + if (d->paused) + { + kpfDebug << "Paused." << endl; + return false; + } + + if (d->serverList.count() >= d->connectionLimit) + { +// kpfDebug << "Hit connection limit." << endl; + return false; + } + + int on = 1; + + ::setsockopt + ( + fd, + SOL_SOCKET, + SO_REUSEADDR, + ( char* )&on, + sizeof( on ) ); + + on = 0; + + ::setsockopt + ( + fd, + SOL_SOCKET, + SO_LINGER, + ( char* ) &on, + sizeof( on ) ); + + Server * s = new Server(d->root, d->followSymlinks, fd, this); + + connect + ( + s, + SIGNAL(output(Server *, ulong)), + SLOT(slotOutput(Server *, ulong)) + ); + + connect(s, SIGNAL(finished(Server *)), SLOT(slotFinished(Server *))); + connect(s, SIGNAL(request(Server *)), SIGNAL(request(Server *))); + connect(s, SIGNAL(response(Server *)), SIGNAL(response(Server *))); + + d->serverList.append(s); + + connect + (s, SIGNAL(readyToWrite(Server *)), SLOT(slotReadyToWrite(Server *))); + + emit(connection(s)); + + return true; + } + + void + WebServer::restart() + { + d->bindTimer.stop(); + + killAllConnections(); + delete d->socket; + d->socket = 0; + d->service->setServiceName(d->serverName); + d->service->setPort(d->listenPort); + d->bindTimer.start(0, true); + } + + void + WebServer::killAllConnections() + { + QPtrListIterator<Server> it(d->serverList); + + for (; it.current(); ++it) + it.current()->cancel(); + + } + + void + WebServer::slotOutput(Server * s, ulong l) + { + emit(output(s, l)); + } + + void + WebServer::slotFinished(Server * s) + { + emit(finished(s)); + d->serverList.removeRef(s); + delete s; + s = 0; + } + + void + WebServer::setBandwidthLimit(ulong l) + { + d->bandwidthLimit = l; + saveConfig(); + } + + ulong + WebServer::bandwidthLimit() + { + return d->bandwidthLimit; + } + + void + WebServer::setFollowSymlinks(bool b) + { + d->followSymlinks = b; + saveConfig(); + } + + bool + WebServer::followSymlinks() + { + return d->followSymlinks; + } + + QString + WebServer::root() + { + return d->root; + } + + uint + WebServer::connectionLimit() + { + return d->connectionLimit; + } + + void + WebServer::setConnectionLimit(uint i) + { + d->connectionLimit = i; + saveConfig(); + } + + uint + WebServer::listenPort() + { + return d->listenPort; + } + + void + WebServer::setListenPort(uint i) + { + d->listenPort = i; + saveConfig(); + } + + bool + WebServer::customErrorMessages() + { + return d->customErrorMessages; + } + + void + WebServer::setCustomErrorMessages(bool b) + { + d->customErrorMessages = b; + saveConfig(); + } + + ulong + WebServer::bytesLeft() const + { +#if 0 + // Multiply the bandwidth limit by 10 so we can do 10 checks per second. + + ulong l = + (d->bandwidthLimit * 10240) - (d->totalOutput - d->lastTotalOutput); +#endif + + ulong l = + ulong(d->bandwidthLimit * (1024 / double(SamplesPerSecond))) + - (d->totalOutput - d->lastTotalOutput); + + return l; + } + + ulong + WebServer::bandwidthPerClient() const + { + ulong l = 0L; + + if (!d->serverList.isEmpty()) + { + l = bytesLeft() / d->serverList.count(); + } + + kpfDebug << l << endl; + + return l; + } + + void + WebServer::slotReadyToWrite(Server *) + { + d->writeTimer.stop(); + d->writeTimer.start(0, true); + } + + void + WebServer::slotWrite() + { + if (d->serverList.isEmpty()) + return; + + QPtrListIterator<Server> it(d->serverList); + + for (; it.current(); ++it) + { + if (0 == bytesLeft()) + break; + + Server * s = it.current(); + + if (0 == s->bytesLeft()) + continue; + + ulong bytesAvailable = 0; + + if (0 == bandwidthPerClient()) + bytesAvailable = bytesLeft(); + else + bytesAvailable = min(s->bytesLeft(), bandwidthPerClient()); + + if (0 != bytesAvailable) + d->totalOutput += s->write(bytesAvailable); + } + + d->writeTimer.start(1000 / SamplesPerSecond, true); + } + + void + WebServer::slotCheckOutput() + { + emit(connectionCount(d->serverList.count())); + ulong lastOutput = d->totalOutput - d->lastTotalOutput; + emit(wholeServerOutput(ulong(lastOutput * SamplesPerSecond))); + d->lastTotalOutput = d->totalOutput; + } + + void + WebServer::slotClearBacklog() + { +// kpfDebug << "WebServer::slotClearBacklog" << endl; + + if (!d->backlog.isEmpty()) + { + uint currentBacklogCount = d->backlog.count(); + + for (uint i = 0; i < currentBacklogCount; i++) + { + int fd = d->backlog.first(); + + if (handleConnection(fd)) + { + kpfDebug + << "Ah, we can now handle this connection. Removing from backlog." + << endl; + + d->backlog.remove(d->backlog.begin()); + } + else + { +// kpfDebug +// << "Still can't handle this connection. Leaving in backlog" +// << endl; + + break; + } + } + } + + if (!d->backlog.isEmpty()) + { + d->backlogTimer.start(10, true); + } + } + + uint + WebServer::connectionCount() + { + return d->serverList.count(); + } + + void + WebServer::pause(bool b) + { + if(b == d->paused) return; + + d->paused = b; + if (b) d->service->stop(); + else d->service->publishAsync(); //published() should be already connected + emit pauseChange(d->paused); + saveConfig(); + } + + bool + WebServer::paused() + { + return d->paused; + } + QString + WebServer::serverName() + { + return d->serverName; + } + void + WebServer::setServerName(const QString& serverName) + { + d->serverName=serverName; + } + + bool + WebServer::portContention() + { + return d->portContention; + } + + void + WebServer::set + ( + uint listenPort, + ulong bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString& serverName + ) + { + d->listenPort = listenPort; + d->bandwidthLimit = bandwidthLimit; + d->connectionLimit = connectionLimit; + d->followSymlinks = followSymlinks; + d->serverName = serverName; + + saveConfig(); + } + + void + WebServer::loadConfig() + { + kpfDebug << "WebServer(" << d->root << "): Loading configuration" << endl; + KConfig c(Config::name()); + + c.setGroup(Config::key(Config::GroupPrefix) + d->root); + + d->listenPort = + c.readUnsignedNumEntry + (Config::key(Config::ListenPort), d->listenPort); + + d->bandwidthLimit = + c.readUnsignedNumEntry + (Config::key(Config::BandwidthLimit), d->bandwidthLimit); + + d->connectionLimit = + c.readUnsignedNumEntry + (Config::key(Config::ConnectionLimit), d->connectionLimit); + + d->followSymlinks = + c.readBoolEntry + (Config::key(Config::FollowSymlinks), d->followSymlinks); + + d->customErrorMessages = + c.readBoolEntry + (Config::key(Config::CustomErrors), d->customErrorMessages); + + d->paused = + c.readBoolEntry + (Config::key(Config::Paused), d->paused); + + d->serverName = + c.readEntry + (Config::key(Config::ServerName), d->serverName); + + } + + void + WebServer::saveConfig() + { + kpfDebug << "WebServer(" << d->root << "): Saving configuration" << endl; + KConfig c(Config::name()); + + c.setGroup(Config::key(Config::GroupPrefix) + d->root); + + c.writeEntry(Config::key(Config::ListenPort), d->listenPort); + c.writeEntry(Config::key(Config::BandwidthLimit), d->bandwidthLimit); + c.writeEntry(Config::key(Config::ConnectionLimit), d->connectionLimit); + c.writeEntry(Config::key(Config::FollowSymlinks), d->followSymlinks); + c.writeEntry(Config::key(Config::CustomErrors), d->customErrorMessages); + c.writeEntry(Config::key(Config::Paused), d->paused); + c.writeEntry(Config::key(Config::ServerName), d->serverName); + + c.sync(); + } + +} // End namespace KPF + +#include "WebServer.moc" +// vim:ts=2:sw=2:tw=78:et |