Index: src/OFHTTPServer.h ================================================================== --- src/OFHTTPServer.h +++ src/OFHTTPServer.h @@ -21,10 +21,11 @@ # error No sockets available! #endif OF_ASSUME_NONNULL_BEGIN +@class OFArray; @class OFHTTPRequest; @class OFHTTPResponse; @class OFHTTPServer; @class OFStream; @class OFTCPSocket; @@ -97,51 +98,88 @@ OFString *_Nullable _certificateFile, *_Nullable _privateKeyFile; const char *_Nullable _privateKeyPassphrase; id _Nullable _delegate; OFString *_Nullable _name; OF_KINDOF(OFTCPSocket *) _Nullable _listeningSocket; +#ifdef OF_HAVE_THREADS + size_t _numberOfThreads, _nextThreadIndex; + OFArray *_threadPool; +#endif } /*! * @brief The host on which the HTTP server will listen. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property OF_NULLABLE_PROPERTY (copy, nonatomic) OFString *host; /*! * @brief The port on which the HTTP server will listen. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property (nonatomic) uint16_t port; /*! * @brief Whether the HTTP server uses TLS. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property (nonatomic) bool usesTLS; /*! * @brief The path to the X.509 certificate file to use for TLS. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property OF_NULLABLE_PROPERTY (copy, nonatomic) OFString *certificateFile; /*! * @brief The path to the PKCS#8 private key file to use for TLS. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property OF_NULLABLE_PROPERTY (copy, nonatomic) OFString *privateKeyFile; /*! * @brief The passphrase to decrypt the PKCS#8 private key file for TLS. * * @warning You have to ensure that this is in secure memory protected from * swapping! This is also the reason why this is not an OFString. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) const char *privateKeyPassphrase; /*! * @brief The delegate for the HTTP server. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; + +#ifdef OF_HAVE_THREADS +/*! + * @brief The number of threads the OFHTTPServer should use. + * + * If this is larger than 1 (the default), one thread will be used to accept + * incoming connections and all others will be used to handle connections. + * + * For maximum CPU utilization, set this to `[OFSystemInfo numberOfCPUs] + 1`. + * + * Setting this after @ref start has been called raises an + * @ref OFAlreadyConnectedException. + */ +@property (nonatomic) size_t numberOfThreads; +#endif /*! * @brief The server name the server presents to clients. * * Setting it to `nil` means no `Server` header will be sent, unless one is Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -19,18 +19,20 @@ #include #include #import "OFHTTPServer.h" +#import "OFArray.h" #import "OFData.h" #import "OFDate.h" #import "OFDictionary.h" #import "OFHTTPRequest.h" #import "OFHTTPResponse.h" #import "OFNumber.h" #import "OFTCPSocket.h" #import "OFTLSSocket.h" +#import "OFThread.h" #import "OFTimer.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" #import "OFInvalidArgumentException.h" @@ -102,10 +104,16 @@ } - (instancetype)initWithSocket: (OF_KINDOF(OFTCPSocket *))sock contentLength: (uintmax_t)contentLength; @end + +#ifdef OF_HAVE_THREADS +@interface OFHTTPServerThread: OFThread +- (void)stop; +@end +#endif static const char * statusCodeToString(short code) { switch (code) { @@ -715,17 +723,23 @@ { [_socket release]; _socket = nil; } @end + +#ifdef OF_HAVE_THREADS +@implementation OFHTTPServerThread +- (void)stop +{ + [[OFRunLoop currentRunLoop] stop]; + [self join]; +} +@end +#endif @implementation OFHTTPServer -@synthesize host = _host, port = _port, usesTLS = _usesTLS; -@synthesize certificateFile = _certificateFile; -@synthesize privateKeyFile = _privateKeyFile; -@synthesize privateKeyPassphrase = _privateKeyPassphrase, delegate = _delegate; -@synthesize name = _name; +@synthesize delegate = _delegate, name = _name; + (instancetype)server { return [[[self alloc] init] autorelease]; } @@ -734,25 +748,140 @@ { self = [super init]; _name = @"OFHTTPServer (ObjFW's HTTP server class " @")"; +#ifdef OF_HAVE_THREADS + _numberOfThreads = 1; +#endif return self; } - (void)dealloc { + [self stop]; + [_host release]; [_listeningSocket release]; [_name release]; [super dealloc]; } + +- (void)setHost: (OFString *)host +{ + OFString *old; + + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + old = _host; + _host = [host copy]; + [old release]; +} + +- (OFString *)host +{ + return _host; +} + +- (void)setPort: (uint16_t)port +{ + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + _port = port; +} + +- (uint16_t)port +{ + return _port; +} + +- (void)setUsesTLS: (bool)usesTLS +{ + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + _usesTLS = usesTLS; +} + +- (bool)usesTLS +{ + return _usesTLS; +} + +- (void)setCertificateFile: (OFString *)certificateFile +{ + OFString *old; + + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + old = _certificateFile; + _certificateFile = [certificateFile copy]; + [old release]; +} + +- (OFString *)certificateFile +{ + return _certificateFile; +} + +- (void)setPrivateKeyFile: (OFString *)privateKeyFile +{ + OFString *old; + + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + old = _privateKeyFile; + _privateKeyFile = [privateKeyFile copy]; + [old release]; +} + +- (OFString *)privateKeyFile +{ + return _privateKeyFile; +} + +- (void)setPrivateKeyPassphrase: (const char *)privateKeyPassphrase +{ + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + _privateKeyPassphrase = privateKeyPassphrase; +} + +- (const char *)privateKeyPassphrase +{ + return _privateKeyPassphrase; +} + +#ifdef OF_HAVE_THREADS +- (void)setNumberOfThreads: (size_t)numberOfThreadas +{ + if (numberOfThreadas == 0) + @throw [OFInvalidArgumentException exception]; + + if (_listeningSocket != nil) + @throw [OFAlreadyConnectedException exception]; + + _numberOfThreads = numberOfThreadas; +} + +- (size_t)numberOfThreads +{ + return _numberOfThreads; +} +#endif - (void)start { + void *pool = objc_autoreleasePoolPush(); + if (_host == nil) @throw [OFInvalidArgumentException exception]; if (_listeningSocket != nil) @throw [OFAlreadyConnectedException exception]; @@ -774,42 +903,87 @@ _listeningSocket = [[OFTCPSocket alloc] init]; _port = [_listeningSocket bindToHost: _host port: _port]; [_listeningSocket listen]; + +#ifdef OF_HAVE_THREADS + if (_numberOfThreads > 1) { + OFMutableArray *threads = + [OFMutableArray arrayWithCapacity: _numberOfThreads - 1]; + + for (size_t i = 1; i < _numberOfThreads; i++) { + OFHTTPServerThread *thread = + [OFHTTPServerThread thread]; + + [thread start]; + [threads addObject: thread]; + } + + [threads makeImmutable]; + _threadPool = [threads copy]; + } +#endif [(OFTCPSocket *)_listeningSocket setDelegate: self]; [_listeningSocket asyncAccept]; + + objc_autoreleasePoolPop(pool); } - (void)stop { [_listeningSocket cancelAsyncRequests]; [_listeningSocket release]; _listeningSocket = nil; + +#ifdef OF_HAVE_THREADS + for (OFHTTPServerThread *thread in _threadPool) + [thread stop]; + + [_threadPool release]; + _threadPool = nil; +#endif +} + +- (void)of_handleAcceptedSocket: (OF_KINDOF(OFTCPSocket *))acceptedSocket +{ + OFHTTPServer_Connection *connection = [[[OFHTTPServer_Connection alloc] + initWithSocket: acceptedSocket + server: self] autorelease]; + + [(OFTCPSocket *)acceptedSocket setDelegate: connection]; + [acceptedSocket asyncReadLine]; } - (bool)socket: (OF_KINDOF(OFTCPSocket *))sock didAcceptSocket: (OF_KINDOF(OFTCPSocket *))acceptedSocket exception: (id)exception { - OFHTTPServer_Connection *connection; - if (exception != nil) { if (![_delegate respondsToSelector: @selector(server:didReceiveExceptionOnListeningSocket:)]) return false; return [_delegate server: self didReceiveExceptionOnListeningSocket: exception]; } - connection = [[[OFHTTPServer_Connection alloc] - initWithSocket: acceptedSocket - server: self] autorelease]; +#ifdef OF_HAVE_THREADS + if (_numberOfThreads > 1) { + OFHTTPServerThread *thread = + [_threadPool objectAtIndex: _nextThreadIndex]; + + if (++_nextThreadIndex >= _numberOfThreads - 1) + _nextThreadIndex = 0; - [(OFTCPSocket *)acceptedSocket setDelegate: connection]; - [acceptedSocket asyncReadLine]; + [self performSelector: @selector(of_handleAcceptedSocket:) + onThread: thread + withObject: acceptedSocket + waitUntilDone: false]; + } else +#endif + [self of_handleAcceptedSocket: acceptedSocket]; return true; } @end