/*
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 <tqsocket.h>
# include <tqdatetime.h>
# include <tqtimer.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 ;
TQPtrList < Server > serverList ;
TQString root ;
TQString serverName ;
TQTimer writeTimer ;
TQTimer resetOutputTimer ;
TQTimer bindTimer ;
TQTimer backlogTimer ;
ulong bandwidthLimit ;
ulong lastTotalOutput ;
ulong totalOutput ;
bool portContention ;
bool paused ;
bool followSymlinks ;
bool customErrorMessages ;
TQValueList < int > backlog ;
DNSSD : : PublicService * service ;
} ;
WebServer : : WebServer ( const TQString & root )
: DCOPObject ( TQCString ( " WebServer_ " ) + root . utf8 ( ) ) ,
TQObject ( )
{
d = new Private ;
d - > root = root ;
loadConfig ( ) ;
publish ( ) ;
connect ( & d - > bindTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotBind ( ) ) ) ;
connect ( & d - > writeTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotWrite ( ) ) ) ;
connect ( & d - > resetOutputTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotCheckOutput ( ) ) ) ;
connect ( & d - > backlogTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotClearBacklog ( ) ) ) ;
d - > bindTimer . start ( 0 , true ) ;
d - > resetOutputTimer . start ( 1000 / SamplesPerSecond , false ) ;
}
WebServer : : WebServer
(
const TQString & root ,
uint listenPort ,
uint bandwidthLimit ,
uint connectionLimit ,
bool followSymlinks ,
const TQString & serverName
)
: DCOPObject ( TQCString ( " WebServer_ " ) + root . utf8 ( ) ) ,
TQObject ( )
{
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 , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotBind ( ) ) ) ;
connect ( & d - > writeTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotWrite ( ) ) ) ;
connect ( & d - > resetOutputTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_SLOT ( slotCheckOutput ( ) ) ) ;
connect ( & d - > backlogTimer , TQT_SIGNAL ( timeout ( ) ) , TQT_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 , TQT_SIGNAL ( published ( bool ) ) , this , TQT_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 , TQT_SIGNAL ( connection ( int ) ) , TQT_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 ,
TQT_SIGNAL ( output ( Server * , ulong ) ) ,
TQT_SLOT ( slotOutput ( Server * , ulong ) )
) ;
connect ( s , TQT_SIGNAL ( finished ( Server * ) ) , TQT_SLOT ( slotFinished ( Server * ) ) ) ;
connect ( s , TQT_SIGNAL ( request ( Server * ) ) , TQT_SIGNAL ( request ( Server * ) ) ) ;
connect ( s , TQT_SIGNAL ( response ( Server * ) ) , TQT_SIGNAL ( response ( Server * ) ) ) ;
d - > serverList . append ( s ) ;
connect
( s , TQT_SIGNAL ( readyToWrite ( Server * ) ) , TQT_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 ( )
{
TQPtrListIterator < 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 ;
TQPtrListIterator < 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 TQString & serverName )
{
d - > serverName = serverName ;
}
bool
WebServer : : portContention ( )
{
return d - > portContention ;
}
void
WebServer : : set
(
uint listenPort ,
ulong bandwidthLimit ,
uint connectionLimit ,
bool followSymlinks ,
const TQString & 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