mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-03-05 00:00:06 +08:00
Added GCDWebServerBodyWriter protocol
This commit is contained in:
@@ -157,10 +157,11 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
if (buffer) {
|
||||
NSInteger remainingLength = length - dispatch_data_get_size(buffer);
|
||||
if (remainingLength >= 0) {
|
||||
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* bufferChunk, size_t size) {
|
||||
NSInteger result = [_request write:bufferChunk maxLength:size];
|
||||
if (result != (NSInteger)size) {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
|
||||
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* chunkBytes, size_t chunkSize) {
|
||||
NSData* data = [NSData dataWithBytes:chunkBytes length:chunkSize];
|
||||
NSError* error = nil;
|
||||
if (![_request performWriteData:data error:&error]) {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -318,7 +319,7 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
if (response) {
|
||||
NSError* error = nil;
|
||||
if ([response hasBody] && ![response performOpen:&error]) {
|
||||
LOG_WARNING(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||
LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||
} else {
|
||||
_response = ARC_RETAIN(response);
|
||||
}
|
||||
@@ -363,42 +364,45 @@ static dispatch_queue_t _formatterQueue = NULL;
|
||||
}
|
||||
|
||||
- (void)_readRequestBody:(NSData*)initialData {
|
||||
if ([_request open]) {
|
||||
NSError* error = nil;
|
||||
if ([_request performOpen:&error]) {
|
||||
NSInteger length = _request.contentLength;
|
||||
if (initialData.length) {
|
||||
NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
|
||||
if (result == (NSInteger)initialData.length) {
|
||||
if ([_request performWriteData:initialData error:&error]) {
|
||||
length -= initialData.length;
|
||||
DCHECK(length >= 0);
|
||||
} else {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
|
||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
length = -1;
|
||||
}
|
||||
}
|
||||
if (length > 0) {
|
||||
[self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) {
|
||||
|
||||
if (![_request close]) {
|
||||
success = NO;
|
||||
}
|
||||
if (success) {
|
||||
NSError* localError = nil;
|
||||
if ([_request performClose:&localError]) {
|
||||
[self _processRequest];
|
||||
} else {
|
||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self _abortWithStatusCode:500];
|
||||
}
|
||||
|
||||
}];
|
||||
} else if (length == 0) {
|
||||
if ([_request close]) {
|
||||
if ([_request performClose:&error]) {
|
||||
[self _processRequest];
|
||||
} else {
|
||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self _abortWithStatusCode:500];
|
||||
}
|
||||
} else {
|
||||
[_request close]; // Can't do anything with result anyway
|
||||
if (![_request performClose:&error]) {
|
||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
}
|
||||
[self _abortWithStatusCode:500];
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||
[self _abortWithStatusCode:500];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,19 +44,23 @@
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
- (BOOL)open:(NSError**)error {
|
||||
DCHECK(_data == nil);
|
||||
_data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
|
||||
return _data ? YES : NO;
|
||||
if (_data == nil) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
DCHECK(_data != nil);
|
||||
[_data appendBytes:buffer length:length];
|
||||
return length;
|
||||
[_data appendData:data];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
- (BOOL)close:(NSError**)error {
|
||||
DCHECK(_data != nil);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
}
|
||||
@end
|
||||
|
||||
static inline NSError* _MakePosixError(int code) {
|
||||
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
|
||||
}
|
||||
|
||||
@implementation GCDWebServerFileRequest
|
||||
|
||||
@synthesize filePath=_filePath;
|
||||
@@ -53,22 +57,34 @@
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
- (BOOL)open:(NSError**)error {
|
||||
DCHECK(_file == 0);
|
||||
_file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
return (_file > 0 ? YES : NO);
|
||||
if (_file <= 0) {
|
||||
*error = _MakePosixError(errno);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
DCHECK(_file > 0);
|
||||
return write(_file, buffer, length);
|
||||
if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
|
||||
*error = _MakePosixError(errno);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
- (BOOL)close:(NSError**)error {
|
||||
DCHECK(_file > 0);
|
||||
int result = close(_file);
|
||||
_file = -1;
|
||||
return (result == 0 ? YES : NO);
|
||||
if (result < 0) {
|
||||
*error = _MakePosixError(errno);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -204,7 +204,7 @@ static NSData* _dashNewlineData = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
- (BOOL)open:(NSError**)error {
|
||||
DCHECK(_parserData == nil);
|
||||
_parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
|
||||
_parserState = kParserState_Start;
|
||||
@@ -329,13 +329,17 @@ static NSData* _dashNewlineData = nil;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
DCHECK(_parserData != nil);
|
||||
[_parserData appendBytes:buffer length:length];
|
||||
return ([self _parseData] ? length : -1);
|
||||
[_parserData appendBytes:data.bytes length:data.length];
|
||||
if (![self _parseData]) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
- (BOOL)close:(NSError**)error {
|
||||
DCHECK(_parserData != nil);
|
||||
ARC_RELEASE(_parserData);
|
||||
_parserData = nil;
|
||||
@@ -352,7 +356,11 @@ static NSData* _dashNewlineData = nil;
|
||||
}
|
||||
ARC_RELEASE(_tmpPath);
|
||||
_tmpPath = nil;
|
||||
return (_parserState == kParserState_End ? YES : NO);
|
||||
if (_parserState != kParserState_End) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
@@ -99,6 +99,7 @@ extern void GCDLogMessage(long level, NSString* format, ...) NS_FORMAT_FUNCTION(
|
||||
|
||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
||||
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
|
||||
|
||||
extern NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute);
|
||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
||||
@@ -117,6 +118,12 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset)
|
||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
||||
@end
|
||||
|
||||
@interface GCDWebServerRequest ()
|
||||
- (BOOL)performOpen:(NSError**)error;
|
||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
||||
- (BOOL)performClose:(NSError**)error;
|
||||
@end
|
||||
|
||||
@interface GCDWebServerResponse ()
|
||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
||||
- (BOOL)performOpen:(NSError**)error;
|
||||
|
||||
@@ -27,7 +27,13 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface GCDWebServerRequest : NSObject
|
||||
@protocol GCDWebServerBodyWriter <NSObject>
|
||||
- (BOOL)open:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
|
||||
- (BOOL)close:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
|
||||
@end
|
||||
|
||||
@interface GCDWebServerRequest : NSObject <GCDWebServerBodyWriter>
|
||||
@property(nonatomic, readonly) NSString* method;
|
||||
@property(nonatomic, readonly) NSURL* URL;
|
||||
@property(nonatomic, readonly) NSDictionary* headers;
|
||||
@@ -39,9 +45,3 @@
|
||||
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
|
||||
- (BOOL)hasBody; // Convenience method
|
||||
@end
|
||||
|
||||
@interface GCDWebServerRequest (Subclassing)
|
||||
- (BOOL)open; // Implementation required
|
||||
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length; // Implementation required
|
||||
- (BOOL)close; // Implementation required
|
||||
@end
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
NSString* _type;
|
||||
NSUInteger _length;
|
||||
NSRange _range;
|
||||
|
||||
BOOL _opened;
|
||||
NSMutableArray* _decoders;
|
||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -97,6 +101,8 @@
|
||||
LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||
}
|
||||
}
|
||||
|
||||
_decoders = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -108,6 +114,7 @@
|
||||
ARC_RELEASE(_path);
|
||||
ARC_RELEASE(_query);
|
||||
ARC_RELEASE(_type);
|
||||
ARC_RELEASE(_decoders);
|
||||
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
@@ -116,23 +123,37 @@
|
||||
return _type ? YES : NO;
|
||||
}
|
||||
|
||||
@end
|
||||
- (BOOL)open:(NSError**)error {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@implementation GCDWebServerRequest (Subclassing)
|
||||
|
||||
- (BOOL)open {
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return -1;
|
||||
- (BOOL)close:(NSError**)error {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return NO;
|
||||
- (BOOL)performOpen:(NSError**)error {
|
||||
if (_opened) {
|
||||
DNOT_REACHED();
|
||||
return NO;
|
||||
}
|
||||
_opened = YES;
|
||||
|
||||
_writer = self;
|
||||
// TODO: Inject decoders
|
||||
return [_writer open:error];
|
||||
}
|
||||
|
||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
|
||||
return [_writer writeData:data error:error];
|
||||
}
|
||||
|
||||
- (BOOL)performClose:(NSError**)error {
|
||||
return [_writer close:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GCDWebServerBodyReader <NSObject>
|
||||
- (BOOL)open:(NSError**)error;
|
||||
- (NSData*)readData:(NSError**)error; // Return nil on error or empty NSData if at end
|
||||
- (BOOL)open:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
|
||||
- (NSData*)readData:(NSError**)error; // Must return nil on error or empty NSData if at end ("error" is guaranteed to be non-NULL)
|
||||
- (void)close;
|
||||
@end
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
@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) BOOL gzipContentEncoding;
|
||||
@property(nonatomic) BOOL chunkedTransferEncoding;
|
||||
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled;
|
||||
@property(nonatomic, getter=isChunkedTransferEncodingEnabled) BOOL chunkedTransferEncodingEnabled;
|
||||
+ (GCDWebServerResponse*) response;
|
||||
- (id)init;
|
||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;
|
||||
|
||||
@@ -157,12 +157,12 @@
|
||||
}
|
||||
|
||||
- (NSData*)readData:(NSError**)error {
|
||||
NSMutableData* gzipData;
|
||||
NSMutableData* encodedData;
|
||||
if (_finished) {
|
||||
gzipData = [[NSMutableData alloc] init];
|
||||
encodedData = [[NSMutableData alloc] init];
|
||||
} else {
|
||||
gzipData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||
if (gzipData == nil) {
|
||||
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||
if (encodedData == nil) {
|
||||
DNOT_REACHED();
|
||||
return nil;
|
||||
}
|
||||
@@ -175,14 +175,14 @@
|
||||
_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);
|
||||
NSUInteger maxLength = encodedData.length - length;
|
||||
_stream.next_out = (Bytef*)((char*)encodedData.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);
|
||||
ARC_RELEASE(encodedData);
|
||||
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
@@ -190,13 +190,13 @@
|
||||
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
|
||||
encodedData.length = 2 * encodedData.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;
|
||||
encodedData.length = length;
|
||||
}
|
||||
return ARC_AUTORELEASE(gzipData);
|
||||
return ARC_AUTORELEASE(encodedData);
|
||||
}
|
||||
|
||||
- (void)close {
|
||||
@@ -225,7 +225,7 @@
|
||||
@implementation GCDWebServerResponse
|
||||
|
||||
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge,
|
||||
gzipContentEncoding=_gzipped, chunkedTransferEncoding=_chunked, additionalHeaders=_headers;
|
||||
gzipContentEncodingEnabled=_gzipped, chunkedTransferEncodingEnabled=_chunked, additionalHeaders=_headers;
|
||||
|
||||
+ (GCDWebServerResponse*)response {
|
||||
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
||||
@@ -264,6 +264,7 @@
|
||||
}
|
||||
|
||||
- (NSData*)readData:(NSError**)error {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
_block = [block copy];
|
||||
|
||||
self.contentType = type;
|
||||
self.chunkedTransferEncoding = YES;
|
||||
self.chunkedTransferEncodingEnabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -47,17 +47,18 @@
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
if (![super close]) {
|
||||
- (BOOL)close:(NSError**)error {
|
||||
if (![super close:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset");
|
||||
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||
_arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
|
||||
DCHECK(_arguments);
|
||||
ARC_RELEASE(string);
|
||||
|
||||
return (_arguments ? YES : NO);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user