mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-04-24 00:00:04 +08:00
Moved response body chunked transfer encoding to GCDWebServerConnection
This commit is contained in:
@@ -44,6 +44,7 @@ typedef void (^WriteBodyCompletionBlock)(BOOL success);
|
|||||||
static NSData* _CRLFData = nil;
|
static NSData* _CRLFData = nil;
|
||||||
static NSData* _CRLFCRLFData = nil;
|
static NSData* _CRLFCRLFData = nil;
|
||||||
static NSData* _continueData = nil;
|
static NSData* _continueData = nil;
|
||||||
|
static NSData* _lastChunkData = nil;
|
||||||
static NSDateFormatter* _dateFormatter = nil;
|
static NSDateFormatter* _dateFormatter = nil;
|
||||||
static dispatch_queue_t _formatterQueue = NULL;
|
static dispatch_queue_t _formatterQueue = NULL;
|
||||||
|
|
||||||
@@ -308,6 +309,26 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
NSData* data = [_response performReadData:&error];
|
NSData* data = [_response performReadData:&error];
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||||
|
size_t hexLength = strlen(hexString);
|
||||||
|
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||||
|
if (chunk == nil) {
|
||||||
|
LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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';
|
||||||
|
data = chunk;
|
||||||
|
}
|
||||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -316,10 +337,18 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
block(success);
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
block(YES);
|
block(YES);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||||
block(NO);
|
block(NO);
|
||||||
@@ -351,6 +380,9 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
CFRelease(message);
|
CFRelease(message);
|
||||||
DCHECK(_continueData);
|
DCHECK(_continueData);
|
||||||
}
|
}
|
||||||
|
if (_lastChunkData == nil) {
|
||||||
|
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
||||||
|
}
|
||||||
if (_dateFormatter == nil) {
|
if (_dateFormatter == nil) {
|
||||||
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
||||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||||
@@ -412,6 +444,9 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if (_response.contentLength != NSNotFound) {
|
if (_response.contentLength != NSNotFound) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
|
||||||
}
|
}
|
||||||
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
|
||||||
|
}
|
||||||
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (ARC_BRIDGE CFStringRef)key, (ARC_BRIDGE CFStringRef)obj);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (ARC_BRIDGE CFStringRef)key, (ARC_BRIDGE CFStringRef)obj);
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset)
|
|||||||
|
|
||||||
@interface GCDWebServerResponse ()
|
@interface GCDWebServerResponse ()
|
||||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
||||||
|
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
- (NSData*)performReadData:(NSError**)error;
|
- (NSData*)performReadData:(NSError**)error;
|
||||||
- (void)performClose;
|
- (void)performClose;
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
@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, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; // Default is disabled
|
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; // Default is disabled
|
||||||
@property(nonatomic, getter=isChunkedTransferEncodingEnabled) BOOL chunkedTransferEncodingEnabled; // Default is disabled
|
|
||||||
+ (GCDWebServerResponse*) response;
|
+ (GCDWebServerResponse*) response;
|
||||||
- (id)init;
|
- (id)init;
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
||||||
|
|||||||
@@ -36,9 +36,6 @@
|
|||||||
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
|
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerChunkEncoder : GCDWebServerBodyEncoder
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
|
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -73,59 +70,6 @@
|
|||||||
|
|
||||||
@end
|
@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 since body length is determined by chunked transfer encoding
|
|
||||||
[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 () {
|
@interface GCDWebServerGZipEncoder () {
|
||||||
@private
|
@private
|
||||||
z_stream _stream;
|
z_stream _stream;
|
||||||
@@ -137,7 +81,7 @@
|
|||||||
|
|
||||||
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
|
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
|
||||||
if ((self = [super initWithResponse:response reader:reader])) {
|
if ((self = [super initWithResponse:response reader:reader])) {
|
||||||
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set since body length is determined by closing the connection
|
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set since we don't know it (client will determine body length when connection is closed)
|
||||||
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
|
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@@ -213,8 +157,8 @@
|
|||||||
NSInteger _status;
|
NSInteger _status;
|
||||||
NSUInteger _maxAge;
|
NSUInteger _maxAge;
|
||||||
NSMutableDictionary* _headers;
|
NSMutableDictionary* _headers;
|
||||||
BOOL _gzipped;
|
|
||||||
BOOL _chunked;
|
BOOL _chunked;
|
||||||
|
BOOL _gzipped;
|
||||||
|
|
||||||
BOOL _opened;
|
BOOL _opened;
|
||||||
NSMutableArray* _encoders;
|
NSMutableArray* _encoders;
|
||||||
@@ -225,7 +169,7 @@
|
|||||||
@implementation GCDWebServerResponse
|
@implementation GCDWebServerResponse
|
||||||
|
|
||||||
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge,
|
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge,
|
||||||
gzipContentEncodingEnabled=_gzipped, chunkedTransferEncodingEnabled=_chunked, additionalHeaders=_headers;
|
gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers;
|
||||||
|
|
||||||
+ (GCDWebServerResponse*)response {
|
+ (GCDWebServerResponse*)response {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
||||||
@@ -259,6 +203,10 @@
|
|||||||
return _type ? YES : NO;
|
return _type ? YES : NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)usesChunkedTransferEncoding {
|
||||||
|
return (_type != nil) && (_length == NSNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -286,12 +234,6 @@
|
|||||||
ARC_RELEASE(encoder);
|
ARC_RELEASE(encoder);
|
||||||
_reader = 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];
|
return [_reader open:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
_block = [block copy];
|
_block = [block copy];
|
||||||
|
|
||||||
self.contentType = type;
|
self.contentType = type;
|
||||||
self.chunkedTransferEncodingEnabled = YES;
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user