Forráskód Böngészése

Added GCDWebServerBodyWriter protocol

Pierre-Olivier Latour 11 éve
szülő
commit
63a66ff331

+ 19 - 15
CGDWebServer/GCDWebServerConnection.m

@@ -157,10 +157,11 @@ static dispatch_queue_t _formatterQueue = NULL;
     if (buffer) {
       NSInteger remainingLength = length - dispatch_data_get_size(buffer);
       if (remainingLength >= 0) {
-        bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* bufferChunk, size_t size) {
-          NSInteger result = [_request write:bufferChunk maxLength:size];
-          if (result != (NSInteger)size) {
-            LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
+        bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* chunkBytes, size_t chunkSize) {
+          NSData* data = [NSData dataWithBytes:chunkBytes length:chunkSize];
+          NSError* error = nil;
+          if (![_request performWriteData:data error:&error]) {
+            LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
             return false;
           }
           return true;
@@ -318,7 +319,7 @@ static dispatch_queue_t _formatterQueue = NULL;
   if (response) {
     NSError* error = nil;
     if ([response hasBody] && ![response performOpen:&error]) {
-      LOG_WARNING(@"Failed opening response body for socket %i: %@", _socket, error);
+      LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
     } else {
       _response = ARC_RETAIN(response);
     }
@@ -363,42 +364,45 @@ static dispatch_queue_t _formatterQueue = NULL;
 }
 
 - (void)_readRequestBody:(NSData*)initialData {
-  if ([_request open]) {
+  NSError* error = nil;
+  if ([_request performOpen:&error]) {
     NSInteger length = _request.contentLength;
     if (initialData.length) {
-      NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
-      if (result == (NSInteger)initialData.length) {
+      if ([_request performWriteData:initialData error:&error]) {
         length -= initialData.length;
         DCHECK(length >= 0);
       } else {
-        LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
+        LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
         length = -1;
       }
     }
     if (length > 0) {
       [self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) {
         
-        if (![_request close]) {
-          success = NO;
-        }
-        if (success) {
+        NSError* localError = nil;
+        if ([_request performClose:&localError]) {
           [self _processRequest];
         } else {
+          LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
           [self _abortWithStatusCode:500];
         }
         
       }];
     } else if (length == 0) {
-      if ([_request close]) {
+      if ([_request performClose:&error]) {
         [self _processRequest];
       } else {
+        LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
         [self _abortWithStatusCode:500];
       }
     } else {
-      [_request close];  // Can't do anything with result anyway
+      if (![_request performClose:&error]) {
+        LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
+      }
       [self _abortWithStatusCode:500];
     }
   } else {
+    LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
     [self _abortWithStatusCode:500];
   }
 }

+ 10 - 6
CGDWebServer/GCDWebServerDataRequest.m

@@ -44,19 +44,23 @@
   ARC_DEALLOC(super);
 }
 
-- (BOOL)open {
+- (BOOL)open:(NSError**)error {
   DCHECK(_data == nil);
   _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
-  return _data ? YES : NO;
+  if (_data == nil) {
+    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
+    return NO;
+  }
+  return YES;
 }
 
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
   DCHECK(_data != nil);
-  [_data appendBytes:buffer length:length];
-  return length;
+  [_data appendData:data];
+  return YES;
 }
 
-- (BOOL)close {
+- (BOOL)close:(NSError**)error {
   DCHECK(_data != nil);
   return YES;
 }

+ 22 - 6
CGDWebServer/GCDWebServerFileRequest.m

@@ -34,6 +34,10 @@
 }
 @end
 
+static inline NSError* _MakePosixError(int code) {
+  return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
+}
+
 @implementation GCDWebServerFileRequest
 
 @synthesize filePath=_filePath;
@@ -53,22 +57,34 @@
   ARC_DEALLOC(super);
 }
 
-- (BOOL)open {
+- (BOOL)open:(NSError**)error {
   DCHECK(_file == 0);
   _file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-  return (_file > 0 ? YES : NO);
+  if (_file <= 0) {
+    *error = _MakePosixError(errno);
+    return NO;
+  }
+  return YES;
 }
 
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
   DCHECK(_file > 0);
-  return write(_file, buffer, length);
+  if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
+    *error = _MakePosixError(errno);
+    return NO;
+  }
+  return YES;
 }
 
-- (BOOL)close {
+- (BOOL)close:(NSError**)error {
   DCHECK(_file > 0);
   int result = close(_file);
   _file = -1;
-  return (result == 0 ? YES : NO);
+  if (result < 0) {
+    *error = _MakePosixError(errno);
+    return NO;
+  }
+  return YES;
 }
 
 @end

+ 14 - 6
CGDWebServer/GCDWebServerMultiPartFormRequest.m

@@ -204,7 +204,7 @@ static NSData* _dashNewlineData = nil;
   return self;
 }
 
-- (BOOL)open {
+- (BOOL)open:(NSError**)error {
   DCHECK(_parserData == nil);
   _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
   _parserState = kParserState_Start;
@@ -329,13 +329,17 @@ static NSData* _dashNewlineData = nil;
   return success;
 }
 
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
   DCHECK(_parserData != nil);
-  [_parserData appendBytes:buffer length:length];
-  return ([self _parseData] ? length : -1);
+  [_parserData appendBytes:data.bytes length:data.length];
+  if (![self _parseData]) {
+    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
+    return NO;
+  }
+  return YES;
 }
 
-- (BOOL)close {
+- (BOOL)close:(NSError**)error {
   DCHECK(_parserData != nil);
   ARC_RELEASE(_parserData);
   _parserData = nil;
@@ -352,7 +356,11 @@ static NSData* _dashNewlineData = nil;
   }
   ARC_RELEASE(_tmpPath);
   _tmpPath = nil;
-  return (_parserState == kParserState_End ? YES : NO);
+  if (_parserState != kParserState_End) {
+    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
+    return NO;
+  }
+  return YES;
 }
 
 - (void)dealloc {

+ 7 - 0
CGDWebServer/GCDWebServerPrivate.h

@@ -99,6 +99,7 @@ extern void GCDLogMessage(long level, NSString* format, ...) NS_FORMAT_FUNCTION(
 
 #define kGCDWebServerDefaultMimeType @"application/octet-stream"
 #define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
+#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
 
 extern NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute);
 extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
@@ -117,6 +118,12 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset)
 - (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
 @end
 
+@interface GCDWebServerRequest ()
+- (BOOL)performOpen:(NSError**)error;
+- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
+- (BOOL)performClose:(NSError**)error;
+@end
+
 @interface GCDWebServerResponse ()
 @property(nonatomic, readonly) NSDictionary* additionalHeaders;
 - (BOOL)performOpen:(NSError**)error;

+ 7 - 7
CGDWebServer/GCDWebServerRequest.h

@@ -27,7 +27,13 @@
 
 #import <Foundation/Foundation.h>
 
-@interface GCDWebServerRequest : NSObject
+@protocol GCDWebServerBodyWriter <NSObject>
+- (BOOL)open:(NSError**)error;  // Return NO on error ("error" is guaranteed to be non-NULL)
+- (BOOL)writeData:(NSData*)data error:(NSError**)error;  // Return NO on error ("error" is guaranteed to be non-NULL)
+- (BOOL)close:(NSError**)error;  // Return NO on error ("error" is guaranteed to be non-NULL)
+@end
+
+@interface GCDWebServerRequest : NSObject <GCDWebServerBodyWriter>
 @property(nonatomic, readonly) NSString* method;
 @property(nonatomic, readonly) NSURL* URL;
 @property(nonatomic, readonly) NSDictionary* headers;
@@ -39,9 +45,3 @@
 - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
 - (BOOL)hasBody;  // Convenience method
 @end
-
-@interface GCDWebServerRequest (Subclassing)
-- (BOOL)open;  // Implementation required
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length;  // Implementation required
-- (BOOL)close;  // Implementation required
-@end

+ 31 - 10
CGDWebServer/GCDWebServerRequest.m

@@ -37,6 +37,10 @@
   NSString* _type;
   NSUInteger _length;
   NSRange _range;
+  
+  BOOL _opened;
+  NSMutableArray* _decoders;
+  id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
 }
 @end
 
@@ -97,6 +101,8 @@
         LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
       }
     }
+    
+    _decoders = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -108,6 +114,7 @@
   ARC_RELEASE(_path);
   ARC_RELEASE(_query);
   ARC_RELEASE(_type);
+  ARC_RELEASE(_decoders);
   
   ARC_DEALLOC(super);
 }
