mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-04-05 00:04:17 +08:00
First pass at adding body encoders
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user