Removed dependency on CFSocket to be 100% GCD based

This commit is contained in:
Pierre-Olivier Latour
2013-04-01 15:42:16 -07:00
parent 20507e9e85
commit 88365bd0c3
5 changed files with 103 additions and 77 deletions

View File

@@ -36,8 +36,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
NSMutableArray* _handlers;
NSUInteger _port;
NSRunLoop* _runLoop;
CFSocketRef _socket;
dispatch_source_t _source;
CFNetServiceRef _service;
}
@property(nonatomic, readonly, getter=isRunning) BOOL running;
@@ -45,8 +44,8 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
- (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

View File

@@ -34,6 +34,8 @@
#import "GCDWebServerPrivate.h"
#define kMaxPendingConnections 16
static BOOL _run;
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
@@ -134,7 +136,7 @@ static void _SignalHandler(int signal) {
}
- (void)dealloc {
if (_runLoop) {
if (_source) {
[self stop];
}
@@ -144,19 +146,19 @@ static void _SignalHandler(int signal) {
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
DCHECK(_runLoop == nil);
DCHECK(_source == NULL);
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
[_handlers insertObject:handler atIndex:0];
[handler release];
}
- (void)removeAllHandlers {
DCHECK(_runLoop == nil);
DCHECK(_source == NULL);
[_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) {
@@ -169,29 +171,13 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
}
}
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 = [[(GCDWebServer*)info class] connectionClass];
GCDWebServerConnection* connection = [[class alloc] initWithServer:(GCDWebServer*)info address:(NSData*)address socket:handle];
[connection release]; // Connection will automatically retain itself while opened
}
} else {
DNOT_REACHED();
}
}
- (BOOL)startWithRunloop:(NSRunLoop*)runloop port:(NSUInteger)port bonjourName:(NSString*)name {
DCHECK(runloop);
DCHECK(_runLoop == nil);
CFSocketContext context = {0, self, NULL, NULL, NULL};
_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, _SocketCallBack, &context);
if (_socket) {
- (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(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
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));
@@ -199,66 +185,104 @@ static void _SocketCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDat
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
if (CFSocketSetAddress(_socket, (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);
DCHECK(sockaddr);
_port = ntohs(sockaddr->sin_port);
CFRelease(addressData);
} else {
_port = port;
}
CFRelease(source);
if (name) {
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (CFStringRef)name, _port);
if (_service) {
CFNetServiceClientContext context = {0, self, NULL, NULL, NULL};
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
CFNetServiceScheduleWithRunLoop(_service, [runloop getCFRunLoop], kCFRunLoopCommonModes);
CFStreamError error = {0};
CFNetServiceRegisterWithOptions(_service, 0, &error);
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 yes = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes)); // 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];
[connection release]; // 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 {
LOG_ERROR(@"Failed creating CFNetService");
_port = port;
}
if (name) {
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (CFStringRef)name, _port);
if (_service) {
CFNetServiceClientContext context = {0, 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 listening on socket (%i): %s", errno, strerror(errno));
close(listeningSocket);
}
_runLoop = [runloop retain];
LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port);
} else {
LOG_ERROR(@"Failed binding socket");
CFRelease(_socket);
_socket = NULL;
LOG_ERROR(@"Failed binding socket (%i): %s", errno, strerror(errno));
close(listeningSocket);
}
} else {
LOG_ERROR(@"Failed creating CFSocket");
LOG_ERROR(@"Failed creating socket (%i): %s", errno, strerror(errno));
}
return (_runLoop != nil ? YES : NO);
return (_source ? YES : NO);
}
- (BOOL)isRunning {
return (_runLoop != nil ? YES : NO);
return (_source ? YES : NO);
}
- (void)stop {
DCHECK(_runLoop != nil);
if (_socket) {
DCHECK(_source != NULL);
if (_source) {
if (_service) {
CFNetServiceUnscheduleFromRunLoop(_service, [_runLoop getCFRunLoop], kCFRunLoopCommonModes);
CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFNetServiceSetClient(_service, NULL, NULL);
CFRelease(_service);
_service = NULL;
}
CFSocketInvalidate(_socket);
CFRelease(_socket);
_socket = NULL;
dispatch_source_cancel(_source); // This will close the socket
dispatch_release(_source);
_source = NULL;
LOG_VERBOSE(@"%@ stopped", [self class]);
}
[_runLoop release];
_runLoop = nil;
_port = 0;
}
@@ -283,9 +307,9 @@ static void _SocketCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDat
_run = YES;
void* handler = signal(SIGINT, _SignalHandler);
if (handler != SIG_ERR) {
if ([self startWithRunloop:[NSRunLoop currentRunLoop] port:port bonjourName:@""]) {
if ([self startWithPort:port bonjourName:@""]) {
while (_run) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
}
[self stop];
success = YES;

View File

@@ -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,7 +48,7 @@ static dispatch_queue_t _formatterQueue = NULL;
@implementation GCDWebServerConnection (Read)
- (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
dispatch_read(_socket, length, kReadWriteQueue, ^(dispatch_data_t buffer, int error) {
dispatch_read(_socket, length, kGCDWebServerGCDQueue, ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
@@ -173,7 +172,7 @@ static dispatch_queue_t _formatterQueue = NULL;
- (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) {
dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t data, int error) {
@autoreleasepool {
if (error == 0) {
@@ -483,7 +482,10 @@ static dispatch_queue_t _formatterQueue = NULL;
}
- (void)close {
close(_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);
}

View File

@@ -79,6 +79,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" {

View File

@@ -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
=======================================