#85 Added support for IPv6

This commit is contained in:
Pierre-Olivier Latour
2014-10-14 00:45:08 -07:00
parent 4449e42601
commit 75d018a375
7 changed files with 224 additions and 174 deletions

View File

@ -155,7 +155,7 @@ static void _ExecuteMainThreadRunLoopSources() {
@private @private
id<GCDWebServerDelegate> __unsafe_unretained _delegate; id<GCDWebServerDelegate> __unsafe_unretained _delegate;
dispatch_queue_t _syncQueue; dispatch_queue_t _syncQueue;
dispatch_semaphore_t _sourceSemaphore; dispatch_group_t _sourceGroup;
NSMutableArray* _handlers; NSMutableArray* _handlers;
NSInteger _activeConnections; // Accessed through _syncQueue only NSInteger _activeConnections; // Accessed through _syncQueue only
BOOL _connected; // Accessed on main thread only BOOL _connected; // Accessed on main thread only
@ -170,7 +170,8 @@ static void _ExecuteMainThreadRunLoopSources() {
BOOL _mapHEADToGET; BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay; CFTimeInterval _disconnectDelay;
NSUInteger _port; NSUInteger _port;
dispatch_source_t _source; dispatch_source_t _source4;
dispatch_source_t _source6;
CFNetServiceRef _registrationService; CFNetServiceRef _registrationService;
CFNetServiceRef _resolutionService; CFNetServiceRef _resolutionService;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@ -196,7 +197,7 @@ static void _ExecuteMainThreadRunLoopSources() {
- (instancetype)init { - (instancetype)init {
if ((self = [super init])) { if ((self = [super init])) {
_syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
_sourceSemaphore = dispatch_semaphore_create(0); _sourceGroup = dispatch_group_create();
_handlers = [[NSMutableArray alloc] init]; _handlers = [[NSMutableArray alloc] init];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
_backgroundTask = UIBackgroundTaskInvalid; _backgroundTask = UIBackgroundTaskInvalid;
@ -212,7 +213,7 @@ static void _ExecuteMainThreadRunLoopSources() {
GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle
ARC_RELEASE(_handlers); ARC_RELEASE(_handlers);
ARC_DISPATCH_RELEASE(_sourceSemaphore); ARC_DISPATCH_RELEASE(_sourceGroup);
ARC_DISPATCH_RELEASE(_syncQueue); ARC_DISPATCH_RELEASE(_syncQueue);
ARC_DEALLOC(super); ARC_DEALLOC(super);
@ -281,7 +282,7 @@ static void _ExecuteMainThreadRunLoopSources() {
- (void)_endBackgroundTask { - (void)_endBackgroundTask {
GWS_DCHECK([NSThread isMainThread]); GWS_DCHECK([NSThread isMainThread]);
if (_backgroundTask != UIBackgroundTaskInvalid) { if (_backgroundTask != UIBackgroundTaskInvalid) {
if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source) { if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) {
[self _stop]; [self _stop];
} }
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
@ -316,7 +317,7 @@ static void _ExecuteMainThreadRunLoopSources() {
_activeConnections -= 1; _activeConnections -= 1;
if (_activeConnections == 0) { if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if ((_disconnectDelay > 0.0) && (_source != NULL)) { if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
if (_disconnectTimer) { if (_disconnectTimer) {
CFRunLoopTimerInvalidate(_disconnectTimer); CFRunLoopTimerInvalidate(_disconnectTimer);
CFRelease(_disconnectTimer); CFRelease(_disconnectTimer);
@ -409,160 +410,197 @@ static inline NSString* _EncodeBase64(NSString* string) {
return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]); return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]);
} }
- (BOOL)_start:(NSError**)error { - (int)_createListeningSocket:(BOOL)useIPv6
GWS_DCHECK(_source == NULL); localAddress:(const void*)address
length:(socklen_t)length
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; maxPendingConnections:(NSUInteger)maxPendingConnections
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @""); error:(NSError**)error {
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket > 0) { if (listeningSocket > 0) {
int yes = 1; int yes = 1;
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
struct sockaddr_in addr4; if (bind(listeningSocket, address, length) == 0) {
bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
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)maxPendingConnections) == 0) { if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
GWS_LOG_DEBUG(@"Did open listening socket %i", listeningSocket); GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; return listeningSocket;
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
}];
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
}];
}
_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, ^{
@autoreleasepool {
int result = close(listeningSocket);
if (result != 0) {
GWS_LOG_ERROR(@"Failed closing listening socket: %s (%i)", strerror(errno), errno);
} else {
GWS_LOG_DEBUG(@"Did close listening socket %i", listeningSocket);
}
}
dispatch_semaphore_signal(_sourceSemaphore);
});
dispatch_source_set_event_handler(_source, ^{
@autoreleasepool {
struct sockaddr remoteSockAddr;
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
if (socket > 0) {
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
struct sockaddr localSockAddr;
socklen_t localAddrLen = sizeof(localSockAddr);
NSData* localAddress = nil;
if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
} else {
GWS_DNOT_REACHED();
}
int noSigPipe = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
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
[connection release];
#endif
} else {
GWS_LOG_ERROR(@"Failed accepting socket: %s (%i)", strerror(errno), errno);
}
}
});
if (port == 0) {
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
if (getsockname(listeningSocket, &addr, &addrlen) == 0) {
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
_port = ntohs(sockaddr->sin_port);
} else {
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
}
} else {
_port = port;
}
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(_registrationService, _NetServiceRegisterCallBack, &context);
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError streamError = {0};
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
if (_resolutionService) {
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
} else {
GWS_LOG_ERROR(@"Failed creating CFNetService");
}
}
dispatch_resume(_source);
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[_delegate webServerDidStart:self];
});
}
} else { } else {
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
GWS_LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno); GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
close(listeningSocket); close(listeningSocket);
} }
} else { } else {
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
GWS_LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno); GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
close(listeningSocket); close(listeningSocket);
} }
} else { } else {
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
GWS_LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno); GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
} }
return (_source ? YES : NO); return -1;
}
- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
dispatch_group_enter(_sourceGroup);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
dispatch_source_set_cancel_handler(source, ^{
@autoreleasepool {
int result = close(listeningSocket);
if (result != 0) {
GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
} else {
GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
}
}
dispatch_group_leave(_sourceGroup);
});
dispatch_source_set_event_handler(source, ^{
@autoreleasepool {
struct sockaddr remoteSockAddr;
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
if (socket > 0) {
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
struct sockaddr localSockAddr;
socklen_t localAddrLen = sizeof(localSockAddr);
NSData* localAddress = nil;
if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_family == AF_INET6));
} else {
GWS_DNOT_REACHED();
}
int noSigPipe = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
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
[connection release];
#endif
} else {
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
}
}
});
return source;
}
- (BOOL)_start:(NSError**)error {
GWS_DCHECK(_source4 == NULL);
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
struct sockaddr_in addr4;
bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
if (listeningSocket4 <= 0) {
return NO;
}
if (port == 0) {
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
if (getsockname(listeningSocket4, &addr, &addrlen) == 0) {
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
port = ntohs(sockaddr->sin_port);
} else {
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
}
}
struct sockaddr_in6 addr6;
bzero(&addr6, sizeof(addr6));
addr6.sin6_len = sizeof(addr6);
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
addr6.sin6_addr = in6addr_any;
int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
if (listeningSocket6 <= 0) {
close(listeningSocket4);
return NO;
}
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
}];
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
}];
}
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
_port = port;
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
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(_registrationService, _NetServiceRegisterCallBack, &context);
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError streamError = {0};
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
if (_resolutionService) {
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
} else {
GWS_LOG_ERROR(@"Failed creating CFNetService");
}
}
dispatch_resume(_source4);
dispatch_resume(_source6);
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[_delegate webServerDidStart:self];
});
}
return YES;
} }
- (void)_stop { - (void)_stop {
GWS_DCHECK(_source != NULL); GWS_DCHECK(_source4 != NULL);
if (_registrationService) { if (_registrationService) {
if (_resolutionService) { if (_resolutionService) {
@ -579,10 +617,13 @@ static inline NSString* _EncodeBase64(NSString* string) {
_registrationService = NULL; _registrationService = NULL;
} }
dispatch_source_cancel(_source); dispatch_source_cancel(_source6);
dispatch_semaphore_wait(_sourceSemaphore, DISPATCH_TIME_FOREVER); // Wait until the cancellation handler has been called which guarantees the listening socket is closed dispatch_source_cancel(_source4);
ARC_DISPATCH_RELEASE(_source); dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed
_source = NULL; ARC_DISPATCH_RELEASE(_source6);
_source6 = NULL;
ARC_DISPATCH_RELEASE(_source4);
_source4 = NULL;
_port = 0; _port = 0;
ARC_RELEASE(_serverName); ARC_RELEASE(_serverName);
@ -616,7 +657,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)_didEnterBackground:(NSNotification*)notification { - (void)_didEnterBackground:(NSNotification*)notification {
GWS_DCHECK([NSThread isMainThread]); GWS_DCHECK([NSThread isMainThread]);
GWS_LOG_DEBUG(@"Did enter background"); GWS_LOG_DEBUG(@"Did enter background");
if ((_backgroundTask == UIBackgroundTaskInvalid) && _source) { if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) {
[self _stop]; [self _stop];
} }
} }
@ -624,7 +665,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)_willEnterForeground:(NSNotification*)notification { - (void)_willEnterForeground:(NSNotification*)notification {
GWS_DCHECK([NSThread isMainThread]); GWS_DCHECK([NSThread isMainThread]);
GWS_LOG_DEBUG(@"Will enter foreground"); GWS_LOG_DEBUG(@"Will enter foreground");
if (!_source) { if (!_source4) {
[self _start:NULL]; // TODO: There's probably nothing we can do on failure [self _start:NULL]; // TODO: There's probably nothing we can do on failure
} }
} }
@ -670,7 +711,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
} }
#endif #endif
if (_source) { if (_source4) {
[self _stop]; [self _stop];
} }
ARC_RELEASE(_options); ARC_RELEASE(_options);
@ -685,8 +726,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (Extensions) @implementation GCDWebServer (Extensions)
- (NSURL*)serverURL { - (NSURL*)serverURL {
if (_source) { if (_source4) {
NSString* ipAddress = GCDWebServerGetPrimaryIPv4Address(); NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
if (ipAddress) { if (ipAddress) {
if (_port != 80) { if (_port != 80) {
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
@ -699,7 +740,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
} }
- (NSURL*)bonjourServerURL { - (NSURL*)bonjourServerURL {
if (_source && _resolutionService) { if (_source4 && _resolutionService) {
NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService); NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService);
if (name.length) { if (name.length) {
name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain

View File

@ -48,6 +48,11 @@
*/ */
@property(nonatomic, readonly) GCDWebServer* server; @property(nonatomic, readonly) GCDWebServer* server;
/**
* Returns true if the connection is using IPv6.
*/
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;
/** /**
* Returns the address of the local peer (i.e. server) of the connection * Returns the address of the local peer (i.e. server) of the connection
* as a raw "struct sockaddr". * as a raw "struct sockaddr".
@ -56,7 +61,7 @@
/** /**
* Returns the address of the local peer (i.e. server) of the connection * Returns the address of the local peer (i.e. server) of the connection
* as a dotted string. * as a string.
*/ */
@property(nonatomic, readonly) NSString* localAddressString; @property(nonatomic, readonly) NSString* localAddressString;
@ -68,7 +73,7 @@
/** /**
* Returns the address of the remote peer (i.e. client) of the connection * Returns the address of the remote peer (i.e. client) of the connection
* as a dotted string. * as a string.
*/ */
@property(nonatomic, readonly) NSString* remoteAddressString; @property(nonatomic, readonly) NSString* remoteAddressString;

View File

@ -365,6 +365,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
} }
- (BOOL)isUsingIPv6 {
const struct sockaddr* localSockAddr = _localAddress.bytes;
return (localSockAddr->sa_family == AF_INET6);
}
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { - (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
_statusCode = statusCode; _statusCode = statusCode;
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
@ -622,25 +627,12 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
return self; return self;
} }
static NSString* _StringFromAddressData(NSData* data) {
NSString* string = nil;
const struct sockaddr* addr = data.bytes;
char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
string = [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer];
} else {
GWS_DNOT_REACHED();
}
return string;
}
- (NSString*)localAddressString { - (NSString*)localAddressString {
return _StringFromAddressData(_localAddress); return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
} }
- (NSString*)remoteAddressString { - (NSString*)remoteAddressString {
return _StringFromAddressData(_remoteAddress); return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
} }
- (void)dealloc { - (void)dealloc {

View File

@ -57,13 +57,13 @@ NSString* GCDWebServerUnescapeURLString(NSString* string);
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
/** /**
* On OS X, returns the IPv4 address as a dotted string of the primary connected * On OS X, returns the IPv4 or IPv6 address as a string of the primary
* service or nil if not available. * connected service or nil if not available.
* *
* On iOS, returns the IPv4 address as a dotted string of the WiFi interface * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* if connected or nil otherwise. * interface if connected or nil otherwise.
*/ */
NSString* GCDWebServerGetPrimaryIPv4Address(); NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/** /**
* Converts a date into a string using RFC822 formatting. * Converts a date into a string using RFC822 formatting.

View File

@ -223,7 +223,19 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
return parameters; return parameters;
} }
NSString* GCDWebServerGetPrimaryIPv4Address() { NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
NSString* string = nil;
char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
} else {
GWS_DNOT_REACHED();
}
return string;
}
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
NSString* address = nil; NSString* address = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#if !TARGET_IPHONE_SIMULATOR #if !TARGET_IPHONE_SIMULATOR
@ -233,7 +245,7 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
const char* primaryInterface = NULL; const char* primaryInterface = NULL;
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL); SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
if (store) { if (store) {
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
if (info) { if (info) {
primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
CFRelease(info); CFRelease(info);
@ -255,11 +267,8 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
{ {
continue; continue;
} }
if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) { if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
char buffer[NI_MAXHOST]; address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
address = [NSString stringWithUTF8String:buffer];
}
break; break;
} }
} }

View File

@ -26,6 +26,7 @@
*/ */
#import <os/object.h> #import <os/object.h>
#import <sys/socket.h>
/** /**
* ARC <-> MRC compatibility macros. * ARC <-> MRC compatibility macros.
@ -212,6 +213,7 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset)
extern BOOL GCDWebServerIsTextContentType(NSString* type); extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2); extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@interface GCDWebServerConnection () @interface GCDWebServerConnection ()
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; - (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;

View File

@ -24,6 +24,7 @@ Extra built-in features:
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files * [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection * [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection
* Automatically handle transitions between foreground, background and suspended modes in iOS apps * Automatically handle transitions between foreground, background and suspended modes in iOS apps
* Full support for both IPv4 and IPv6
Included extensions: Included extensions:
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser * [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser