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 a557080a07
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" {