First pass at adding body encoders
This commit is contained in:
@ -30,7 +30,6 @@
|
|||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
#define kHeadersReadBuffer 1024
|
#define kHeadersReadBuffer 1024
|
||||||
#define kBodyWriteBufferSize (32 * 1024)
|
|
||||||
|
|
||||||
typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer);
|
typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer);
|
||||||
typedef void (^ReadDataCompletionBlock)(NSData* data);
|
typedef void (^ReadDataCompletionBlock)(NSData* data);
|
||||||
@ -234,27 +233,25 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
|
|
||||||
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||||
DCHECK([_response hasBody]);
|
DCHECK([_response hasBody]);
|
||||||
void* buffer = malloc(kBodyWriteBufferSize);
|
NSError* error = nil;
|
||||||
NSInteger result = [_response read:buffer maxLength:kBodyWriteBufferSize];
|
NSData* data = [_response performReadData:&error];
|
||||||
if (result > 0) {
|
if (data) {
|
||||||
dispatch_data_t wrapper = dispatch_data_create(buffer, result, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
|
if (data.length) {
|
||||||
[self _writeBuffer:wrapper withCompletionBlock:^(BOOL success) {
|
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
[self _writeBodyWithCompletionBlock:block];
|
[self _writeBodyWithCompletionBlock:block];
|
||||||
} else {
|
} else {
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
ARC_DISPATCH_RELEASE(wrapper);
|
} else {
|
||||||
} else if (result < 0) {
|
block(YES);
|
||||||
LOG_ERROR(@"Failed reading response body on socket %i (error %i)", _socket, (int)result);
|
}
|
||||||
block(NO);
|
|
||||||
free(buffer);
|
|
||||||
} else {
|
} else {
|
||||||
block(YES);
|
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||||
free(buffer);
|
block(NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,8 +315,13 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
DCHECK(_responseMessage == NULL);
|
DCHECK(_responseMessage == NULL);
|
||||||
|
|
||||||
GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
|
GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
|
||||||
if (![response hasBody] || [response open]) {
|
if (response) {
|
||||||
_response = ARC_RETAIN(response);
|
NSError* error = nil;
|
||||||
|
if ([response hasBody] && ![response performOpen:&error]) {
|
||||||
|
LOG_WARNING(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||||
|
} else {
|
||||||
|
_response = ARC_RETAIN(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_response) {
|
if (_response) {
|
||||||
@ -344,12 +346,13 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
if ([_response hasBody]) {
|
if ([_response hasBody]) {
|
||||||
[self _writeBodyWithCompletionBlock:^(BOOL successInner) {
|
[self _writeBodyWithCompletionBlock:^(BOOL successInner) {
|
||||||
|
|
||||||
[_response close]; // Can't do anything with result anyway
|
[_response performClose];
|
||||||
|
LOG_VERBOSE(@"%@ | %@ \"%@ %@\" %i %lu", self.localAddressString, self.remoteAddressString, _request.method, _request.path, (int)_response.statusCode, (unsigned long)_bytesWritten);
|
||||||
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
} else if ([_response hasBody]) {
|
} else if ([_response hasBody]) {
|
||||||
[_response close]; // Can't do anything with result anyway
|
[_response performClose];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
@ -537,7 +540,6 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
@try {
|
@try {
|
||||||
response = block(request);
|
response = block(request);
|
||||||
LOG_VERBOSE(@"%@ | %@ \"%@ %@\" %i %lu", self.localAddressString, self.remoteAddressString, _request.method, _request.path, (int)response.statusCode, (unsigned long)(response.contentLength != NSNotFound ? response.contentLength : 0));
|
|
||||||
}
|
}
|
||||||
@catch (NSException* exception) {
|
@catch (NSException* exception) {
|
||||||
LOG_EXCEPTION(exception);
|
LOG_EXCEPTION(exception);
|
||||||
|
|||||||
@ -104,3 +104,10 @@ extern void GCDLogMessage(long level, NSString* format, ...) NS_FORMAT_FUNCTION(
|
|||||||
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
||||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerResponse ()
|
||||||
|
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
||||||
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
|
- (NSData*)performReadData:(NSError**)error;
|
||||||
|
- (void)performClose;
|
||||||
|
@end
|
||||||
|
|||||||
@ -27,26 +27,27 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
typedef NSData* (^GCDWebServerChunkBlock)();
|
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
|
||||||
|
|
||||||
@interface GCDWebServerResponse : NSObject
|
@protocol GCDWebServerBodyReader <NSObject>
|
||||||
|
- (BOOL)open:(NSError**)error;
|
||||||
|
- (NSData*)readData:(NSError**)error; // Return nil on error or empty NSData if at end
|
||||||
|
- (void)close;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerResponse : NSObject <GCDWebServerBodyReader>
|
||||||
@property(nonatomic, copy) NSString* contentType; // Default is nil i.e. no body
|
@property(nonatomic, copy) NSString* contentType; // Default is nil i.e. no body
|
||||||
@property(nonatomic) NSUInteger contentLength; // Default is NSNotFound i.e. undefined
|
@property(nonatomic) NSUInteger contentLength; // Default is NSNotFound i.e. undefined
|
||||||
@property(nonatomic) NSInteger statusCode; // Default is 200
|
@property(nonatomic) NSInteger statusCode; // Default is 200
|
||||||
@property(nonatomic) NSUInteger cacheControlMaxAge; // Default is 0 seconds i.e. "no-cache"
|
@property(nonatomic) NSUInteger cacheControlMaxAge; // Default is 0 seconds i.e. "no-cache"
|
||||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
@property(nonatomic) BOOL gzipContentEncoding;
|
||||||
|
@property(nonatomic) BOOL chunkedTransferEncoding;
|
||||||
+ (GCDWebServerResponse*) response;
|
+ (GCDWebServerResponse*) response;
|
||||||
- (id)init;
|
- (id)init;
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
||||||
- (BOOL)hasBody; // Convenience method
|
- (BOOL)hasBody; // Convenience method
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerResponse (Subclassing)
|
|
||||||
- (BOOL)open; // Implementation required
|
|
||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length; // Implementation required
|
|
||||||
- (BOOL)close; // Implementation required
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerResponse (Extensions)
|
@interface GCDWebServerResponse (Extensions)
|
||||||
+ (GCDWebServerResponse*)responseWithStatusCode:(NSInteger)statusCode;
|
+ (GCDWebServerResponse*)responseWithStatusCode:(NSInteger)statusCode;
|
||||||
+ (GCDWebServerResponse*)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
+ (GCDWebServerResponse*)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
||||||
@ -83,7 +84,7 @@ typedef NSData* (^GCDWebServerChunkBlock)();
|
|||||||
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerChunkedResponse : GCDWebServerResponse // Use chunked transfer encoding
|
@interface GCDWebServerStreamResponse : GCDWebServerResponse // Forces chunked transfer encoding
|
||||||
+ (GCDWebServerChunkedResponse*)responseWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block;
|
+ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
||||||
- (id)initWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block; // Return nil when done
|
- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; // Block must return empty NSData when done or nil on error
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -26,9 +26,188 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <sys/stat.h>
|
#import <sys/stat.h>
|
||||||
|
#import <zlib.h>
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.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 () {
|
@interface GCDWebServerResponse () {
|
||||||
@private
|
@private
|
||||||
NSString* _type;
|
NSString* _type;
|
||||||
@ -36,37 +215,19 @@
|
|||||||
NSInteger _status;
|
NSInteger _status;
|
||||||
NSUInteger _maxAge;
|
NSUInteger _maxAge;
|
||||||
NSMutableDictionary* _headers;
|
NSMutableDictionary* _headers;
|
||||||
}
|
BOOL _gzipped;
|
||||||
@end
|
BOOL _chunked;
|
||||||
|
|
||||||
@interface GCDWebServerDataResponse () {
|
BOOL _opened;
|
||||||
@private
|
NSMutableArray* _encoders;
|
||||||
NSData* _data;
|
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerResponse
|
@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 {
|
+ (GCDWebServerResponse*)response {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
||||||
@ -79,6 +240,7 @@
|
|||||||
_status = 200;
|
_status = 200;
|
||||||
_maxAge = 0;
|
_maxAge = 0;
|
||||||
_headers = [[NSMutableDictionary alloc] init];
|
_headers = [[NSMutableDictionary alloc] init];
|
||||||
|
_encoders = [[NSMutableArray alloc] init];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -86,6 +248,7 @@
|
|||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
ARC_RELEASE(_type);
|
ARC_RELEASE(_type);
|
||||||
ARC_RELEASE(_headers);
|
ARC_RELEASE(_headers);
|
||||||
|
ARC_RELEASE(_encoders);
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
ARC_DEALLOC(super);
|
||||||
}
|
}
|
||||||
@ -98,23 +261,47 @@
|
|||||||
return _type ? YES : NO;
|
return _type ? YES : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
- (BOOL)open:(NSError**)error {
|
||||||
|
return YES;
|
||||||
@implementation GCDWebServerResponse (Subclassing)
|
|
||||||
|
|
||||||
- (BOOL)open {
|
|
||||||
[self doesNotRecognizeSelector:_cmd];
|
|
||||||
return NO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
- (NSData*)readData:(NSError**)error {
|
||||||
[self doesNotRecognizeSelector:_cmd];
|
return nil;
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)close {
|
- (void)close {
|
||||||
[self doesNotRecognizeSelector:_cmd];
|
;
|
||||||
return NO;
|
}
|
||||||
|
|
||||||
|
- (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
|
@end
|
||||||
@ -146,6 +333,13 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerDataResponse () {
|
||||||
|
@private
|
||||||
|
NSData* _data;
|
||||||
|
BOOL _done;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerDataResponse
|
@implementation GCDWebServerDataResponse
|
||||||
|
|
||||||
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
|
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
|
||||||
@ -161,7 +355,6 @@
|
|||||||
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_data = ARC_RETAIN(data);
|
_data = ARC_RETAIN(data);
|
||||||
_offset = -1;
|
|
||||||
|
|
||||||
self.contentType = type;
|
self.contentType = type;
|
||||||
self.contentLength = data.length;
|
self.contentLength = data.length;
|
||||||
@ -170,33 +363,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
DCHECK(_offset < 0);
|
|
||||||
ARC_RELEASE(_data);
|
ARC_RELEASE(_data);
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
ARC_DEALLOC(super);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)open {
|
- (NSData*)readData:(NSError**)error {
|
||||||
DCHECK(_offset < 0);
|
NSData* data;
|
||||||
_offset = 0;
|
if (_done) {
|
||||||
return YES;
|
data = [NSData data];
|
||||||
}
|
} else {
|
||||||
|
data = _data;
|
||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
_done = YES;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
return size;
|
return data;
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)close {
|
|
||||||
DCHECK(_offset >= 0);
|
|
||||||
_offset = -1;
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -268,6 +448,15 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerFileResponse () {
|
||||||
|
@private
|
||||||
|
NSString* _path;
|
||||||
|
NSUInteger _offset;
|
||||||
|
NSUInteger _size;
|
||||||
|
int _file;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerFileResponse
|
@implementation GCDWebServerFileResponse
|
||||||
|
|
||||||
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
|
||||||
@ -356,13 +545,19 @@
|
|||||||
ARC_DEALLOC(super);
|
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);
|
DCHECK(_file <= 0);
|
||||||
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
||||||
if (_file <= 0) {
|
if (_file <= 0) {
|
||||||
|
*error = _MakePosixError(errno);
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
|
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
|
||||||
|
*error = _MakePosixError(errno);
|
||||||
close(_file);
|
close(_file);
|
||||||
_file = 0;
|
_file = 0;
|
||||||
return NO;
|
return NO;
|
||||||
@ -370,91 +565,60 @@
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
- (NSData*)readData:(NSError**)error {
|
||||||
DCHECK(_file > 0);
|
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) {
|
if (result > 0) {
|
||||||
|
[data setLength:result];
|
||||||
_size -= result;
|
_size -= result;
|
||||||
}
|
}
|
||||||
return result;
|
return ARC_AUTORELEASE(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)close {
|
- (void)close {
|
||||||
DCHECK(_file > 0);
|
DCHECK(_file > 0);
|
||||||
int result = close(_file);
|
close(_file);
|
||||||
_file = 0;
|
_file = 0;
|
||||||
return (result == 0 ? YES : NO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerChunkedResponse
|
@interface GCDWebServerStreamResponse () {
|
||||||
|
@private
|
||||||
|
GCDWebServerStreamBlock _block;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
+ (GCDWebServerChunkedResponse*)responseWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block {
|
@implementation GCDWebServerStreamResponse
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type chunkBlock:block]);
|
|
||||||
|
+ (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])) {
|
if ((self = [super init])) {
|
||||||
_block = [block copy];
|
_block = [block copy];
|
||||||
|
|
||||||
self.contentType = type;
|
self.contentType = type;
|
||||||
[self setValue:@"chunked" forAdditionalHeader:@"Transfer-Encoding"];
|
self.chunkedTransferEncoding = YES;
|
||||||
}
|
}
|
||||||
return self;
|
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 {
|
- (void)dealloc {
|
||||||
DCHECK(_chunk == nil);
|
ARC_RELEASE(_block);
|
||||||
ARC_RELEASE(_chunk);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
ARC_DEALLOC(super);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSData*)readData:(NSError**)error {
|
||||||
|
return _block(error);
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -38,6 +38,8 @@
|
|||||||
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E22112981690B7AA0048D2B2 /* CFNetwork.framework */; };
|
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E22112981690B7AA0048D2B2 /* CFNetwork.framework */; };
|
||||||
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129A1690B7B10048D2B2 /* UIKit.framework */; };
|
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129A1690B7B10048D2B2 /* UIKit.framework */; };
|
||||||
E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */; };
|
E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */; };
|
||||||
|
E2B0D4A718F13495009A7927 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2B0D4A618F13495009A7927 /* libz.dylib */; };
|
||||||
|
E2B0D4A918F134A8009A7927 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2B0D4A818F134A8009A7927 /* libz.dylib */; };
|
||||||
E2BE850A18E77ECA0061360B /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */; };
|
E2BE850A18E77ECA0061360B /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */; };
|
||||||
E2BE850B18E77ECA0061360B /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE850918E77ECA0061360B /* GCDWebUploader.m */; };
|
E2BE850B18E77ECA0061360B /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE850918E77ECA0061360B /* GCDWebUploader.m */; };
|
||||||
E2BE850C18E785940061360B /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE850918E77ECA0061360B /* GCDWebUploader.m */; };
|
E2BE850C18E785940061360B /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = E2BE850918E77ECA0061360B /* GCDWebUploader.m */; };
|
||||||
@ -97,6 +99,8 @@
|
|||||||
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
|
E2B0D4A618F13495009A7927 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
|
||||||
|
E2B0D4A818F134A8009A7927 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; };
|
||||||
E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = GCDWebUploader.bundle; sourceTree = "<group>"; };
|
E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = GCDWebUploader.bundle; sourceTree = "<group>"; };
|
||||||
E2BE850818E77ECA0061360B /* GCDWebUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebUploader.h; sourceTree = "<group>"; };
|
E2BE850818E77ECA0061360B /* GCDWebUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebUploader.h; sourceTree = "<group>"; };
|
||||||
E2BE850918E77ECA0061360B /* GCDWebUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebUploader.m; sourceTree = "<group>"; };
|
E2BE850918E77ECA0061360B /* GCDWebUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebUploader.m; sourceTree = "<group>"; };
|
||||||
@ -111,6 +115,7 @@
|
|||||||
E2BE851118E79DAF0061360B /* SystemConfiguration.framework in Frameworks */,
|
E2BE851118E79DAF0061360B /* SystemConfiguration.framework in Frameworks */,
|
||||||
E208D1B3167BB17E00500836 /* CoreServices.framework in Frameworks */,
|
E208D1B3167BB17E00500836 /* CoreServices.framework in Frameworks */,
|
||||||
E208D149167B76B700500836 /* CFNetwork.framework in Frameworks */,
|
E208D149167B76B700500836 /* CFNetwork.framework in Frameworks */,
|
||||||
|
E2B0D4A718F13495009A7927 /* libz.dylib in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -121,6 +126,7 @@
|
|||||||
E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */,
|
E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */,
|
||||||
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */,
|
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */,
|
||||||
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */,
|
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */,
|
||||||
|
E2B0D4A918F134A8009A7927 /* libz.dylib in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -191,6 +197,7 @@
|
|||||||
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */,
|
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */,
|
||||||
E221129A1690B7B10048D2B2 /* UIKit.framework */,
|
E221129A1690B7B10048D2B2 /* UIKit.framework */,
|
||||||
E22112981690B7AA0048D2B2 /* CFNetwork.framework */,
|
E22112981690B7AA0048D2B2 /* CFNetwork.framework */,
|
||||||
|
E2B0D4A818F134A8009A7927 /* libz.dylib */,
|
||||||
);
|
);
|
||||||
name = "iOS Frameworks and Libraries";
|
name = "iOS Frameworks and Libraries";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -201,6 +208,7 @@
|
|||||||
E2BE851018E79DAF0061360B /* SystemConfiguration.framework */,
|
E2BE851018E79DAF0061360B /* SystemConfiguration.framework */,
|
||||||
E208D1B2167BB17E00500836 /* CoreServices.framework */,
|
E208D1B2167BB17E00500836 /* CoreServices.framework */,
|
||||||
E208D148167B76B700500836 /* CFNetwork.framework */,
|
E208D148167B76B700500836 /* CFNetwork.framework */,
|
||||||
|
E2B0D4A618F13495009A7927 /* libz.dylib */,
|
||||||
);
|
);
|
||||||
name = "Mac Frameworks and Libraries";
|
name = "Mac Frameworks and Libraries";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -366,9 +374,10 @@
|
|||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
WARNING_CFLAGS = (
|
WARNING_CFLAGS = (
|
||||||
"-Wall",
|
"-Wall",
|
||||||
"-Wshadow",
|
|
||||||
"-Weverything",
|
"-Weverything",
|
||||||
|
"-Wshadow",
|
||||||
"-Wshorten-64-to-32",
|
"-Wshorten-64-to-32",
|
||||||
|
"-Wno-explicit-ownership-type",
|
||||||
"-Wno-gnu-statement-expression",
|
"-Wno-gnu-statement-expression",
|
||||||
"-Wno-direct-ivar-access",
|
"-Wno-direct-ivar-access",
|
||||||
"-Wno-implicit-retain-self",
|
"-Wno-implicit-retain-self",
|
||||||
|
|||||||
Reference in New Issue
Block a user