@@ -116,23 +123,37 @@
   return _type ? YES : NO;
 }
 
-@end
-
-@implementation GCDWebServerRequest (Subclassing)
+- (BOOL)open:(NSError**)error {
+  return YES;
+}
 
-- (BOOL)open {
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
   [self doesNotRecognizeSelector:_cmd];
   return NO;
 }
 
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
-  [self doesNotRecognizeSelector:_cmd];
-  return -1;
+- (BOOL)close:(NSError**)error {
+  return YES;
 }
 
-- (BOOL)close {
-  [self doesNotRecognizeSelector:_cmd];
-  return NO;
+- (BOOL)performOpen:(NSError**)error {
+  if (_opened) {
+    DNOT_REACHED();
+    return NO;
+  }
+  _opened = YES;
+  
+  _writer = self;
+  // TODO: Inject decoders
+  return [_writer open:error];
+}
+
+- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
+  return [_writer writeData:data error:error];
+}
+
+- (BOOL)performClose:(NSError**)error {
+  return [_writer close:error];
 }
 
 @end

+ 4 - 4
CGDWebServer/GCDWebServerResponse.h

@@ -28,8 +28,8 @@
 #import <Foundation/Foundation.h>
 
 @protocol GCDWebServerBodyReader <NSObject>
-- (BOOL)open:(NSError**)error;
-- (NSData*)readData:(NSError**)error;  // Return nil on error or empty NSData if at end
+- (BOOL)open:(NSError**)error;  // Return NO on error ("error" is guaranteed to be non-NULL)
+- (NSData*)readData:(NSError**)error;  // Must return nil on error or empty NSData if at end ("error" is guaranteed to be non-NULL)
 - (void)close;
 @end
 
