mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-03-05 00:00:06 +08:00
Added support for background mode on iOS
This commit is contained in:
@@ -49,6 +49,9 @@ extern NSString* const GCDWebServerOption_ServerName; // NSString (default is s
|
||||
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 seconds - set to <=0.0 to disable coaslescing of -webServerDidConnect: / -webServerDidDisconnect:)
|
||||
#if TARGET_OS_IPHONE
|
||||
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; // NSNumber / BOOL (default is YES)
|
||||
#endif
|
||||
|
||||
@class GCDWebServer;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user