#85 Added support for IPv6
This commit is contained in:
@ -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,27 +410,136 @@ 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));
|
||||||
|
|
||||||
|
if (bind(listeningSocket, address, length) == 0) {
|
||||||
|
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
|
||||||
|
GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
||||||
|
return listeningSocket;
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
*error = GCDWebServerMakePosixError(errno);
|
||||||
|
}
|
||||||
|
GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||||
|
close(listeningSocket);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
*error = GCDWebServerMakePosixError(errno);
|
||||||
|
}
|
||||||
|
GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||||
|
close(listeningSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
*error = GCDWebServerMakePosixError(errno);
|
||||||
|
}
|
||||||
|
GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||||
|
}
|
||||||
|
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;
|
struct sockaddr_in addr4;
|
||||||
bzero(&addr4, sizeof(addr4));
|
bzero(&addr4, sizeof(addr4));
|
||||||
addr4.sin_len = sizeof(addr4);
|
addr4.sin_len = sizeof(addr4);
|
||||||
addr4.sin_family = AF_INET;
|
addr4.sin_family = AF_INET;
|
||||||
addr4.sin_port = htons(port);
|
addr4.sin_port = htons(port);
|
||||||
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) {
|
int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
|
||||||
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
|
if (listeningSocket4 <= 0) {
|
||||||
GWS_LOG_DEBUG(@"Did open listening socket %i", listeningSocket);
|
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];
|
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||||
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||||
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||||
@ -450,67 +560,13 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
_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 {
|
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
|
||||||
int result = close(listeningSocket);
|
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
|
||||||
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;
|
_port = port;
|
||||||
}
|
|
||||||
|
|
||||||
|
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
|
||||||
|
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
|
||||||
if (bonjourName) {
|
if (bonjourName) {
|
||||||
_registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
|
_registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
|
||||||
if (_registrationService) {
|
if (_registrationService) {
|
||||||
@ -531,38 +587,20 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_resume(_source);
|
dispatch_resume(_source4);
|
||||||
|
dispatch_resume(_source6);
|
||||||
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
||||||
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[_delegate webServerDidStart:self];
|
[_delegate webServerDidStart:self];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (error) {
|
return YES;
|
||||||
*error = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
GWS_LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno);
|
|
||||||
close(listeningSocket);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (error) {
|
|
||||||
*error = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
GWS_LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno);
|
|
||||||
close(listeningSocket);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (error) {
|
|
||||||
*error = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
GWS_LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno);
|
|
||||||
}
|
|
||||||
return (_source ? YES : NO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user