@@ -38,8 +38,8 @@
 @property(nonatomic) NSUInteger contentLength;  // Default is NSNotFound i.e. undefined
 @property(nonatomic) NSInteger statusCode;  // Default is 200
 @property(nonatomic) NSUInteger cacheControlMaxAge;  // Default is 0 seconds i.e. "no-cache"
-@property(nonatomic) BOOL gzipContentEncoding;
-@property(nonatomic) BOOL chunkedTransferEncoding;
+@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled;
+@property(nonatomic, getter=isChunkedTransferEncodingEnabled) BOOL chunkedTransferEncodingEnabled;
 + (GCDWebServerResponse*) response;
 - (id)init;
 - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;

+ 12 - 11
CGDWebServer/GCDWebServerResponse.m

@@ -157,12 +157,12 @@
 }
 
 - (NSData*)readData:(NSError**)error {
-  NSMutableData* gzipData;
+  NSMutableData* encodedData;
   if (_finished) {
-    gzipData = [[NSMutableData alloc] init];
+    encodedData = [[NSMutableData alloc] init];
   } else {
-    gzipData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
-    if (gzipData == nil) {
+    encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
+    if (encodedData == nil) {
       DNOT_REACHED();
       return nil;
     }
@@ -175,14 +175,14 @@
       _stream.next_in = (Bytef*)data.bytes;
       _stream.avail_in = (uInt)data.length;
       while (1) {
-        NSUInteger maxLength = gzipData.length - length;
-        _stream.next_out = (Bytef*)((char*)gzipData.mutableBytes + length);
+        NSUInteger maxLength = encodedData.length - length;
+        _stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length);
         _stream.avail_out = (uInt)maxLength;
         int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH);
         if (result == Z_STREAM_END) {
           _finished = YES;
         } else if (result != Z_OK) {
-          ARC_RELEASE(gzipData);
+          ARC_RELEASE(encodedData);
           *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
           return nil;
         }
@@ -190,13 +190,13 @@
         if (_stream.avail_out > 0) {
           break;
         }
-        gzipData.length = 2 * gzipData.length;  // zlib has used all the output buffer so resize it and try again in case more data is available
+        encodedData.length = 2 * encodedData.length;  // zlib has used all the output buffer so resize it and try again in case more data is available
       }
       DCHECK(_stream.avail_in == 0);
     } while (length == 0);  // Make sure we don't return an empty NSData if not in finished state
-    gzipData.length = length;
+    encodedData.length = length;
   }
-  return ARC_AUTORELEASE(gzipData);
+  return ARC_AUTORELEASE(encodedData);
 }
 
 - (void)close {
@@ -225,7 +225,7 @@
 @implementation GCDWebServerResponse
 
 @synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge,
-            gzipContentEncoding=_gzipped, chunkedTransferEncoding=_chunked, additionalHeaders=_headers;
+            gzipContentEncodingEnabled=_gzipped, chunkedTransferEncodingEnabled=_chunked, additionalHeaders=_headers;
 
 + (GCDWebServerResponse*)response {
   return ARC_AUTORELEASE([[[self class] alloc] init]);
@@ -264,6 +264,7 @@
 }
 
 - (NSData*)readData:(NSError**)error {
+  [self doesNotRecognizeSelector:_cmd];
   return nil;
 }
 

+ 1 - 1
CGDWebServer/GCDWebServerStreamResponse.m

@@ -44,7 +44,7 @@
     _block = [block copy];
     
     self.contentType = type;
-    self.chunkedTransferEncoding = YES;
+    self.chunkedTransferEncodingEnabled = YES;
   }
   return self;
 }

+ 4 - 3
CGDWebServer/GCDWebServerURLEncodedFormRequest.m

@@ -47,17 +47,18 @@
   ARC_DEALLOC(super);
 }
 
-- (BOOL)close {
-  if (![super close]) {
+- (BOOL)close:(NSError**)error {
+  if (![super close:error]) {
     return NO;
   }
   
   NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset");
   NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
   _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
+  DCHECK(_arguments);
   ARC_RELEASE(string);
   
-  return (_arguments ? YES : NO);
+  return YES;
 }
 
 @end