mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-04-05 00:04:17 +08:00
Removed dependency on CFSocket to be 100% GCD based
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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" {
|
||||
|
||||
Reference in New Issue
Block a user