diff --git a/CGDWebServer/GCDWebServerConnection.h b/CGDWebServer/GCDWebServerConnection.h index 0f139c4..366c5c8 100644 --- a/CGDWebServer/GCDWebServerConnection.h +++ b/CGDWebServer/GCDWebServerConnection.h @@ -40,6 +40,7 @@ @end @interface GCDWebServerConnection (Subclassing) ++ (BOOL)shouldAutomaticallyMapHEADToGET; // Default is YES which means HEAD requests are mapped to GET requests with the response body being discarded - (void)open; - (void)didUpdateBytesRead; // Called from arbitrary thread after @totalBytesRead is updated - Default implementation does nothing - (void)didUpdateBytesWritten; // Called from arbitrary thread after @totalBytesWritten is updated - Default implementation does nothing diff --git a/CGDWebServer/GCDWebServerConnection.m b/CGDWebServer/GCDWebServerConnection.m index 703f870..5a263d1 100644 --- a/CGDWebServer/GCDWebServerConnection.m +++ b/CGDWebServer/GCDWebServerConnection.m @@ -54,6 +54,7 @@ static NSData* _lastChunkData = nil; CFSocketNativeHandle _socket; NSUInteger _bytesRead; NSUInteger _bytesWritten; + BOOL _virtualHEAD; CFHTTPMessageRef _requestMessage; GCDWebServerRequest* _request; @@ -395,13 +396,18 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - (void)_processRequest { DCHECK(_responseMessage == NULL); + BOOL hasBody = NO; GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock]; if (response) { response = [self replaceResponse:response forRequest:_request]; if (response) { + if ([response hasBody]) { + [response prepareForReading]; + hasBody = !_virtualHEAD; + } NSError* error = nil; - if ([response hasBody] && ![response performOpen:&error]) { + if (hasBody && ![response performOpen:&error]) { LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error); } else { _response = ARC_RETAIN(response); @@ -437,14 +443,14 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { [self _writeHeadersWithCompletionBlock:^(BOOL success) { if (success) { - if ([_response hasBody]) { + if (hasBody) { [self _writeBodyWithCompletionBlock:^(BOOL successInner) { [_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent }]; } - } else if ([_response hasBody]) { + } else if (hasBody) { [_response performClose]; } @@ -527,6 +533,10 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { if (extraData) { NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase DCHECK(requestMethod); + if ([[self class] shouldAutomaticallyMapHEADToGET] && [requestMethod isEqualToString:@"HEAD"]) { + requestMethod = @"GET"; + _virtualHEAD = YES; + } 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 @@ -546,7 +556,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { } } if (_request) { - if (_request.hasBody) { + if ([_request hasBody]) { + [_request prepareForWriting]; if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) { NSString* expectHeader = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect"))); if (expectHeader) { @@ -649,6 +660,10 @@ static NSString* _StringFromAddressData(NSData* data) { @implementation GCDWebServerConnection (Subclassing) ++ (BOOL)shouldAutomaticallyMapHEADToGET { + return YES; +} + - (void)open { LOG_DEBUG(@"Did open connection on socket %i", _socket); [self _readRequestHeaders]; diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index 5941487..6d94c6f 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/CGDWebServer/GCDWebServerPrivate.h @@ -129,6 +129,7 @@ extern NSDate* GCDWebServerParseHTTPDate(NSString* string); @interface GCDWebServerRequest () @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +- (void)prepareForWriting; - (BOOL)performOpen:(NSError**)error; - (BOOL)performWriteData:(NSData*)data error:(NSError**)error; - (BOOL)performClose:(NSError**)error; @@ -137,6 +138,7 @@ extern NSDate* GCDWebServerParseHTTPDate(NSString* string); @interface GCDWebServerResponse () @property(nonatomic, readonly) NSDictionary* additionalHeaders; @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +- (void)prepareForReading; - (BOOL)performOpen:(NSError**)error; - (NSData*)performReadData:(NSError**)error; - (void)performClose; diff --git a/CGDWebServer/GCDWebServerRequest.m b/CGDWebServer/GCDWebServerRequest.m index a7477d9..e44fb72 100644 --- a/CGDWebServer/GCDWebServerRequest.m +++ b/CGDWebServer/GCDWebServerRequest.m @@ -271,14 +271,7 @@ return YES; } -- (BOOL)performOpen:(NSError**)error { - DCHECK(_type); - if (_opened) { - DNOT_REACHED(); - return NO; - } - _opened = YES; - +- (void)prepareForWriting { _writer = self; if ([[[self.headers objectForKey:@"Content-Encoding"] lowercaseString] isEqualToString:@"gzip"]) { GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; @@ -286,6 +279,16 @@ ARC_RELEASE(decoder); _writer = decoder; } +} + +- (BOOL)performOpen:(NSError**)error { + DCHECK(_type); + DCHECK(_writer); + if (_opened) { + DNOT_REACHED(); + return NO; + } + _opened = YES; return [_writer open:error]; } diff --git a/CGDWebServer/GCDWebServerResponse.m b/CGDWebServer/GCDWebServerResponse.m index 26bf207..25714c6 100644 --- a/CGDWebServer/GCDWebServerResponse.m +++ b/CGDWebServer/GCDWebServerResponse.m @@ -223,14 +223,7 @@ ; } -- (BOOL)performOpen:(NSError**)error { - DCHECK(_type); - if (_opened) { - DNOT_REACHED(); - return NO; - } - _opened = YES; - +- (void)prepareForReading { _reader = self; if (_gzipped) { GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; @@ -238,6 +231,16 @@ ARC_RELEASE(encoder); _reader = encoder; } +} + +- (BOOL)performOpen:(NSError**)error { + DCHECK(_type); + DCHECK(_reader); + if (_opened) { + DNOT_REACHED(); + return NO; + } + _opened = YES; return [_reader open:error]; }