diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h index 96dc3cf..9df1d6a 100644 --- a/GCDWebServer/Core/GCDWebServer.h +++ b/GCDWebServer/Core/GCDWebServer.h @@ -77,16 +77,18 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r extern NSString* const GCDWebServerOption_Port; /** - * The Bonjour name used by the GCDWebServer (NSString). + * The Bonjour name used by the GCDWebServer (NSString). If set to an empty string, + * the name will automatically take the value of the GCDWebServerOption_ServerName + * option. If this option is set to nil, Bonjour will be disabled. * - * The default value is an empty string i.e. use the computer / device name. + * The default value is an empty string. */ extern NSString* const GCDWebServerOption_BonjourName; /** * The Bonjour service type used by the GCDWebServer (NSString). * - * The default value is "_http._tcp", standard HTTP web server. + * The default value is "_http._tcp", the service type for HTTP web servers. */ extern NSString* const GCDWebServerOption_BonjourType; @@ -336,12 +338,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess; * * @warning This property is only valid if the server is running and Bonjour * registration has successfully completed, which can take up to a few seconds. + * Also be aware this property will not automatically update if the Bonjour hostname + * has been dynamically changed after the server started running (this should be rare). */ @property(nonatomic, readonly) NSURL* bonjourServerURL; /** * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) - * using the computer / device name for as the Bonjour name. + * using the default Bonjour name. * * Returns NO if the server failed to start. */ @@ -350,7 +354,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess; /** * Starts the server on a given port and with a specific Bonjour name. * Pass a nil Bonjour name to disable Bonjour entirely or an empty string to - * use the computer / device name. + * use the default name. * * Returns NO if the server failed to start. */ diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index 5ae8f9e..9c1afa8 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -160,7 +160,8 @@ static void _ExecuteMainThreadRunLoopSources() { CFTimeInterval _disconnectDelay; NSUInteger _port; dispatch_source_t _source; - CFNetServiceRef _service; + CFNetServiceRef _registrationService; + CFNetServiceRef _resolutionService; #if TARGET_OS_IPHONE BOOL _suspendInBackground; UIBackgroundTaskIdentifier _backgroundTask; @@ -339,12 +340,12 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) { } - (NSString*)bonjourName { - CFStringRef name = _service ? CFNetServiceGetName(_service) : NULL; + CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL; return name && CFStringGetLength(name) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; } - (NSString*)bonjourType { - CFStringRef type = _service ? CFNetServiceGetType(_service) : NULL; + CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL; return type && CFStringGetLength(type) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil; } @@ -360,11 +361,26 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) { [_handlers removeAllObjects]; } -static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { +static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { DCHECK([NSThread isMainThread]); @autoreleasepool { if (error->error) { - LOG_ERROR(@"Bonjour error %i (domain %i)", (int)error->error, (int)error->domain); + LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain); + } else { + GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info; + LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]); + CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL); + } + } +} + +static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { + DCHECK([NSThread isMainThread]); + @autoreleasepool { + if (error->error) { + if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) { + LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain); + } } else { GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info; LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL); @@ -392,8 +408,9 @@ static inline NSString* _EncodeBase64(NSString* string) { - (BOOL)_start:(NSError**)error { DCHECK(_source == NULL); + NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; - NSString* name = _GetOption(_options, GCDWebServerOption_BonjourName, @""); + NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @""); NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); @@ -491,14 +508,21 @@ static inline NSString* _EncodeBase64(NSString* string) { _port = port; } - if (name) { - _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)name, (SInt32)_port); - if (_service) { + if (bonjourName) { + _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); + if (_registrationService) { CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; - CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context); - CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); + + CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); + CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); CFStreamError streamError = {0}; - CFNetServiceRegisterWithOptions(_service, 0, &streamError); + CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); + + _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); + if (_resolutionService) { + CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); + CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + } } else { LOG_ERROR(@"Failed creating CFNetService"); } @@ -537,11 +561,19 @@ static inline NSString* _EncodeBase64(NSString* string) { - (void)_stop { DCHECK(_source != NULL); - if (_service) { - CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); - CFNetServiceSetClient(_service, NULL, NULL); - CFRelease(_service); - _service = NULL; + if (_registrationService) { + if (_resolutionService) { + CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_resolutionService, NULL, NULL); + CFNetServiceCancel(_resolutionService); + CFRelease(_resolutionService); + _resolutionService = NULL; + } + CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_registrationService, NULL, NULL); + CFNetServiceCancel(_registrationService); + CFRelease(_registrationService); + _registrationService = NULL; } dispatch_source_cancel(_source); @@ -663,13 +695,14 @@ static inline NSString* _EncodeBase64(NSString* string) { } - (NSURL*)bonjourServerURL { - if (_source && _service) { - CFStringRef name = CFNetServiceGetName(_service); - if (name && CFStringGetLength(name)) { + if (_source && _resolutionService) { + NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService); + if (name.length) { + name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain if (_port != 80) { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@.local:%i/", name, (int)_port]]; + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]]; } else { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@.local/", name]]; + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]]; } } }