From 998a47b099b2554198bd28d6461ed8c023d969ea Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Latour Date: Wed, 16 Apr 2014 01:29:52 -0300 Subject: [PATCH] Replaced GCDWebServer subclassing with explicit options --- GCDWebServer/Core/GCDWebServer.h | 17 ++--- GCDWebServer/Core/GCDWebServer.m | 83 ++++++++++++---------- GCDWebServer/Core/GCDWebServerConnection.m | 4 +- GCDWebServer/Core/GCDWebServerPrivate.h | 2 + 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h index 90c038f..bc3e184 100644 --- a/GCDWebServer/Core/GCDWebServer.h +++ b/GCDWebServer/Core/GCDWebServer.h @@ -42,6 +42,14 @@ typedef NS_ENUM(int, GCDWebServerLogLevel) { typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request); +extern NSString* const GCDWebServerOption_Port; // NSNumber / NSUInteger (default is 0 i.e. use a random port) +extern NSString* const GCDWebServerOption_BonjourName; // NSString (default is empty string i.e. use computer name) +extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / NSUInteger (default is 16) +extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name) +extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class) +extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES) +extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 - set to 0.0 to disable) + @class GCDWebServer; // These methods are always called on main thread @@ -65,17 +73,10 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r - (BOOL)start; // Default is port 8080 (OS X & iOS Simulator) or 80 (iOS) and computer / device name for Bonjour - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name +- (BOOL)startWithOptions:(NSDictionary*)options; - (void)stop; @end -@interface GCDWebServer (Subclassing) -+ (NSUInteger)maxPendingConnections; // Default is 16 -+ (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) @property(nonatomic, readonly) NSURL* serverURL; // Only non-nil if server is running @property(nonatomic, readonly) NSURL* bonjourServerURL; // Only non-nil if server is running and Bonjour registration is active diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index 0abb881..13f62f9 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -50,6 +50,10 @@ BOOL _connected; CFRunLoopTimerRef _connectedTimer; + NSString* _serverName; + Class _connectionClass; + BOOL _mapHEADToGET; + CFTimeInterval _disconnectDelay; NSUInteger _port; dispatch_source_t _source; CFNetServiceRef _service; @@ -66,6 +70,14 @@ } @end +NSString* const GCDWebServerOption_Port = @"Port"; +NSString* const GCDWebServerOption_BonjourName = @"BonjourName"; +NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections"; +NSString* const GCDWebServerOption_ServerName = @"ServerName"; +NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass"; +NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; +NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; + #ifndef __GCDWEBSERVER_LOGGING_HEADER__ #ifdef NDEBUG GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info; @@ -124,7 +136,7 @@ static void _SignalHandler(int signal) { @implementation GCDWebServer -@synthesize delegate=_delegate, handlers=_handlers, port=_port, connected=_connected; +@synthesize delegate=_delegate, handlers=_handlers, port=_port, connected=_connected, serverName=_serverName, shouldAutomaticallyMapHEADToGET=_mapHEADToGET; #ifndef __GCDWEBSERVER_LOGGING_HEADER__ @@ -152,10 +164,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { _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); - } + _connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context); + CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes); } return self; } @@ -169,10 +179,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { [self stop]; } - if (_connectedTimer) { - CFRunLoopTimerInvalidate(_connectedTimer); - CFRelease(_connectedTimer); - } + CFRunLoopTimerInvalidate(_connectedTimer); + CFRelease(_connectedTimer); ARC_RELEASE(_handlers); ARC_DISPATCH_RELEASE(_syncQueue); @@ -195,7 +203,7 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { DCHECK(_activeConnections >= 0); if (_activeConnections == 0) { dispatch_async(dispatch_get_main_queue(), ^{ - if (_connectedTimer) { + if (_disconnectDelay > 0.0) { CFRunLoopTimerSetNextFireDate(_connectedTimer, HUGE_VAL); } if (_connected == NO) { @@ -224,8 +232,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { _activeConnections -= 1; if (_activeConnections == 0) { dispatch_async(dispatch_get_main_queue(), ^{ - if (_connectedTimer) { - CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + [[self class] connectedStateCoalescingInterval]); + if (_disconnectDelay > 0.0) { + CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay); } else { [self _didDisconnect]; } @@ -267,7 +275,22 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er } - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name { + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port]; + [options setValue:name forKey:GCDWebServerOption_BonjourName]; + return [self startWithOptions:options]; +} + +static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) { + id value = [options objectForKey:key]; + return value ? value : defaultValue; +} + +- (BOOL)startWithOptions:(NSDictionary*)options { DCHECK(_source == NULL); + NSUInteger port = [_GetOption(options, GCDWebServerOption_Port, [NSNumber numberWithUnsignedInteger:0]) unsignedIntegerValue]; + NSString* name = _GetOption(options, GCDWebServerOption_BonjourName, @""); + NSUInteger maxPendingConnections = [_GetOption(options, GCDWebServerOption_MaxPendingConnections, [NSNumber numberWithUnsignedInteger:16]) unsignedIntegerValue]; int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (listeningSocket > 0) { int yes = 1; @@ -280,8 +303,12 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er addr4.sin_port = htons(port); addr4.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) { - if (listen(listeningSocket, (int)[[self class] maxPendingConnections]) == 0) { + if (listen(listeningSocket, (int)maxPendingConnections) == 0) { LOG_DEBUG(@"Did open listening socket %i", listeningSocket); + _serverName = [_GetOption(options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; + _connectionClass = _GetOption(options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); + _mapHEADToGET = [_GetOption(options, GCDWebServerOption_AutomaticallyMapHEADToGET, [NSNumber numberWithBool:YES]) boolValue]; + _disconnectDelay = [_GetOption(options, GCDWebServerOption_ConnectedStateCoalescingInterval, [NSNumber numberWithDouble:1.0]) doubleValue]; _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue); dispatch_source_set_cancel_handler(_source, ^{ @@ -316,8 +343,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er int noSigPipe = 1; setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE - Class connectionClass = [[self class] connectionClass]; - GCDWebServerConnection* connection = [[connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened + GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened #if __has_feature(objc_arc) [connection self]; // Prevent compiler from complaining about unused variable / useless statement #else @@ -396,6 +422,9 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er _source = NULL; _port = 0; + ARC_RELEASE(_serverName); + _serverName = nil; + LOG_INFO(@"%@ stopped", [self class]); if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -407,30 +436,6 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er @end -@implementation GCDWebServer (Subclassing) - -+ (NSUInteger)maxPendingConnections { - return 16; -} - -+ (Class)connectionClass { - return [GCDWebServerConnection class]; -} - -+ (NSString*)serverName { - return NSStringFromClass(self); -} - -+ (BOOL)shouldAutomaticallyMapHEADToGET { - return YES; -} - -+ (NSTimeInterval)connectedStateCoalescingInterval { - return 1.0; -} - -@end - @implementation GCDWebServer (Extensions) - (NSURL*)serverURL { diff --git a/GCDWebServer/Core/GCDWebServerConnection.m b/GCDWebServer/Core/GCDWebServerConnection.m index 2b3b445..149e026 100644 --- a/GCDWebServer/Core/GCDWebServerConnection.m +++ b/GCDWebServer/Core/GCDWebServerConnection.m @@ -363,7 +363,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { _statusCode = statusCode; _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close")); - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)[[_server class] serverName]); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)_server.serverName); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date])); } @@ -509,7 +509,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { if (extraData) { NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase - if ([[_server class] shouldAutomaticallyMapHEADToGET] && [requestMethod isEqualToString:@"HEAD"]) { + if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) { requestMethod = @"GET"; _virtualHEAD = YES; } diff --git a/GCDWebServer/Core/GCDWebServerPrivate.h b/GCDWebServer/Core/GCDWebServerPrivate.h index 0ee19e4..1f76a94 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; +@property(nonatomic, readonly) NSString* serverName; +@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; - (void)willStartConnection:(GCDWebServerConnection*)connection; - (void)didEndConnection:(GCDWebServerConnection*)connection; @end