diff --git a/CGDWebServer/GCDWebServer.h b/CGDWebServer/GCDWebServer.h index abbcef14..db1d54a8 100644 --- a/CGDWebServer/GCDWebServer.h +++ b/CGDWebServer/GCDWebServer.h @@ -33,19 +33,18 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r @interface GCDWebServer : NSObject { @private - NSUInteger _port; - CFSocketRef _socket; - CFNetServiceRef _service; + NSUInteger _port; + dispatch_source_t _source; + CFNetServiceRef _service; } -@property(nonatomic, strong) NSRunLoop *runLoop; @property(nonatomic, strong) NSMutableArray *handlers; @property(nonatomic, readonly, getter=isRunning) BOOL running; @property(nonatomic, readonly) NSUInteger port; - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock; - (void)removeAllHandlers; -- (BOOL)start; // Default is main runloop, 8080 port and computer name -- (BOOL)startWithRunloop:(NSRunLoop*)runloop port:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name +- (BOOL)start; // Default is 8080 port and computer name +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name - (void)stop; @end diff --git a/CGDWebServer/GCDWebServer.m b/CGDWebServer/GCDWebServer.m index 04f49eca..a249febb 100644 --- a/CGDWebServer/GCDWebServer.m +++ b/CGDWebServer/GCDWebServer.m @@ -34,6 +34,8 @@ #import "GCDWebServerPrivate.h" +#define kMaxPendingConnections 16 + static BOOL _run; NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) { @@ -127,126 +129,146 @@ - (id)init { } - (void)dealloc { - if (self.runLoop) - [self stop]; - self.handlers = nil; + if (_source) + [self stop]; + self.handlers = nil; } - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock { - DCHECK(self.runLoop == nil); - GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock]; - [self.handlers insertObject:handler atIndex:0]; + DCHECK(_source == NULL); + GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock]; + [self.handlers insertObject:handler atIndex:0]; } - (void)removeAllHandlers { - DCHECK(self.runLoop == nil); - [self.handlers removeAllObjects]; + DCHECK(_source == NULL); + [self.handlers removeAllObjects]; } - (BOOL)start { - return [self startWithRunloop:[NSRunLoop mainRunLoop] port:8080 bonjourName:@""]; + return [self startWithPort:8080 bonjourName:@""]; } static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { - @autoreleasepool { - if (error->error) { - LOG_ERROR(@"Bonjour error %i (domain %i)", error->error, (int)error->domain); - } else { - LOG_VERBOSE(@"Registered Bonjour service \"%@\" with type '%@' on port %i", CFNetServiceGetName(service), CFNetServiceGetType(service), CFNetServiceGetPortNumber(service)); - } - } + @autoreleasepool { + if (error->error) { + LOG_ERROR(@"Bonjour error %i (domain %i)", error->error, (int)error->domain); + } else { + LOG_VERBOSE(@"Registered Bonjour service \"%@\" with type '%@' on port %i", CFNetServiceGetName(service), CFNetServiceGetType(service), CFNetServiceGetPortNumber(service)); + } + } } -static void _SocketCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) { - if (type == kCFSocketAcceptCallBack) { - CFSocketNativeHandle handle = *(CFSocketNativeHandle*)data; - int set = 1; - setsockopt(handle, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)); // Make sure this socket cannot generate SIG_PIPE - @autoreleasepool { - Class class = [[(__bridge GCDWebServer*)info class] connectionClass]; - GCDWebServerConnection* connection = [[class alloc] initWithServer:(__bridge GCDWebServer*)info address:(__bridge NSData*)address socket:handle]; - NSLog(@"%@", connection); - } - } else { - DNOT_REACHED(); - } -} - -- (BOOL)startWithRunloop:(NSRunLoop*)runloop port:(NSUInteger)port bonjourName:(NSString*)name { - DCHECK(runloop); - DCHECK(self.runLoop == nil); - CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; - _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, _SocketCallBack, &context); - if (_socket) { - int yes = 1; - setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - 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); - if (CFSocketSetAddress(_socket, (__bridge CFDataRef)[NSData dataWithBytes:&addr4 length:sizeof(addr4)]) == kCFSocketSuccess) { - CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0); - CFRunLoopAddSource([runloop getCFRunLoop], source, kCFRunLoopCommonModes); - if (port == 0) { - // determine the actual port we are listening on - CFDataRef addressData = CFSocketCopyAddress(_socket); - struct sockaddr_in* sockaddr = (struct sockaddr_in*)CFDataGetBytePtr(addressData); - _port = ntohs(sockaddr->sin_port); - CFRelease(addressData); - } else { - _port = port; - } - - CFRelease(source); - - if (name) { - _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (__bridge CFStringRef)name, _port); - if (_service) { - CFNetServiceClientContext netServiceContext = {0, (__bridge void*)self, NULL, NULL, NULL}; - CFNetServiceSetClient(_service, _NetServiceClientCallBack, &netServiceContext); - CFNetServiceScheduleWithRunLoop(_service, [runloop getCFRunLoop], kCFRunLoopCommonModes); - CFStreamError error = {0}; - CFNetServiceRegisterWithOptions(_service, 0, &error); - } else { - LOG_ERROR(@"Failed creating CFNetService"); - } - } - self.runLoop = runloop; - LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port); - } else { - LOG_ERROR(@"Failed binding socket"); - CFRelease(_socket); - _socket = NULL; - } - } else { - LOG_ERROR(@"Failed creating CFSocket"); - } - return (self.runLoop != nil ? YES : NO); +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name { + DCHECK(_source == NULL); + int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listeningSocket > 0) { + int yes = 1; + setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); + + 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); + if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) { + if (listen(listeningSocket, kMaxPendingConnections) == 0) { + _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) { + LOG_ERROR(@"Failed closing socket (%i): %s", errno, strerror(errno)); + } else { + LOG_DEBUG(@"Closed listening socket"); + } + } + + }); + dispatch_source_set_event_handler(_source, ^{ + + @autoreleasepool { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + int socket = accept(listeningSocket, &addr, &addrlen); + if (socket > 0) { + int localYes = 1; + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &localYes, sizeof(localYes)); // Make sure this socket cannot generate SIG_PIPE + + NSData* data = [NSData dataWithBytes:&addr length:addrlen]; + Class connectionClass = [[self class] connectionClass]; + GCDWebServerConnection* connection = [[connectionClass alloc] initWithServer:self address:data socket:socket]; + DCHECK(connection); // Connection will automatically retain itself while opened + } else { + LOG_ERROR(@"Failed accepting socket (%i): %s", errno, strerror(errno)); + } + } + + }); + + if (port == 0) { // Determine the actual port we are listening on + 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 { + LOG_ERROR(@"Failed retrieving socket address (%i): %s", errno, strerror(errno)); + } + } else { + _port = port; + } + + if (name) { + _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (__bridge CFStringRef)name, _port); + if (_service) { + CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context); + CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFStreamError error = {0}; + CFNetServiceRegisterWithOptions(_service, 0, &error); + } else { + LOG_ERROR(@"Failed creating CFNetService"); + } + } + + dispatch_resume(_source); + LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port); + } else { + LOG_ERROR(@"Failed binding socket"); + close(listeningSocket); + } + } else { + LOG_ERROR(@"Failed binding socket (%i): %s", errno, strerror(errno)); + } + } + return (_source ? YES : NO); } - (BOOL)isRunning { - return (self.runLoop != nil ? YES : NO); + return (_source ? YES : NO); } - (void)stop { - DCHECK(self.runLoop != nil); - if (_socket) { - if (_service) { - CFNetServiceUnscheduleFromRunLoop(_service, [self.runLoop getCFRunLoop], kCFRunLoopCommonModes); - CFNetServiceSetClient(_service, NULL, NULL); - CFRelease(_service); - } - - CFSocketInvalidate(_socket); - CFRelease(_socket); - _socket = NULL; - LOG_VERBOSE(@"%@ stopped", [self class]); - } - self.runLoop = nil; - _port = 0; + DCHECK(_source != NULL); + if (_source) { + if (_service) { + CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_service, NULL, NULL); + CFRelease(_service); + _service = NULL; + } + + dispatch_source_cancel(_source); // This will close the socket + dispatch_release(_source); + _source = NULL; + + LOG_VERBOSE(@"%@ stopped", [self class]); + } + _port = 0; } @end @@ -266,20 +288,20 @@ + (NSString*)serverName { @implementation GCDWebServer (Extensions) - (BOOL)runWithPort:(NSUInteger)port { - BOOL success = NO; - _run = YES; - void* handler = signal(SIGINT, _SignalHandler); - if (handler != SIG_ERR) { - if ([self startWithRunloop:[NSRunLoop currentRunLoop] port:port bonjourName:@""]) { - while (_run) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; - } - [self stop]; - success = YES; - } - signal(SIGINT, handler); - } - return success; + BOOL success = NO; + _run = YES; + void* handler = signal(SIGINT, _SignalHandler); + if (handler != SIG_ERR) { + if ([self startWithPort:port bonjourName:@""]) { + while (_run) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); + } + [self stop]; + success = YES; + } + signal(SIGINT, handler); + } + return success; } @end diff --git a/CGDWebServer/GCDWebServerConnection.m b/CGDWebServer/GCDWebServerConnection.m index 2a861f14..11ef02fa 100644 --- a/CGDWebServer/GCDWebServerConnection.m +++ b/CGDWebServer/GCDWebServerConnection.m @@ -27,7 +27,6 @@ #import "GCDWebServerPrivate.h" -#define kReadWriteQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) #define kHeadersReadBuffer 1024 #define kBodyWriteBufferSize (32 * 1024) @@ -49,39 +48,39 @@ @implementation GCDWebServerConnection (Read) - (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block { - dispatch_read(_socket, length, kReadWriteQueue, ^(dispatch_data_t buffer, int error) { - - @autoreleasepool { - if (error == 0) { - size_t size = dispatch_data_get_size(buffer); - if (size > 0) { - LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket); - _bytesRead += size; - block(buffer); - } else { - if (_bytesRead > 0) { - LOG_ERROR(@"No more data available on socket %i", _socket); - } else { - LOG_WARNING(@"No data received from socket %i", _socket); - } - block(NULL); - } - } else { - LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error); - block(NULL); - } - } - - }); + dispatch_read(_socket, length, kGCDWebServerGCDQueue, ^(dispatch_data_t buffer, int error) { + + @autoreleasepool { + if (error == 0) { + size_t size = dispatch_data_get_size(buffer); + if (size > 0) { + LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket); + _bytesRead += size; + block(buffer); + } else { + if (_bytesRead > 0) { + LOG_ERROR(@"No more data available on socket %i", _socket); + } else { + LOG_WARNING(@"No data received from socket %i", _socket); + } + block(NULL); + } + } else { + LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error); + block(NULL); + } + } + + }); } - (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block { - [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { + [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t blockBuffer) { - if (buffer) { - NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(buffer)]; - dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* blockBuffer, size_t size) { - [data appendBytes:blockBuffer length:size]; + if (blockBuffer) { + NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(blockBuffer)]; + dispatch_data_apply(blockBuffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) { + [data appendBytes:buffer length:size]; return true; }); block(data); @@ -93,37 +92,42 @@ - (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block { } - (void)_readHeadersWithCompletionBlock:(ReadHeadersCompletionBlock)block { - DCHECK(_requestMessage); - NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer]; - [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { - - if (buffer) { - dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* blockBuffer, size_t size) { - [data appendBytes:blockBuffer length:size]; - return true; - }); - NSRange range = [data rangeOfData:_separatorData options:0 range:NSMakeRange(0, data.length)]; - if (range.location == NSNotFound) { - [self _readHeadersWithCompletionBlock:block]; - } else { - NSUInteger length = range.location + range.length; - if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, length)) { - if (CFHTTPMessageIsHeaderComplete(_requestMessage)) { - block([data subdataWithRange:NSMakeRange(length, data.length - length)]); - } else { - LOG_ERROR(@"Failed parsing request headers from socket %i", _socket); - block(nil); - } - } else { - LOG_ERROR(@"Failed appending request headers data from socket %i", _socket); - block(nil); - } - } - } else { - block(nil); - } - - }]; + DCHECK(_requestMessage); + [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { + + if (buffer) { + NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer]; + dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* blockBuffer, size_t size) { + [data appendBytes:blockBuffer length:size]; + return true; + }); + NSRange range = [data rangeOfData:_separatorData options:0 range:NSMakeRange(0, data.length)]; + if (range.location == NSNotFound) { + if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, data.length)) { + [self _readHeadersWithCompletionBlock:block]; + } else { + LOG_ERROR(@"Failed appending request headers data from socket %i", _socket); + block(nil); + } + } else { + NSUInteger length = range.location + range.length; + if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, length)) { + if (CFHTTPMessageIsHeaderComplete(_requestMessage)) { + block([data subdataWithRange:NSMakeRange(length, data.length - length)]); + } else { + LOG_ERROR(@"Failed parsing request headers from socket %i", _socket); + block(nil); + } + } else { + LOG_ERROR(@"Failed appending request headers data from socket %i", _socket); + block(nil); + } + } + } else { + block(nil); + } + + }]; } - (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block { @@ -166,28 +170,29 @@ - (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBod @implementation GCDWebServerConnection (Write) - (void)_writeBuffer:(dispatch_data_t)buffer withCompletionBlock:(WriteBufferCompletionBlock)block { - size_t size = dispatch_data_get_size(buffer); - dispatch_write(_socket, buffer, kReadWriteQueue, ^(dispatch_data_t data, int error) { - - @autoreleasepool { - if (error == 0) { - DCHECK(data == NULL); - LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket); - _bytesWritten += size; - block(YES); - } else { - LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error); - block(NO); - } - } - - }); + size_t size = dispatch_data_get_size(buffer); + dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t data, int error) { + + @autoreleasepool { + if (error == 0) { + DCHECK(data == NULL); + LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket); + _bytesWritten += size; + block(YES); + } else { + LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error); + block(NO); + } + } + + }); } - (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block { - dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_main_queue(), ^{ - }); - [self _writeBuffer:buffer withCompletionBlock:block]; + dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_current_queue(), ^{ + [data self]; // keeps ARC from releasing data too early. + }); + [self _writeBuffer:buffer withCompletionBlock:block]; } - (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block { @@ -204,7 +209,7 @@ - (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { if (result > 0) { dispatch_data_t wrapper = dispatch_data_create(buffer, result, NULL, DISPATCH_DATA_DESTRUCTOR_FREE); [self _writeBuffer:wrapper withCompletionBlock:^(BOOL success) { - + if (success) { [self _writeBodyWithCompletionBlock:block]; } else { @@ -212,6 +217,7 @@ - (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { } }]; + dispatch_release(wrapper); } else if (result < 0) { LOG_ERROR(@"Failed reading response body on socket %i (error %i)", _socket, (int)result); block(NO); @@ -387,7 +393,7 @@ - (void)_readRequestHeaders { if (_request) { if (_request.hasBody) { if (extraData.length <= _request.contentLength) { - NSString* expectHeader = (__bridge id)CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect")); + NSString* expectHeader = (__bridge_transfer NSString*)CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect")); if (expectHeader) { if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { [self _writeData:_continueData withCompletionBlock:^(BOOL success) { @@ -464,8 +470,11 @@ - (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock: } - (void)close { - close(_socket); - LOG_DEBUG(@"Did close connection on socket %i", _socket); + int result = close(_socket); + if (result != 0) { + LOG_ERROR(@"Failed closing socket %i for connection (%i): %s", _socket, errno, strerror(errno)); + } + LOG_DEBUG(@"Did close connection on socket %i", _socket); } @end diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index 6021ee02..50805919 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/CGDWebServer/GCDWebServerPrivate.h @@ -78,6 +78,7 @@ static inline void __LogMessage(long level, NSString* format, ...) { #endif #define kGCDWebServerDefaultMimeType @"application/octet-stream" +#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) #ifdef __cplusplus extern "C" { @@ -96,7 +97,6 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); @end @interface GCDWebServer () -//@property(nonatomic, readonly) NSArray* handlers; @end @interface GCDWebServerHandler : NSObject { diff --git a/README.md b/README.md index e7038716..64058009 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ You start by creating an instance of the 'GCDWebServer' class. Note that you can Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones. -Finally you start the server on a given port. Note that even if built on GCD, GCDWebServer still requires a runloop to be around (by default the main thread runloop is used). This is because there is no CGD API at this point to handle listening sockets, so it must be done using CFSocket which requires a runloop. However, the runloop is only used to accept the connection: immediately afterwards, the connection handling is dispatched to GCD queues. +Finally you start the server on a given port. Understanding GCDWebServer Architecture =======================================