Replaced GCDWebServer subclassing with explicit options

This commit is contained in:
Pierre-Olivier Latour 2014-04-16 01:29:52 -03:00
parent 2d8996b91e
commit 998a47b099
4 changed files with 57 additions and 49 deletions

View File

@ -42,6 +42,14 @@ typedef NS_ENUM(int, GCDWebServerLogLevel) {
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
extern NSString* const GCDWebServerOption_Port; // NSNumber / NSUInteger (default is 0 i.e. use a random port)
extern NSString* const GCDWebServerOption_BonjourName; // NSString (default is empty string i.e. use computer name)
extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / NSUInteger (default is 16)
extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name)
extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class)
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES)
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 - set to 0.0 to disable)
@class GCDWebServer;
// These methods are always called on main thread
@ -65,17 +73,10 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
- (BOOL)start; // Default is port 8080 (OS X & iOS Simulator) or 80 (iOS) and computer / device name for Bonjour
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name
- (BOOL)startWithOptions:(NSDictionary*)options;
- (void)stop;
@end
@interface GCDWebServer (Subclassing)
+ (NSUInteger)maxPendingConnections; // Default is 16
+ (Class)connectionClass; // Default is GCDWebServerConnection
+ (NSString*)serverName; // Default is class name
+ (BOOL)shouldAutomaticallyMapHEADToGET; // Default is YES which means HEAD requests are mapped to GET requests with the response body being discarded
+ (NSTimeInterval)connectedStateCoalescingInterval; // Allows coalescing of fast sequences of -webServerDidConnect: / -webServerDidDisconnect: - Default is 1.0 seconds (set to 0.0 to disable)
@end
@interface GCDWebServer (Extensions)
@property(nonatomic, readonly) NSURL* serverURL; // Only non-nil if server is running
@property(nonatomic, readonly) NSURL* bonjourServerURL; // Only non-nil if server is running and Bonjour registration is active

View File

@ -50,6 +50,10 @@
BOOL _connected;
CFRunLoopTimerRef _connectedTimer;
NSString* _serverName;
Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay;
NSUInteger _port;
dispatch_source_t _source;
CFNetServiceRef _service;
@ -66,6 +70,14 @@
}
@end
NSString* const GCDWebServerOption_Port = @"Port";
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
NSString* const GCDWebServerOption_ServerName = @"ServerName";
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
#ifdef NDEBUG
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
@ -124,7 +136,7 @@ static void _SignalHandler(int signal) {
@implementation GCDWebServer
@synthesize delegate=_delegate, handlers=_handlers, port=_port, connected=_connected;
@synthesize delegate=_delegate, handlers=_handlers, port=_port, connected=_connected, serverName=_serverName, shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
@ -152,10 +164,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
_syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
_handlers = [[NSMutableArray alloc] init];
CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
if ([[self class] connectedStateCoalescingInterval] > 0.0) {
_connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context);
CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes);
}
_connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context);
CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes);
}
return self;
}
@ -169,10 +179,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
[self stop];
}
if (_connectedTimer) {
CFRunLoopTimerInvalidate(_connectedTimer);
CFRelease(_connectedTimer);
}
CFRunLoopTimerInvalidate(_connectedTimer);
CFRelease(_connectedTimer);
ARC_RELEASE(_handlers);
ARC_DISPATCH_RELEASE(_syncQueue);
@ -195,7 +203,7 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
DCHECK(_activeConnections >= 0);
if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
if (_connectedTimer) {
if (_disconnectDelay > 0.0) {
CFRunLoopTimerSetNextFireDate(_connectedTimer, HUGE_VAL);
}
if (_connected == NO) {
@ -224,8 +232,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
_activeConnections -= 1;
if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
if (_connectedTimer) {
CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + [[self class] connectedStateCoalescingInterval]);
if (_disconnectDelay > 0.0) {
CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay);
} else {
[self _didDisconnect];
}
@ -267,7 +275,22 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self startWithOptions:options];
}
static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
id value = [options objectForKey:key];
return value ? value : defaultValue;
}
- (BOOL)startWithOptions:(NSDictionary*)options {
DCHECK(_source == NULL);
NSUInteger port = [_GetOption(options, GCDWebServerOption_Port, [NSNumber numberWithUnsignedInteger:0]) unsignedIntegerValue];
NSString* name = _GetOption(options, GCDWebServerOption_BonjourName, @"");
NSUInteger maxPendingConnections = [_GetOption(options, GCDWebServerOption_MaxPendingConnections, [NSNumber numberWithUnsignedInteger:16]) unsignedIntegerValue];
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket > 0) {
int yes = 1;
@ -280,8 +303,12 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) {
if (listen(listeningSocket, (int)[[self class] maxPendingConnections]) == 0) {
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
LOG_DEBUG(@"Did open listening socket %i", listeningSocket);
_serverName = [_GetOption(options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
_connectionClass = _GetOption(options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
_mapHEADToGET = [_GetOption(options, GCDWebServerOption_AutomaticallyMapHEADToGET, [NSNumber numberWithBool:YES]) boolValue];
_disconnectDelay = [_GetOption(options, GCDWebServerOption_ConnectedStateCoalescingInterval, [NSNumber numberWithDouble:1.0]) doubleValue];
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
dispatch_source_set_cancel_handler(_source, ^{
@ -316,8 +343,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
int noSigPipe = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
Class connectionClass = [[self class] connectionClass];
GCDWebServerConnection* connection = [[connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
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
@ -396,6 +422,9 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
_source = NULL;
_port = 0;
ARC_RELEASE(_serverName);
_serverName = nil;
LOG_INFO(@"%@ stopped", [self class]);
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
@ -407,30 +436,6 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
@end
@implementation GCDWebServer (Subclassing)
+ (NSUInteger)maxPendingConnections {
return 16;
}
+ (Class)connectionClass {
return [GCDWebServerConnection class];
}
+ (NSString*)serverName {
return NSStringFromClass(self);
}
+ (BOOL)shouldAutomaticallyMapHEADToGET {
return YES;
}
+ (NSTimeInterval)connectedStateCoalescingInterval {
return 1.0;
}
@end
@implementation GCDWebServer (Extensions)
- (NSURL*)serverURL {

View File

@ -363,7 +363,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
_statusCode = statusCode;
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)[[_server class] serverName]);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)_server.serverName);
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}
@ -509,7 +509,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
if (extraData) {
NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
if ([[_server class] shouldAutomaticallyMapHEADToGET] && [requestMethod isEqualToString:@"HEAD"]) {
if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
requestMethod = @"GET";
_virtualHEAD = YES;
}

View File

@ -128,6 +128,8 @@ extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
@interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers;
@property(nonatomic, readonly) NSString* serverName;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
- (void)willStartConnection:(GCDWebServerConnection*)connection;
- (void)didEndConnection:(GCDWebServerConnection*)connection;
@end