diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h index c48639d..90c038f 100644 --- a/GCDWebServer/Core/GCDWebServer.h +++ b/GCDWebServer/Core/GCDWebServer.h @@ -48,6 +48,8 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r @protocol GCDWebServerDelegate @optional - (void)webServerDidStart:(GCDWebServer*)server; +- (void)webServerDidConnect:(GCDWebServer*)server; +- (void)webServerDidDisconnect:(GCDWebServer*)server; - (void)webServerDidStop:(GCDWebServer*)server; @end @@ -56,6 +58,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r @property(nonatomic, readonly, getter=isRunning) BOOL running; @property(nonatomic, readonly) NSUInteger port; @property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active +@property(nonatomic, readonly, getter=isConnected) BOOL connected; - (instancetype)init; - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock; - (void)removeAllHandlers; @@ -70,6 +73,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r + (Class)connectionClass; // Default is GCDWebServerConnection + (NSString*)serverName; // Default is class name + (BOOL)shouldAutomaticallyMapHEADToGET; // Default is YES which means HEAD requests are mapped to GET requests with the response body being discarded ++ (NSTimeInterval)connectedStateCoalescingInterval; // Allows coalescing of fast sequences of -webServerDidConnect: / -webServerDidDisconnect: - Default is 1.0 seconds (set to 0.0 to disable) @end @interface GCDWebServer (Extensions) diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index 402f340..0abb881 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -44,7 +44,11 @@ @interface GCDWebServer () { @private id __unsafe_unretained _delegate; + dispatch_queue_t _syncQueue; NSMutableArray* _handlers; + NSInteger _activeConnections; // Accessed only with _syncQueue + BOOL _connected; + CFRunLoopTimerRef _connectedTimer; NSUInteger _port; dispatch_source_t _source; @@ -120,7 +124,7 @@ static void _SignalHandler(int signal) { @implementation GCDWebServer -@synthesize delegate=_delegate, handlers=_handlers, port=_port; +@synthesize delegate=_delegate, handlers=_handlers, port=_port, connected=_connected; #ifndef __GCDWEBSERVER_LOGGING_HEADER__ @@ -137,24 +141,99 @@ static void _SignalHandler(int signal) { GCDWebServerInitializeFunctions(); } +static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { + @autoreleasepool { + [(ARC_BRIDGE GCDWebServer*)info _didDisconnect]; + } +} + - (instancetype)init { if ((self = [super init])) { + _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); _handlers = [[NSMutableArray alloc] init]; + CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; + if ([[self class] connectedStateCoalescingInterval] > 0.0) { + _connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context); + CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes); + } } return self; } - (void)dealloc { + DCHECK(_connected == NO); + DCHECK(_activeConnections == 0); + _delegate = nil; if (_source) { [self stop]; } + if (_connectedTimer) { + CFRunLoopTimerInvalidate(_connectedTimer); + CFRelease(_connectedTimer); + } ARC_RELEASE(_handlers); + ARC_DISPATCH_RELEASE(_syncQueue); ARC_DEALLOC(super); } +- (void)_didConnect { + DCHECK(_connected == NO); + _connected = YES; + LOG_DEBUG(@"Did connect"); + if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { + [_delegate webServerDidConnect:self]; + } +} + +// Called from any thread +- (void)willStartConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + + DCHECK(_activeConnections >= 0); + if (_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (_connectedTimer) { + CFRunLoopTimerSetNextFireDate(_connectedTimer, HUGE_VAL); + } + if (_connected == NO) { + [self _didConnect]; + } + }); + } + _activeConnections += 1; + + }); +} + +- (void)_didDisconnect { + DCHECK(_connected == YES); + _connected = NO; + LOG_DEBUG(@"Did disconnect"); + if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) { + [_delegate webServerDidDisconnect:self]; + } +} + +// Called from any thread +- (void)didEndConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + DCHECK(_activeConnections > 0); + _activeConnections -= 1; + if (_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (_connectedTimer) { + CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + [[self class] connectedStateCoalescingInterval]); + } else { + [self _didDisconnect]; + } + }); + } + }); +} + - (NSString*)bonjourName { CFStringRef name = _service ? CFNetServiceGetName(_service) : NULL; return name && CFStringGetLength(name) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; @@ -346,6 +425,10 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er return YES; } ++ (NSTimeInterval)connectedStateCoalescingInterval { + return 1.0; +} + @end @implementation GCDWebServer (Extensions) diff --git a/GCDWebServer/Core/GCDWebServerConnection.m b/GCDWebServer/Core/GCDWebServerConnection.m index 7a69c28..2b3b445 100644 --- a/GCDWebServer/Core/GCDWebServerConnection.m +++ b/GCDWebServer/Core/GCDWebServerConnection.m @@ -584,6 +584,9 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { _localAddress = ARC_RETAIN(localAddress); _remoteAddress = ARC_RETAIN(remoteAddress); _socket = socket; + LOG_DEBUG(@"Did open connection on socket %i", _socket); + + [_server willStartConnection:self]; if (![self open]) { close(_socket); @@ -592,7 +595,6 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { } _opened = YES; - LOG_DEBUG(@"Did open connection on socket %i", _socket); [self _readRequestHeaders]; } return self; @@ -620,10 +622,6 @@ static NSString* _StringFromAddressData(NSData* data) { } - (void)dealloc { - if (_opened) { - [self close]; - } - int result = close(_socket); if (result != 0) { LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno); @@ -631,6 +629,11 @@ static NSString* _StringFromAddressData(NSData* data) { LOG_DEBUG(@"Did close connection on socket %i", _socket); } + if (_opened) { + [self close]; + } + + [_server didEndConnection:self]; ARC_RELEASE(_server); ARC_RELEASE(_localAddress); ARC_RELEASE(_remoteAddress); @@ -783,6 +786,7 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET unlink([_responsePath fileSystemRepresentation]); } #endif + if (_request) { LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); } else { diff --git a/GCDWebServer/Core/GCDWebServerPrivate.h b/GCDWebServer/Core/GCDWebServerPrivate.h index 0661644..0ee19e4 100644 --- a/GCDWebServer/Core/GCDWebServerPrivate.h +++ b/GCDWebServer/Core/GCDWebServerPrivate.h @@ -128,6 +128,8 @@ extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); @interface GCDWebServer () @property(nonatomic, readonly) NSArray* handlers; +- (void)willStartConnection:(GCDWebServerConnection*)connection; +- (void)didEndConnection:(GCDWebServerConnection*)connection; @end @interface GCDWebServerHandler : NSObject diff --git a/Mac/main.m b/Mac/main.m index 8557121..23e07e6 100644 --- a/Mac/main.m +++ b/Mac/main.m @@ -52,6 +52,77 @@ typedef enum { kMode_StreamingResponse } Mode; +@interface Delegate : NSObject +@end + +@implementation Delegate + +- (void)_logDelegateCall:(SEL)selector { + fprintf(stdout, "\n", [NSStringFromSelector(selector) UTF8String]); +} + +- (void)webServerDidStart:(GCDWebServer*)server { + [self _logDelegateCall:_cmd]; +} + +- (void)webServerDidConnect:(GCDWebServer*)server { + [self _logDelegateCall:_cmd]; +} + +- (void)webServerDidDisconnect:(GCDWebServer*)server { + [self _logDelegateCall:_cmd]; +} + +- (void)webServerDidStop:(GCDWebServer*)server { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didDownloadFileAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didUploadFileAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didDeleteItemAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)davServer:(GCDWebDAVServer*)server didCreateDirectoryAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)webUploader:(GCDWebUploader*)uploader didDownloadFileAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath { + [self _logDelegateCall:_cmd]; +} + +- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path { + [self _logDelegateCall:_cmd]; +} + +@end + int main(int argc, const char* argv[]) { int result = -1; @autoreleasepool { @@ -199,6 +270,8 @@ int main(int argc, const char* argv[]) { #endif if (webServer) { + Delegate* delegate = [[Delegate alloc] init]; + webServer.delegate = delegate; if (testDirectory) { fprintf(stdout, "\n\n", [testDirectory UTF8String]); result = (int)[webServer runTestsInDirectory:testDirectory withPort:8080]; @@ -214,6 +287,7 @@ int main(int argc, const char* argv[]) { } #if !__has_feature(objc_arc) [webServer release]; + [delegate release]; #endif } }