Added GCDWebServerBodyWriter protocol

This commit is contained in:
Pierre-Olivier Latour
2014-04-06 12:09:44 -07:00
parent 1f9a0d38d0
commit 63a66ff331
11 changed files with 131 additions and 69 deletions

View File

@@ -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];
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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