Browse Source

Added connected state to GCDWebServer

Pierre-Olivier Latour 11 years ago
parent
commit
2d8996b91e

+ 4 - 0
GCDWebServer/Core/GCDWebServer.h

@@ -48,6 +48,8 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
 @protocol GCDWebServerDelegate <NSObject>
 @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)

+ 84 - 1
GCDWebServer/Core/GCDWebServer.m

@@ -44,7 +44,11 @@
 @interface GCDWebServer () {
 @private
   id<GCDWebServerDelegate> __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)

+ 9 - 5
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 {

+ 2 - 0
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

+ 74 - 0
Mac/main.m

@@ -52,6 +52,77 @@ typedef enum {
   kMode_StreamingResponse
 } Mode;
 
+@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
+@end
+
+@implementation Delegate
+
+- (void)_logDelegateCall:(SEL)selector {
+  fprintf(stdout, "<DELEGATE METHOD \"%s\" CALLED>\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, "<RUNNING TESTS FROM \"%s\">\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
     }
   }