From 63a66ff331ce31f548af180c5d4b216393a56e37 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Latour Date: Sun, 6 Apr 2014 12:09:44 -0700 Subject: [PATCH] Added GCDWebServerBodyWriter protocol --- CGDWebServer/GCDWebServerConnection.m | 34 ++++++++------- CGDWebServer/GCDWebServerDataRequest.m | 16 +++++--- CGDWebServer/GCDWebServerFileRequest.m | 28 ++++++++++--- .../GCDWebServerMultiPartFormRequest.m | 20 ++++++--- CGDWebServer/GCDWebServerPrivate.h | 7 ++++ CGDWebServer/GCDWebServerRequest.h | 14 +++---- CGDWebServer/GCDWebServerRequest.m | 41 ++++++++++++++----- CGDWebServer/GCDWebServerResponse.h | 8 ++-- CGDWebServer/GCDWebServerResponse.m | 23 ++++++----- CGDWebServer/GCDWebServerStreamResponse.m | 2 +- .../GCDWebServerURLEncodedFormRequest.m | 7 ++-- 11 files changed, 131 insertions(+), 69 deletions(-) diff --git a/CGDWebServer/GCDWebServerConnection.m b/CGDWebServer/GCDWebServerConnection.m index f140271..515821f 100644 --- a/CGDWebServer/GCDWebServerConnection.m +++ b/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]; } } diff --git a/CGDWebServer/GCDWebServerDataRequest.m b/CGDWebServer/GCDWebServerDataRequest.m index 1bfab58..801dbae 100644 --- a/CGDWebServer/GCDWebServerDataRequest.m +++ b/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; } diff --git a/CGDWebServer/GCDWebServerFileRequest.m b/CGDWebServer/GCDWebServerFileRequest.m index 2a5c1c7..11db890 100644 --- a/CGDWebServer/GCDWebServerFileRequest.m +++ b/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 diff --git a/CGDWebServer/GCDWebServerMultiPartFormRequest.m b/CGDWebServer/GCDWebServerMultiPartFormRequest.m index 52f5933..0571c1f 100644 --- a/CGDWebServer/GCDWebServerMultiPartFormRequest.m +++ b/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 { diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index 4fe60ae..3dfdae9 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/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; diff --git a/CGDWebServer/GCDWebServerRequest.h b/CGDWebServer/GCDWebServerRequest.h index 5dcc645..256c0c1 100644 --- a/CGDWebServer/GCDWebServerRequest.h +++ b/CGDWebServer/GCDWebServerRequest.h @@ -27,7 +27,13 @@ #import -@interface GCDWebServerRequest : NSObject +@protocol GCDWebServerBodyWriter +- (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 @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 diff --git a/CGDWebServer/GCDWebServerRequest.m b/CGDWebServer/GCDWebServerRequest.m index 9a03b5b..307b8d7 100644 --- a/CGDWebServer/GCDWebServerRequest.m +++ b/CGDWebServer/GCDWebServerRequest.m @@ -37,6 +37,10 @@ NSString* _type; NSUInteger _length; NSRange _range; + + BOOL _opened; + NSMutableArray* _decoders; + id __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 +- (BOOL)open:(NSError**)error { + return YES; +} -@implementation GCDWebServerRequest (Subclassing) - -- (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 diff --git a/CGDWebServer/GCDWebServerResponse.h b/CGDWebServer/GCDWebServerResponse.h index e8469bc..433cd08 100644 --- a/CGDWebServer/GCDWebServerResponse.h +++ b/CGDWebServer/GCDWebServerResponse.h @@ -28,8 +28,8 @@ #import @protocol GCDWebServerBodyReader -- (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; diff --git a/CGDWebServer/GCDWebServerResponse.m b/CGDWebServer/GCDWebServerResponse.m index 770d839..1ef8a07 100644 --- a/CGDWebServer/GCDWebServerResponse.m +++ b/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; } diff --git a/CGDWebServer/GCDWebServerStreamResponse.m b/CGDWebServer/GCDWebServerStreamResponse.m index 9ab33e0..0338019 100644 --- a/CGDWebServer/GCDWebServerStreamResponse.m +++ b/CGDWebServer/GCDWebServerStreamResponse.m @@ -44,7 +44,7 @@ _block = [block copy]; self.contentType = type; - self.chunkedTransferEncoding = YES; + self.chunkedTransferEncodingEnabled = YES; } return self; } diff --git a/CGDWebServer/GCDWebServerURLEncodedFormRequest.m b/CGDWebServer/GCDWebServerURLEncodedFormRequest.m index e2fe12a..7962e54 100644 --- a/CGDWebServer/GCDWebServerURLEncodedFormRequest.m +++ b/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