mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2024-10-07 16:48:03 +08:00
Replaced GCDWebServer subclassing with explicit options
This commit is contained in:
parent
2d8996b91e
commit
998a47b099
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user