Procházet zdrojové kódy

#92 Added support for async handlers

Pierre-Olivier Latour před 11 roky
rodič
revize
b35ebd7d58

+ 49 - 4
GCDWebServer/Core/GCDWebServer.h

@@ -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)

+ 34 - 10
GCDWebServer/Core/GCDWebServer.m

@@ -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();
   }

+ 4 - 3
GCDWebServer/Core/GCDWebServerConnection.h

@@ -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

+ 20 - 13
GCDWebServer/Core/GCDWebServerConnection.m

@@ -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

+ 1 - 2
GCDWebServer/Core/GCDWebServerPrivate.h

@@ -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 ()

+ 22 - 1
Mac/main.m

@@ -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");