First pass at adding body encoders

This commit is contained in:
Pierre-Olivier Latour
2014-04-06 10:44:53 -07:00
parent 7506f9c9a2
commit 81638ad086
5 changed files with 343 additions and 160 deletions

View File

@@ -26,9 +26,188 @@
*/
#import <sys/stat.h>
#import <zlib.h>
#import "GCDWebServerPrivate.h"
#define kZlibErrorDomain @"ZlibErrorDomain"
#define kGZipInitialBufferSize (256 * 1024)
#define kFileReadBufferSize (32 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
@end
@interface GCDWebServerChunkEncoder : GCDWebServerBodyEncoder
@end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end
@interface GCDWebServerBodyEncoder () {
@private
GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
@end
@implementation GCDWebServerBodyEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super init])) {
_response = response;
_reader = reader;
}
return self;
}
- (BOOL)open:(NSError**)error {
return [_reader open:error];
}
- (NSData*)readData:(NSError**)error {
return [_reader readData:error];
}
- (void)close {
[_reader close];
}
@end
@interface GCDWebServerChunkEncoder () {
@private
BOOL _finished;
}
@end
@implementation GCDWebServerChunkEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set
[response setValue:@"chunked" forAdditionalHeader:@"Transfer-Encoding"];
}
return self;
}
- (NSData*)readData:(NSError**)error {
NSData* chunk;
if (_finished) {
chunk = [[NSData alloc] init];
} else {
NSData* data = [super readData:error];
if (data == nil) {
return nil;
}
if (data.length) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
chunk = [[NSMutableData alloc] initWithLength:(hexLength + 2 + data.length + 2)];
if (chunk == nil) {
DNOT_REACHED();
return nil;
}
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
} else {
chunk = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
DCHECK(chunk);
_finished = YES;
}
}
return ARC_AUTORELEASE(chunk);
}
@end
@interface GCDWebServerGZipEncoder () {
@private
z_stream _stream;
BOOL _finished;
}
@end
@implementation GCDWebServerGZipEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
}
return self;
}
- (BOOL)open:(NSError**)error {
int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (result != Z_OK) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
return NO;
}
if (![super open:error]) {
deflateEnd(&_stream);
return NO;
}
return YES;
}
- (NSData*)readData:(NSError**)error {
NSMutableData* gzipData;
if (_finished) {
gzipData = [[NSMutableData alloc] init];
} else {
gzipData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
if (gzipData == nil) {
DNOT_REACHED();
return nil;
}
NSUInteger length = 0;
do {
NSData* data = [super readData:error];
if (data == nil) {
return nil;
}
_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);
_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);
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
return nil;
}
length += maxLength - _stream.avail_out;
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
}
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;
}
return ARC_AUTORELEASE(gzipData);
}
- (void)close {
deflateEnd(&_stream);
[super close];
}
@end
@interface GCDWebServerResponse () {
@private
NSString* _type;
@@ -36,37 +215,19 @@
NSInteger _status;
NSUInteger _maxAge;
NSMutableDictionary* _headers;
}
@end
@interface GCDWebServerDataResponse () {
@private
NSData* _data;
NSInteger _offset;
}
@end
@interface GCDWebServerFileResponse () {
@private
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@end
@interface GCDWebServerChunkedResponse () {
@private
GCDWebServerChunkBlock _block;
NSData* _chunk;
NSUInteger _offset;
BOOL _terminated;
BOOL _gzipped;
BOOL _chunked;
BOOL _opened;
NSMutableArray* _encoders;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
@end
@implementation GCDWebServerResponse
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers;
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge,
gzipContentEncoding=_gzipped, chunkedTransferEncoding=_chunked, additionalHeaders=_headers;
+ (GCDWebServerResponse*)response {
return ARC_AUTORELEASE([[[self class] alloc] init]);
@@ -79,6 +240,7 @@
_status = 200;
_maxAge = 0;
_headers = [[NSMutableDictionary alloc] init];
_encoders = [[NSMutableArray alloc] init];
}
return self;
}
@@ -86,6 +248,7 @@
- (void)dealloc {
ARC_RELEASE(_type);
ARC_RELEASE(_headers);
ARC_RELEASE(_encoders);
ARC_DEALLOC(super);
}
@@ -98,23 +261,47 @@
return _type ? YES : NO;
}
@end
@implementation GCDWebServerResponse (Subclassing)
- (BOOL)open {
[self doesNotRecognizeSelector:_cmd];
return NO;
- (BOOL)open:(NSError**)error {
return YES;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
[self doesNotRecognizeSelector:_cmd];
return -1;
- (NSData*)readData:(NSError**)error {
return nil;
}
- (BOOL)close {
[self doesNotRecognizeSelector:_cmd];
return NO;
- (void)close {
;
}
- (BOOL)performOpen:(NSError**)error {
if (_opened) {
DNOT_REACHED();
return NO;
}
_opened = YES;
_reader = self;
if (_gzipped) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
ARC_RELEASE(encoder);
_reader = encoder;
}
if (_chunked) {
GCDWebServerChunkEncoder* encoder = [[GCDWebServerChunkEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
ARC_RELEASE(encoder);
_reader = encoder;
}
return [_reader open:error];
}
- (NSData*)performReadData:(NSError**)error {
return [_reader readData:error];
}
- (void)performClose {
[_reader close];
}
@end
@@ -146,6 +333,13 @@
@end
@interface GCDWebServerDataResponse () {
@private
NSData* _data;
BOOL _done;
}
@end
@implementation GCDWebServerDataResponse
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
@@ -161,7 +355,6 @@
if ((self = [super init])) {
_data = ARC_RETAIN(data);
_offset = -1;
self.contentType = type;
self.contentLength = data.length;
@@ -170,33 +363,20 @@
}
- (void)dealloc {
DCHECK(_offset < 0);
ARC_RELEASE(_data);
ARC_DEALLOC(super);
}
- (BOOL)open {
DCHECK(_offset < 0);
_offset = 0;
return YES;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
DCHECK(_offset >= 0);
NSInteger size = 0;
if (_offset < (NSInteger)_data.length) {
size = MIN(_data.length - _offset, length);
bcopy((char*)_data.bytes + _offset, buffer, size);
_offset += size;
- (NSData*)readData:(NSError**)error {
NSData* data;
if (_done) {
data = [NSData data];
} else {
data = _data;
_done = YES;
}
return size;
}
- (BOOL)close {
DCHECK(_offset >= 0);
_offset = -1;
return YES;
return data;
}
@end
@@ -268,6 +448,15 @@
@end
@interface GCDWebServerFileResponse () {
@private
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@end
@implementation GCDWebServerFileResponse
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
@@ -356,13 +545,19 @@
ARC_DEALLOC(super);
}
- (BOOL)open {
static inline NSError* _MakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
}
- (BOOL)open:(NSError**)error {
DCHECK(_file <= 0);
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
if (_file <= 0) {
*error = _MakePosixError(errno);
return NO;
}
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
*error = _MakePosixError(errno);
close(_file);
_file = 0;
return NO;
@@ -370,91 +565,60 @@
return YES;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
- (NSData*)readData:(NSError**)error {
DCHECK(_file > 0);
ssize_t result = read(_file, buffer, MIN(length, _size));
size_t length = MIN((NSUInteger)kFileReadBufferSize, _size);
NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
ssize_t result = read(_file, data.mutableBytes, length);
if (result < 0) {
*error = _MakePosixError(errno);
return nil;
}
if (result > 0) {
[data setLength:result];
_size -= result;
}
return result;
return ARC_AUTORELEASE(data);
}
- (BOOL)close {
- (void)close {
DCHECK(_file > 0);
int result = close(_file);
close(_file);
_file = 0;
return (result == 0 ? YES : NO);
}
@end
@implementation GCDWebServerChunkedResponse
@interface GCDWebServerStreamResponse () {
@private
GCDWebServerStreamBlock _block;
}
@end
+ (GCDWebServerChunkedResponse*)responseWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block {
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type chunkBlock:block]);
@implementation GCDWebServerStreamResponse
+ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
}
- (id)initWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block {
- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
if ((self = [super init])) {
_block = [block copy];
self.contentType = type;
[self setValue:@"chunked" forAdditionalHeader:@"Transfer-Encoding"];
self.chunkedTransferEncoding = YES;
}
return self;
}
- (BOOL)open {
DCHECK(_chunk == nil);
return YES;
}
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
if (_offset >= _chunk.length) {
ARC_RELEASE(_chunk);
_chunk = nil;
}
if (_chunk == nil) {
if (_terminated) {
return 0;
}
NSData* data = _block();
if (data.length > 0) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
_chunk = [[NSMutableData alloc] initWithLength:(hexLength + 2 + data.length + 2)];
char* ptr = (char*)_chunk.bytes;
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
} else {
_chunk = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
_terminated = YES;
}
_offset = 0;
}
NSInteger size = MIN(_chunk.length - _offset, length);
bcopy((char*)_chunk.bytes + _offset, buffer, size);
_offset += size;
return size;
}
- (BOOL)close {
ARC_RELEASE(_chunk);
_chunk = nil;
return YES;
}
- (void)dealloc {
DCHECK(_chunk == nil);
ARC_RELEASE(_chunk);
ARC_RELEASE(_block);
ARC_DEALLOC(super);
}
- (NSData*)readData:(NSError**)error {
return _block(error);
}
@end