mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-04-24 00:00:04 +08:00
First pass at adding body encoders
This commit is contained in:
@@ -30,7 +30,6 @@
|
||||
#import "GCDWebServerPrivate.h"
|
||||
|
||||
#define kHeadersReadBuffer 1024
|
||||
#define kBodyWriteBufferSize (32 * 1024)
|
||||
|
||||
typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer);
|
||||
typedef void (^ReadDataCompletionBlock)(NSData* data);
|
||||
@@ -234,27 +233,25 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
|
||||
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||
DCHECK([_response hasBody]);
|
||||
void* buffer = malloc(kBodyWriteBufferSize);
|
||||
NSInteger result = [_response read:buffer maxLength:kBodyWriteBufferSize];
|
||||
if (result > 0) {
|
||||
dispatch_data_t wrapper = dispatch_data_create(buffer, result, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
|
||||
[self _writeBuffer:wrapper withCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
[self _writeBodyWithCompletionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
ARC_DISPATCH_RELEASE(wrapper);
|
||||
} else if (result < 0) {
|
||||
LOG_ERROR(@"Failed reading response body on socket %i (error %i)", _socket, (int)result);
|
||||
block(NO);
|
||||
free(buffer);
|
||||
NSError* error = nil;
|
||||
NSData* data = [_response performReadData:&error];
|
||||
if (data) {
|
||||
if (data.length) {
|
||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
[self _writeBodyWithCompletionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
block(YES);
|
||||
}
|
||||
} else {
|
||||
block(YES);
|
||||
free(buffer);
|
||||
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,8 +315,13 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
DCHECK(_responseMessage == NULL);
|
||||
|
||||
GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
|
||||
if (![response hasBody] || [response open]) {
|
||||
_response = ARC_RETAIN(response);
|
||||
if (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) {
|
||||
@@ -344,12 +346,13 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
if ([_response hasBody]) {
|
||||
[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]) {
|
||||
[_response close]; // Can't do anything with result anyway
|
||||
[_response performClose];
|
||||
}
|
||||
|
||||
}];
|
||||
@@ -537,7 +540,6 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
GCDWebServerResponse* response = nil;
|
||||
@try {
|
||||
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) {
|
||||
LOG_EXCEPTION(exception);
|
||||
|
||||
@@ -104,3 +104,10 @@ extern void GCDLogMessage(long level, NSString* format, ...) NS_FORMAT_FUNCTION(
|
||||
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
||||
@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>
|
||||
|
||||
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) 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, readonly) NSDictionary* additionalHeaders;
|
||||
@property(nonatomic) BOOL gzipContentEncoding;
|
||||
@property(nonatomic) BOOL chunkedTransferEncoding;
|
||||
+ (GCDWebServerResponse*) response;
|
||||
- (id)init;
|
||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
||||
- (BOOL)hasBody; // Convenience method
|
||||
@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)
|
||||
+ (GCDWebServerResponse*)responseWithStatusCode:(NSInteger)statusCode;
|
||||
+ (GCDWebServerResponse*)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
||||
@@ -83,7 +84,7 @@ typedef NSData* (^GCDWebServerChunkBlock)();
|
||||
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
||||
@end
|
||||
|
||||
@interface GCDWebServerChunkedResponse : GCDWebServerResponse // Use chunked transfer encoding
|
||||
+ (GCDWebServerChunkedResponse*)responseWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block;
|
||||
- (id)initWithContentType:(NSString*)type chunkBlock:(GCDWebServerChunkBlock)block; // Return nil when done
|
||||
@interface GCDWebServerStreamResponse : GCDWebServerResponse // Forces chunked transfer encoding
|
||||
+ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
||||
- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; // Block must return empty NSData when done or nil on error
|
||||
@end
|
||||
|
||||
+285
-121
@@ -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
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E22112981690B7AA0048D2B2 /* CFNetwork.framework */; };
|
||||
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129A1690B7B10048D2B2 /* UIKit.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 */; };
|
||||
E2BE850B18E77ECA0061360B /* 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; };
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -111,6 +115,7 @@
|
||||
E2BE851118E79DAF0061360B /* SystemConfiguration.framework in Frameworks */,
|
||||
E208D1B3167BB17E00500836 /* CoreServices.framework in Frameworks */,
|
||||
E208D149167B76B700500836 /* CFNetwork.framework in Frameworks */,
|
||||
E2B0D4A718F13495009A7927 /* libz.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -121,6 +126,7 @@
|
||||
E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */,
|
||||
E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */,
|
||||
E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */,
|
||||
E2B0D4A918F134A8009A7927 /* libz.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -191,6 +197,7 @@
|
||||
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */,
|
||||
E221129A1690B7B10048D2B2 /* UIKit.framework */,
|
||||
E22112981690B7AA0048D2B2 /* CFNetwork.framework */,
|
||||
E2B0D4A818F134A8009A7927 /* libz.dylib */,
|
||||
);
|
||||
name = "iOS Frameworks and Libraries";
|
||||
sourceTree = "<group>";
|
||||
@@ -201,6 +208,7 @@
|
||||
E2BE851018E79DAF0061360B /* SystemConfiguration.framework */,
|
||||
E208D1B2167BB17E00500836 /* CoreServices.framework */,
|
||||
E208D148167B76B700500836 /* CFNetwork.framework */,
|
||||
E2B0D4A618F13495009A7927 /* libz.dylib */,
|
||||
);
|
||||
name = "Mac Frameworks and Libraries";
|
||||
sourceTree = "<group>";
|
||||
@@ -366,9 +374,10 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Wall",
|
||||
"-Wshadow",
|
||||
"-Weverything",
|
||||
"-Wshadow",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wno-explicit-ownership-type",
|
||||
"-Wno-gnu-statement-expression",
|
||||
"-Wno-direct-ivar-access",
|
||||
"-Wno-implicit-retain-self",
|
||||
|
||||
Reference in New Issue
Block a user