123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * The name of Pierre-Olivier Latour may not be used to endorse
- or promote products derived from this software without specific
- prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- #import "GCDWebServerPrivate.h"
- #define kHeadersReadBuffer 1024
- #define kBodyWriteBufferSize (32 * 1024)
- typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer);
- typedef void (^ReadDataCompletionBlock)(NSData* data);
- typedef void (^ReadHeadersCompletionBlock)(NSData* extraData);
- typedef void (^ReadBodyCompletionBlock)(BOOL success);
- typedef void (^WriteBufferCompletionBlock)(BOOL success);
- typedef void (^WriteDataCompletionBlock)(BOOL success);
- typedef void (^WriteHeadersCompletionBlock)(BOOL success);
- typedef void (^WriteBodyCompletionBlock)(BOOL success);
- static NSData* _separatorData = nil;
- static NSData* _continueData = nil;
- static NSDateFormatter* _dateFormatter = nil;
- static dispatch_queue_t _formatterQueue = NULL;
- @implementation GCDWebServerConnection (Read)
- - (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
- dispatch_read(_socket, length, kGCDWebServerGCDQueue, ^(dispatch_data_t buffer, int error) {
-
- @autoreleasepool {
- if (error == 0) {
- size_t size = dispatch_data_get_size(buffer);
- if (size > 0) {
- LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket);
- _bytesRead += size;
- block(buffer);
- } else {
- if (_bytesRead > 0) {
- LOG_ERROR(@"No more data available on socket %i", _socket);
- } else {
- LOG_WARNING(@"No data received from socket %i", _socket);
- }
- block(NULL);
- }
- } else {
- LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
- block(NULL);
- }
- }
-
- });
- }
- - (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block {
- [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) {
-
- if (buffer) {
- NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(buffer)];
- dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
- [data appendBytes:buffer length:size];
- return true;
- });
- block(data);
- ARC_RELEASE(data);
- } else {
- block(nil);
- }
-
- }];
- }
- - (void)_readHeadersWithCompletionBlock:(ReadHeadersCompletionBlock)block {
- DCHECK(_requestMessage);
- [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) {
-
- if (buffer) {
- NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer];
- dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
- [data appendBytes:buffer length:size];
- return true;
- });
- NSRange range = [data rangeOfData:_separatorData options:0 range:NSMakeRange(0, data.length)];
- if (range.location == NSNotFound) {
- if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, data.length)) {
- [self _readHeadersWithCompletionBlock:block];
- } else {
- LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
- block(nil);
- }
- } else {
- NSUInteger length = range.location + range.length;
- if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, length)) {
- if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
- block([data subdataWithRange:NSMakeRange(length, data.length - length)]);
- } else {
- LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
- block(nil);
- }
- } else {
- LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
- block(nil);
- }
- }
- } else {
- block(nil);
- }
-
- }];
- }
- - (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
- DCHECK([_request hasBody]);
- [self _readBufferWithLength:length completionBlock:^(dispatch_data_t buffer) {
-
- 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* buffer, size_t size) {
- NSInteger result = [_request write:buffer maxLength:size];
- if (result != size) {
- LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
- return false;
- }
- return true;
- });
- if (success) {
- if (remainingLength > 0) {
- [self _readBodyWithRemainingLength:remainingLength completionBlock:block];
- } else {
- block(YES);
- }
- } else {
- block(NO);
- }
- } else {
- DNOT_REACHED();
- block(NO);
- }
- } else {
- block(NO);
- }
-
- }];
- }
- @end
- @implementation GCDWebServerConnection (Write)
- - (void)_writeBuffer:(dispatch_data_t)buffer withCompletionBlock:(WriteBufferCompletionBlock)block {
- size_t size = dispatch_data_get_size(buffer);
- dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t data, int error) {
-
- @autoreleasepool {
- if (error == 0) {
- DCHECK(data == NULL);
- LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket);
- _bytesWritten += size;
- block(YES);
- } else {
- LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
- block(NO);
- }
- }
-
- });
- }
- - (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
- #if !__has_feature(objc_arc)
- [data retain];
- #endif
- dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_main_queue(), ^{
- #if __has_feature(objc_arc)
- [data self]; // Keeps ARC from releasing data too early
- #else
- [data release];
- #endif
- });
- [self _writeBuffer:buffer withCompletionBlock:block];
- ARC_DISPATCH_RELEASE(buffer);
- }
- - (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
- DCHECK(_responseMessage);
- CFDataRef message = CFHTTPMessageCopySerializedMessage(_responseMessage);
- [self _writeData:(ARC_BRIDGE NSData*)message withCompletionBlock:block];
- CFRelease(message);
- }
- - (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);
- } else {
- block(YES);
- free(buffer);
- }
- }
- @end
- @implementation GCDWebServerConnection
- @synthesize server=_server, address=_address, totalBytesRead=_bytesRead, totalBytesWritten=_bytesWritten;
- + (void)initialize {
- if (_separatorData == nil) {
- _separatorData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
- DCHECK(_separatorData);
- }
- if (_continueData == nil) {
- CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
- #if __has_feature(objc_arc)
- _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
- #else
- _continueData = (NSData*)CFHTTPMessageCopySerializedMessage(message);
- #endif
- CFRelease(message);
- DCHECK(_continueData);
- }
- if (_dateFormatter == nil) {
- DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
- _dateFormatter = [[NSDateFormatter alloc] init];
- _dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
- _dateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
- _dateFormatter.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
- DCHECK(_dateFormatter);
- }
- if (_formatterQueue == NULL) {
- _formatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
- DCHECK(_formatterQueue);
- }
- }
- - (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
- _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)[[_server class] serverName]);
- dispatch_sync(_formatterQueue, ^{
- NSString* date = [_dateFormatter stringFromDate:[NSDate date]];
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)date);
- });
- }
- - (void)_abortWithStatusCode:(NSUInteger)statusCode {
- DCHECK(_responseMessage == NULL);
- DCHECK((statusCode >= 400) && (statusCode < 600));
- [self _initializeResponseHeadersWithStatusCode:statusCode];
- [self _writeHeadersWithCompletionBlock:^(BOOL success) {
- ; // Nothing more to do
- }];
- LOG_DEBUG(@"Connection aborted with status code %i on socket %i", statusCode, _socket);
- }
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- - (void)_processRequest {
- DCHECK(_responseMessage == NULL);
-
- GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
- if (![response hasBody] || [response open]) {
- _response = ARC_RETAIN(response);
- }
-
- if (_response) {
- [self _initializeResponseHeadersWithStatusCode:_response.statusCode];
- NSUInteger maxAge = _response.cacheControlMaxAge;
- if (maxAge > 0) {
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)maxAge]);
- } else {
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
- }
- [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, (ARC_BRIDGE CFStringRef)key, (ARC_BRIDGE CFStringRef)obj);
- }];
- if ([_response hasBody]) {
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)_response.contentType);
- CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%i", (int)_response.contentLength]);
- }
- [self _writeHeadersWithCompletionBlock:^(BOOL success) {
-
- if (success) {
- if ([_response hasBody]) {
- [self _writeBodyWithCompletionBlock:^(BOOL success) {
-
- [_response close]; // Can't do anything with result anyway
-
- }];
- }
- } else if ([_response hasBody]) {
- [_response close]; // Can't do anything with result anyway
- }
-
- }];
- } else {
- [self _abortWithStatusCode:500];
- }
-
- }
- - (void)_readRequestBody:(NSData*)initialData {
- if ([_request open]) {
- NSInteger length = _request.contentLength;
- if (initialData.length) {
- NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
- if (result == initialData.length) {
- length -= initialData.length;
- DCHECK(length >= 0);
- } else {
- LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
- length = -1;
- }
- }
- if (length > 0) {
- [self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) {
-
- if (![_request close]) {
- success = NO;
- }
- if (success) {
- [self _processRequest];
- } else {
- [self _abortWithStatusCode:500];
- }
-
- }];
- } else if (length == 0) {
- if ([_request close]) {
- [self _processRequest];
- } else {
- [self _abortWithStatusCode:500];
- }
- } else {
- [_request close]; // Can't do anything with result anyway
- [self _abortWithStatusCode:500];
- }
- } else {
- [self _abortWithStatusCode:500];
- }
- }
- - (void)_readRequestHeaders {
- _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
- [self _readHeadersWithCompletionBlock:^(NSData* extraData) {
-
- if (extraData) {
- NSString* requestMethod = [ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)) uppercaseString];
- DCHECK(requestMethod);
- NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage));
- DCHECK(requestURL);
- NSString* requestPath = GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))); // Don't use -[NSURL path] which strips the ending slash
- DCHECK(requestPath);
- NSDictionary* requestQuery = nil;
- NSString* queryString = ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)); // Don't use -[NSURL query] to make sure query is not unescaped;
- if (queryString.length) {
- requestQuery = GCDWebServerParseURLEncodedForm(queryString);
- DCHECK(requestQuery);
- }
- NSDictionary* requestHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(_requestMessage));
- DCHECK(requestHeaders);
- for (_handler in _server.handlers) {
- _request = ARC_RETAIN(_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery));
- if (_request) {
- break;
- }
- }
- if (_request) {
- if (_request.hasBody) {
- if (extraData.length <= _request.contentLength) {
- NSString* expectHeader = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect")));
- if (expectHeader) {
- if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) {
- [self _writeData:_continueData withCompletionBlock:^(BOOL success) {
-
- if (success) {
- [self _readRequestBody:extraData];
- }
-
- }];
- } else {
- LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
- [self _abortWithStatusCode:417];
- }
- } else {
- [self _readRequestBody:extraData];
- }
- } else {
- LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
- [self _abortWithStatusCode:400];
- }
- } else {
- [self _processRequest];
- }
- } else {
- [self _abortWithStatusCode:405];
- }
- } else {
- [self _abortWithStatusCode:500];
- }
-
- }];
- }
- - (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket {
- if ((self = [super init])) {
- _server = ARC_RETAIN(server);
- _address = ARC_RETAIN(address);
- _socket = socket;
-
- [self open];
- }
- return self;
- }
- - (void)dealloc {
- [self close];
-
- ARC_RELEASE(_server);
- ARC_RELEASE(_address);
-
- if (_requestMessage) {
- CFRelease(_requestMessage);
- }
- ARC_RELEASE(_request);
-
- if (_responseMessage) {
- CFRelease(_responseMessage);
- }
- ARC_RELEASE(_response);
-
- ARC_DEALLOC(super);
- }
- @end
- @implementation GCDWebServerConnection (Subclassing)
- - (void)open {
- LOG_DEBUG(@"Did open connection on socket %i", _socket);
- [self _readRequestHeaders];
- }
- - (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
- LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength);
- GCDWebServerResponse* response = nil;
- @try {
- response = block(request);
- }
- @catch (NSException* exception) {
- LOG_EXCEPTION(exception);
- }
- return response;
- }
- - (void)close {
- int result = close(_socket);
- if (result != 0) {
- LOG_ERROR(@"Failed closing socket %i for connection (%i): %s", _socket, errno, strerror(errno));
- }
- LOG_DEBUG(@"Did close connection on socket %i", _socket);
- }
- @end
|