|
@@ -26,8 +26,10 @@
|
|
|
*/
|
|
|
|
|
|
#import <TargetConditionals.h>
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+#import <UIKit/UIKit.h>
|
|
|
+#else
|
|
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
|
-#if !TARGET_OS_IPHONE
|
|
|
#import <AppKit/AppKit.h>
|
|
|
#endif
|
|
|
#endif
|
|
@@ -50,6 +52,7 @@
|
|
|
BOOL _connected;
|
|
|
CFRunLoopTimerRef _connectedTimer;
|
|
|
|
|
|
+ NSDictionary* _options;
|
|
|
NSString* _serverName;
|
|
|
Class _connectionClass;
|
|
|
BOOL _mapHEADToGET;
|
|
@@ -57,6 +60,10 @@
|
|
|
NSUInteger _port;
|
|
|
dispatch_source_t _source;
|
|
|
CFNetServiceRef _service;
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ BOOL _suspendInBackground;
|
|
|
+ UIBackgroundTaskIdentifier _backgroundTask;
|
|
|
+#endif
|
|
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
|
BOOL _recording;
|
|
|
#endif
|
|
@@ -77,6 +84,9 @@ NSString* const GCDWebServerOption_ServerName = @"ServerName";
|
|
|
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
|
|
|
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
|
|
|
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
|
|
|
+#endif
|
|
|
|
|
|
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
|
|
#ifdef NDEBUG
|
|
@@ -166,6 +176,9 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
|
|
CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
|
|
_connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context);
|
|
|
CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes);
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ _backgroundTask = UIBackgroundTaskInvalid;
|
|
|
+#endif
|
|
|
}
|
|
|
return self;
|
|
|
}
|
|
@@ -175,7 +188,7 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
|
|
DCHECK(_activeConnections == 0);
|
|
|
|
|
|
_delegate = nil;
|
|
|
- if (_source) {
|
|
|
+ if (_options) {
|
|
|
[self stop];
|
|
|
}
|
|
|
|
|
@@ -187,16 +200,42 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
|
|
ARC_DEALLOC(super);
|
|
|
}
|
|
|
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+
|
|
|
+// Always called on main thread
|
|
|
+- (void)_startBackgroundTask {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
+ if (_backgroundTask == UIBackgroundTaskInvalid) {
|
|
|
+ LOG_DEBUG(@"Did start background task");
|
|
|
+ _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
|
|
+
|
|
|
+ LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
|
|
+ [self _endBackgroundTask];
|
|
|
+
|
|
|
+ }];
|
|
|
+ } else {
|
|
|
+ DNOT_REACHED();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+// Always called on main thread
|
|
|
- (void)_didConnect {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
DCHECK(_connected == NO);
|
|
|
_connected = YES;
|
|
|
LOG_DEBUG(@"Did connect");
|
|
|
+
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ [self _startBackgroundTask];
|
|
|
+#endif
|
|
|
+
|
|
|
if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
|
|
|
[_delegate webServerDidConnect:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Called from any thread
|
|
|
- (void)willStartConnection:(GCDWebServerConnection*)connection {
|
|
|
dispatch_sync(_syncQueue, ^{
|
|
|
|
|
@@ -216,16 +255,41 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+
|
|
|
+// Always called on main thread
|
|
|
+- (void)_endBackgroundTask {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
+ if (_backgroundTask != UIBackgroundTaskInvalid) {
|
|
|
+ if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source) {
|
|
|
+ [self _stop];
|
|
|
+ }
|
|
|
+ [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
|
|
|
+ _backgroundTask = UIBackgroundTaskInvalid;
|
|
|
+ LOG_DEBUG(@"Did end background task");
|
|
|
+ } else {
|
|
|
+ DNOT_REACHED();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+// Always called on main thread
|
|
|
- (void)_didDisconnect {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
DCHECK(_connected == YES);
|
|
|
_connected = NO;
|
|
|
LOG_DEBUG(@"Did disconnect");
|
|
|
+
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ [self _endBackgroundTask];
|
|
|
+#endif
|
|
|
+
|
|
|
if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) {
|
|
|
[_delegate webServerDidDisconnect:self];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Called from any thread
|
|
|
- (void)didEndConnection:(GCDWebServerConnection*)connection {
|
|
|
dispatch_sync(_syncQueue, ^{
|
|
|
DCHECK(_activeConnections > 0);
|
|
@@ -248,21 +312,17 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
|
|
}
|
|
|
|
|
|
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
|
|
|
- DCHECK(_source == NULL);
|
|
|
+ DCHECK(_options == nil);
|
|
|
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
|
|
|
[_handlers insertObject:handler atIndex:0];
|
|
|
ARC_RELEASE(handler);
|
|
|
}
|
|
|
|
|
|
- (void)removeAllHandlers {
|
|
|
- DCHECK(_source == NULL);
|
|
|
+ DCHECK(_options == nil);
|
|
|
[_handlers removeAllObjects];
|
|
|
}
|
|
|
|
|
|
-- (BOOL)start {
|
|
|
- return [self startWithPort:kDefaultPort bonjourName:@""];
|
|
|
-}
|
|
|
-
|
|
|
static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
|
|
|
@autoreleasepool {
|
|
|
if (error->error) {
|
|
@@ -274,23 +334,16 @@ 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 {
|
|
|
+- (BOOL)_start {
|
|
|
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];
|
|
|
+ NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
|
|
+ NSString* name = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
|
|
|
+ NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
|
|
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
if (listeningSocket > 0) {
|
|
|
int yes = 1;
|
|
@@ -305,10 +358,10 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|
|
if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 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];
|
|
|
+ _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
|
|
+ _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
|
|
+ _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
|
|
+ _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
|
|
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
|
|
|
dispatch_source_set_cancel_handler(_source, ^{
|
|
|
|
|
@@ -403,34 +456,109 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|
|
return (_source ? YES : NO);
|
|
|
}
|
|
|
|
|
|
+- (void)_stop {
|
|
|
+ DCHECK(_source != NULL);
|
|
|
+
|
|
|
+ if (_service) {
|
|
|
+ CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
|
|
+ CFNetServiceSetClient(_service, NULL, NULL);
|
|
|
+ CFRelease(_service);
|
|
|
+ _service = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ dispatch_source_cancel(_source); // This will close the socket
|
|
|
+ ARC_DISPATCH_RELEASE(_source);
|
|
|
+ _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(), ^{
|
|
|
+ [_delegate webServerDidStop:self];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)start {
|
|
|
+ return [self startWithPort:kDefaultPort bonjourName:@""];
|
|
|
+}
|
|
|
+
|
|
|
+- (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];
|
|
|
+}
|
|
|
+
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+
|
|
|
+- (void)_didEnterBackground:(NSNotification*)notification {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
+ LOG_DEBUG(@"Did enter background");
|
|
|
+ if ((_backgroundTask == UIBackgroundTaskInvalid) && _source) {
|
|
|
+ [self _stop];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)_willEnterForeground:(NSNotification*)notification {
|
|
|
+ DCHECK([NSThread isMainThread]);
|
|
|
+ LOG_DEBUG(@"Will enter foreground");
|
|
|
+ if (!_source) {
|
|
|
+ [self _start]; // TODO: There's probably nothing we can do on failure
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+- (BOOL)startWithOptions:(NSDictionary*)options {
|
|
|
+ if (_options == nil) {
|
|
|
+ _options = [options copy];
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ _suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
|
|
|
+ if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start])
|
|
|
+#else
|
|
|
+ if (![self _start])
|
|
|
+#endif
|
|
|
+ {
|
|
|
+ ARC_RELEASE(_options);
|
|
|
+ _options = nil;
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ if (_suspendInBackground) {
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ return YES;
|
|
|
+ } else {
|
|
|
+ DNOT_REACHED();
|
|
|
+ }
|
|
|
+ return NO;
|
|
|
+}
|
|
|
+
|
|
|
- (BOOL)isRunning {
|
|
|
return (_source ? YES : NO);
|
|
|
}
|
|
|
|
|
|
- (void)stop {
|
|
|
- DCHECK(_source != NULL);
|
|
|
- if (_source) {
|
|
|
- if (_service) {
|
|
|
- CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
|
|
- CFNetServiceSetClient(_service, NULL, NULL);
|
|
|
- CFRelease(_service);
|
|
|
- _service = NULL;
|
|
|
+ if (_options) {
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
+ if (_suspendInBackground) {
|
|
|
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
|
|
|
}
|
|
|
-
|
|
|
- dispatch_source_cancel(_source); // This will close the socket
|
|
|
- ARC_DISPATCH_RELEASE(_source);
|
|
|
- _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(), ^{
|
|
|
- [_delegate webServerDidStop:self];
|
|
|
- });
|
|
|
+#endif
|
|
|
+ if (_source) {
|
|
|
+ [self _stop];
|
|
|
}
|
|
|
+ ARC_RELEASE(_options);
|
|
|
+ _options = nil;
|
|
|
+ } else {
|
|
|
+ DNOT_REACHED();
|
|
|
}
|
|
|
}
|
|
|
|