#92 Added support for async handlers

This commit is contained in:
Pierre-Olivier Latour 2014-10-09 09:53:03 -07:00
parent a11b047233
commit b35ebd7d58
6 changed files with 130 additions and 33 deletions

View File

@ -69,6 +69,19 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
*/
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
/**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
* except the GCDWebServerResponse can be returned to the server at a later time
* allowing for asynchronous generation of the response.
*
* The block must eventually call "completionBlock" passing a GCDWebServerResponse
* or nil on error, which will result in a 500 HTTP status code returned to the client.
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/**
* The port used by the GCDWebServer (NSNumber / NSUInteger).
*
@ -289,7 +302,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
- (instancetype)init;
/**
* Adds a handler to the server to handle incoming HTTP requests.
* Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
@ -298,6 +311,16 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
/**
* Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;
/**
* Removes all handlers previously added to the server.
*
@ -392,22 +415,44 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method.
* with a given HTTP method and generate responses synchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses asynchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path.
* HTTP method and a specific case-insensitive path and generate responses
* synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression.
* HTTP method and a specific case-insensitive path and generate responses
* asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
@end
@interface GCDWebServer (GETHandlers)

View File

@ -114,25 +114,25 @@ static void _ExecuteMainThreadRunLoopSources() {
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end
@implementation GCDWebServerHandler
@synthesize matchBlock=_matchBlock, processBlock=_processBlock;
@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
if ((self = [super init])) {
_matchBlock = [matchBlock copy];
_processBlock = [processBlock copy];
_asyncProcessBlock = [processBlock copy];
}
return self;
}
- (void)dealloc {
ARC_RELEASE(_matchBlock);
ARC_RELEASE(_processBlock);
ARC_RELEASE(_asyncProcessBlock);
ARC_DEALLOC(super);
}
@ -345,9 +345,15 @@ static void _ExecuteMainThreadRunLoopSources() {
return type && CFStringGetLength(type) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
[self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(processBlock(request));
}];
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
DCHECK(_options == nil);
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock];
[_handlers insertObject:handler atIndex:0];
ARC_RELEASE(handler);
}
@ -754,6 +760,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) {
@ -761,10 +773,16 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block];
} asyncProcessBlock:block];
}
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
@ -776,13 +794,19 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block];
} asyncProcessBlock:block];
} else {
DNOT_REACHED();
}
}
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
@ -808,7 +832,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
return ARC_AUTORELEASE(request);
} processBlock:block];
} asyncProcessBlock:block];
} else {
DNOT_REACHED();
}

View File

@ -138,13 +138,14 @@
/**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request.
* this method is called to process the request by executing the handler's
* process block.
*/
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
/**
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:withBlock: returned a non-nil GCDWebServerResponse,
* or -processRequest:completion: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a

View File

@ -373,15 +373,24 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}
- (void)_startProcessingRequest {
DCHECK(_responseMessage == NULL);
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
if (preflightResponse) {
[self _finishProcessingRequest:preflightResponse];
} else {
[self processRequest:_request completion:^(GCDWebServerResponse* processResponse) {
[self _finishProcessingRequest:processResponse];
}];
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_processRequest {
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
DCHECK(_responseMessage == NULL);
BOOL hasBody = NO;
GCDWebServerResponse* response = [self preflightRequest:_request];
if (!response) {
response = [self processRequest:_request withBlock:_handler.processBlock];
}
if (response) {
response = [self overrideResponse:response forRequest:_request];
}
@ -471,7 +480,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@ -480,7 +489,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}];
} else {
if ([_request performClose:&error]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@ -501,7 +510,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@ -572,7 +581,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
}
} else {
[self _processRequest];
[self _startProcessingRequest];
}
} else {
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
@ -772,16 +781,14 @@ static NSString* _StringFromAddressData(NSData* data) {
return response;
}
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
GCDWebServerResponse* response = nil;
@try {
response = block(request);
_handler.asyncProcessBlock(request, completion);
}
@catch (NSException* exception) {
LOG_EXCEPTION(exception);
}
return response;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26

View File

@ -143,8 +143,7 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
@interface GCDWebServerHandler : NSObject
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
@end
@interface GCDWebServerRequest ()

View File

@ -52,6 +52,7 @@ typedef enum {
kMode_WebDAV,
kMode_WebUploader,
kMode_StreamingResponse,
kMode_AsyncResponse
} Mode;
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
@ -142,7 +143,7 @@ int main(int argc, const char* argv[]) {
NSString* authenticationPassword = nil;
if (argc == 1) {
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
} else {
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
@ -164,6 +165,8 @@ int main(int argc, const char* argv[]) {
mode = kMode_WebUploader;
} else if (!strcmp(argv[i], "streamingResponse")) {
mode = kMode_StreamingResponse;
} else if (!strcmp(argv[i], "asyncResponse")) {
mode = kMode_AsyncResponse;
}
} else if (!strcmp(argv[i], "-record")) {
recording = YES;
@ -328,6 +331,24 @@ int main(int argc, const char* argv[]) {
break;
}
// Test async responses
case kMode_AsyncResponse: {
fprintf(stdout, "Running in Async Response mode");
webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest *request, GCDWebServerCompletionBlock completionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response);
});
}];
break;
}
}
#if __has_feature(objc_arc)
fprintf(stdout, " (ARC is ON)\n");