653 lines
22 KiB
Objective-C
653 lines
22 KiB
Objective-C
/*
|
|
File: QHTTPOperation.m
|
|
|
|
Contains: An NSOperation that runs an HTTP request.
|
|
|
|
Written by: DTS
|
|
|
|
Copyright: Copyright (c) 2010 Apple Inc. All Rights Reserved.
|
|
|
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
|
|
("Apple") in consideration of your agreement to the following
|
|
terms, and your use, installation, modification or
|
|
redistribution of this Apple software constitutes acceptance of
|
|
these terms. If you do not agree with these terms, please do
|
|
not use, install, modify or redistribute this Apple software.
|
|
|
|
In consideration of your agreement to abide by the following
|
|
terms, and subject to these terms, Apple grants you a personal,
|
|
non-exclusive license, under Apple's copyrights in this
|
|
original Apple software (the "Apple Software"), to use,
|
|
reproduce, modify and redistribute the Apple Software, with or
|
|
without modifications, in source and/or binary forms; provided
|
|
that if you redistribute the Apple Software in its entirety and
|
|
without modifications, you must retain this notice and the
|
|
following text and disclaimers in all such redistributions of
|
|
the Apple Software. Neither the name, trademarks, service marks
|
|
or logos of Apple Inc. may be used to endorse or promote
|
|
products derived from the Apple Software without specific prior
|
|
written permission from Apple. Except as expressly stated in
|
|
this notice, no other rights or licenses, express or implied,
|
|
are granted by Apple herein, including but not limited to any
|
|
patent rights that may be infringed by your derivative works or
|
|
by other works in which the Apple Software may be incorporated.
|
|
|
|
The Apple Software is provided by Apple on an "AS IS" basis.
|
|
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
|
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
|
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
|
|
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
|
COMBINATION WITH YOUR PRODUCTS.
|
|
|
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
|
|
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
|
|
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
|
|
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
|
|
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
#import "QHTTPOperation.h"
|
|
|
|
@interface QHTTPOperation ()
|
|
|
|
// Read/write versions of public properties
|
|
|
|
@property (copy, readwrite) NSURLRequest * lastRequest;
|
|
@property (copy, readwrite) NSHTTPURLResponse * lastResponse;
|
|
|
|
// Internal properties
|
|
|
|
@property (retain, readwrite) NSURLConnection * connection;
|
|
@property (assign, readwrite) BOOL firstData;
|
|
@property (retain, readwrite) NSMutableData * dataAccumulator;
|
|
|
|
#if ! defined(NDEBUG)
|
|
@property (retain, readwrite) NSTimer * debugDelayTimer;
|
|
#endif
|
|
|
|
@end
|
|
|
|
@implementation QHTTPOperation
|
|
|
|
#pragma mark * Initialise and finalise
|
|
|
|
- (id)initWithRequest:(NSURLRequest *)request
|
|
// See comment in header.
|
|
{
|
|
// any thread
|
|
assert(request != nil);
|
|
assert([request URL] != nil);
|
|
// Because we require an NSHTTPURLResponse, we only support HTTP and HTTPS URLs.
|
|
assert([[[[request URL] scheme] lowercaseString] isEqual:@"http"] || [[[[request URL] scheme] lowercaseString] isEqual:@"https"]);
|
|
self = [super init];
|
|
if (self != nil) {
|
|
#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
|
|
static const NSUInteger kPlatformReductionFactor = 4;
|
|
#else
|
|
static const NSUInteger kPlatformReductionFactor = 1;
|
|
#endif
|
|
self->_request = [request copy];
|
|
self->_defaultResponseSize = 1 * 1024 * 1024 / kPlatformReductionFactor;
|
|
self->_maximumResponseSize = 4 * 1024 * 1024 / kPlatformReductionFactor;
|
|
self->_firstData = YES;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithURL:(NSURL *)url
|
|
// See comment in header.
|
|
{
|
|
assert(url != nil);
|
|
return [self initWithRequest:[NSURLRequest requestWithURL:url]];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
#if ! defined(NDEBUG)
|
|
[self->_debugError release];
|
|
[self->_debugDelayTimer invalidate];
|
|
[self->_debugDelayTimer release];
|
|
#endif
|
|
// any thread
|
|
[self->_request release];
|
|
[self->_acceptableStatusCodes release];
|
|
[self->_acceptableContentTypes release];
|
|
[self->_responseOutputStream release];
|
|
assert(self->_connection == nil); // should have been shut down by now
|
|
[self->_dataAccumulator release];
|
|
[self->_lastRequest release];
|
|
[self->_lastResponse release];
|
|
[self->_responseBody release];
|
|
[super dealloc];
|
|
}
|
|
|
|
#pragma mark * Properties
|
|
|
|
// We write our own settings for many properties because we want to bounce
|
|
// sets that occur in the wrong state. And, given that we've written the
|
|
// setter anyway, we also avoid KVO notifications when the value doesn't change.
|
|
|
|
@synthesize request = _request;
|
|
|
|
@synthesize authenticationDelegate = _authenticationDelegate;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfAuthenticationDelegate
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (id<QHTTPOperationAuthenticationDelegate>)authenticationDelegate
|
|
{
|
|
return self->_authenticationDelegate;
|
|
}
|
|
|
|
- (void)setAuthenticationDelegate:(id<QHTTPOperationAuthenticationDelegate>)newValue
|
|
{
|
|
if (self.state != kQRunLoopOperationStateInited) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_authenticationDelegate) {
|
|
[self willChangeValueForKey:@"authenticationDelegate"];
|
|
self->_authenticationDelegate = newValue;
|
|
[self didChangeValueForKey:@"authenticationDelegate"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize acceptableStatusCodes = _acceptableStatusCodes;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfAcceptableStatusCodes
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSIndexSet *)acceptableStatusCodes
|
|
{
|
|
return [[self->_acceptableStatusCodes retain] autorelease];
|
|
}
|
|
|
|
- (void)setAcceptableStatusCodes:(NSIndexSet *)newValue
|
|
{
|
|
if (self.state != kQRunLoopOperationStateInited) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_acceptableStatusCodes) {
|
|
[self willChangeValueForKey:@"acceptableStatusCodes"];
|
|
[self->_acceptableStatusCodes autorelease];
|
|
self->_acceptableStatusCodes = [newValue copy];
|
|
[self didChangeValueForKey:@"acceptableStatusCodes"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize acceptableContentTypes = _acceptableContentTypes;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfAcceptableContentTypes
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSSet *)acceptableContentTypes
|
|
{
|
|
return [[self->_acceptableContentTypes retain] autorelease];
|
|
}
|
|
|
|
- (void)setAcceptableContentTypes:(NSSet *)newValue
|
|
{
|
|
if (self.state != kQRunLoopOperationStateInited) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_acceptableContentTypes) {
|
|
[self willChangeValueForKey:@"acceptableContentTypes"];
|
|
[self->_acceptableContentTypes autorelease];
|
|
self->_acceptableContentTypes = [newValue copy];
|
|
[self didChangeValueForKey:@"acceptableContentTypes"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize responseOutputStream = _responseOutputStream;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfResponseOutputStream
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSOutputStream *)responseOutputStream
|
|
{
|
|
return [[self->_responseOutputStream retain] autorelease];
|
|
}
|
|
|
|
- (void)setResponseOutputStream:(NSOutputStream *)newValue
|
|
{
|
|
if (self.dataAccumulator != nil) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_responseOutputStream) {
|
|
[self willChangeValueForKey:@"responseOutputStream"];
|
|
[self->_responseOutputStream autorelease];
|
|
self->_responseOutputStream = [newValue retain];
|
|
[self didChangeValueForKey:@"responseOutputStream"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize defaultResponseSize = _defaultResponseSize;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfDefaultResponseSize
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSUInteger)defaultResponseSize
|
|
{
|
|
return self->_defaultResponseSize;
|
|
}
|
|
|
|
- (void)setDefaultResponseSize:(NSUInteger)newValue
|
|
{
|
|
if (self.dataAccumulator != nil) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_defaultResponseSize) {
|
|
[self willChangeValueForKey:@"defaultResponseSize"];
|
|
self->_defaultResponseSize = newValue;
|
|
[self didChangeValueForKey:@"defaultResponseSize"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize maximumResponseSize = _maximumResponseSize;
|
|
|
|
+ (BOOL)automaticallyNotifiesObserversOfMaximumResponseSize
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSUInteger)maximumResponseSize
|
|
{
|
|
return self->_maximumResponseSize;
|
|
}
|
|
|
|
- (void)setMaximumResponseSize:(NSUInteger)newValue
|
|
{
|
|
if (self.dataAccumulator != nil) {
|
|
assert(NO);
|
|
} else {
|
|
if (newValue != self->_maximumResponseSize) {
|
|
[self willChangeValueForKey:@"maximumResponseSize"];
|
|
self->_maximumResponseSize = newValue;
|
|
[self didChangeValueForKey:@"maximumResponseSize"];
|
|
}
|
|
}
|
|
}
|
|
|
|
@synthesize lastRequest = _lastRequest;
|
|
@synthesize lastResponse = _lastResponse;
|
|
@synthesize responseBody = _responseBody;
|
|
|
|
@synthesize connection = _connection;
|
|
@synthesize firstData = _firstData;
|
|
@synthesize dataAccumulator = _dataAccumulator;
|
|
|
|
- (NSURL *)URL
|
|
{
|
|
return [self.request URL];
|
|
}
|
|
|
|
- (BOOL)isStatusCodeAcceptable
|
|
{
|
|
NSIndexSet * acceptableStatusCodes;
|
|
NSInteger statusCode;
|
|
|
|
assert(self.lastResponse != nil);
|
|
|
|
acceptableStatusCodes = self.acceptableStatusCodes;
|
|
if (acceptableStatusCodes == nil) {
|
|
acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
|
|
}
|
|
assert(acceptableStatusCodes != nil);
|
|
|
|
statusCode = [self.lastResponse statusCode];
|
|
return (statusCode >= 0) && [acceptableStatusCodes containsIndex: (NSUInteger) statusCode];
|
|
}
|
|
|
|
- (BOOL)isContentTypeAcceptable
|
|
{
|
|
NSString * contentType;
|
|
|
|
assert(self.lastResponse != nil);
|
|
contentType = [self.lastResponse MIMEType];
|
|
return (self.acceptableContentTypes == nil) || ((contentType != nil) && [self.acceptableContentTypes containsObject:contentType]);
|
|
}
|
|
|
|
#pragma mark * Start and finish overrides
|
|
|
|
- (void)operationDidStart
|
|
// Called by QRunLoopOperation when the operation starts. This kicks of an
|
|
// asynchronous NSURLConnection.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(self.state == kQRunLoopOperationStateExecuting);
|
|
|
|
assert(self.defaultResponseSize > 0);
|
|
assert(self.maximumResponseSize > 0);
|
|
assert(self.defaultResponseSize <= self.maximumResponseSize);
|
|
|
|
assert(self.request != nil);
|
|
|
|
// If a debug error is set, apply that error rather than running the connection.
|
|
|
|
#if ! defined(NDEBUG)
|
|
if (self.debugError != nil) {
|
|
[self finishWithError:self.debugError];
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Create a connection that's scheduled in the required run loop modes.
|
|
|
|
assert(self.connection == nil);
|
|
self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease];
|
|
assert(self.connection != nil);
|
|
|
|
for (NSString * mode in self.actualRunLoopModes) {
|
|
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode];
|
|
}
|
|
|
|
[self.connection start];
|
|
}
|
|
|
|
- (void)operationWillFinish
|
|
// Called by QRunLoopOperation when the operation has finished. We
|
|
// do various bits of tidying up.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(self.state == kQRunLoopOperationStateExecuting);
|
|
|
|
// It is possible to hit this state of the operation is cancelled while
|
|
// the debugDelayTimer is running. In that case, hey, we'll just accept
|
|
// the inevitable and finish rather than trying anything else clever.
|
|
|
|
#if ! defined(NDEBUG)
|
|
if (self.debugDelayTimer != nil) {
|
|
[self.debugDelayTimer invalidate];
|
|
self.debugDelayTimer = nil;
|
|
}
|
|
#endif
|
|
|
|
[self.connection cancel];
|
|
self.connection = nil;
|
|
|
|
// If we have an output stream, close it at this point. We might never
|
|
// have actually opened this stream but, AFAICT, closing an unopened stream
|
|
// doesn't hurt.
|
|
|
|
if (self.responseOutputStream != nil) {
|
|
[self.responseOutputStream close];
|
|
}
|
|
}
|
|
|
|
- (void)finishWithError:(NSError *)error
|
|
// We override -finishWithError: just so we can handle our debug delay.
|
|
{
|
|
// If a debug delay was set, don't finish now but rather start the debug delay timer
|
|
// and have it do the actual finish. We clear self.debugDelay so that the next
|
|
// time this code runs its doesn't do this again.
|
|
//
|
|
// We only do this in the non-cancellation case. In the cancellation case, we
|
|
// just stop immediately.
|
|
|
|
#if ! defined(NDEBUG)
|
|
if (self.debugDelay > 0.0) {
|
|
if ( (error != nil) && [[error domain] isEqual:NSCocoaErrorDomain] && ([error code] == NSUserCancelledError) ) {
|
|
self.debugDelay = 0.0;
|
|
} else {
|
|
assert(self.debugDelayTimer == nil);
|
|
self.debugDelayTimer = [NSTimer timerWithTimeInterval:self.debugDelay target:self selector:@selector(debugDelayTimerDone:) userInfo:error repeats:NO];
|
|
assert(self.debugDelayTimer != nil);
|
|
for (NSString * mode in self.actualRunLoopModes) {
|
|
[[NSRunLoop currentRunLoop] addTimer:self.debugDelayTimer forMode:mode];
|
|
}
|
|
self.debugDelay = 0.0;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[super finishWithError:error];
|
|
}
|
|
|
|
#if ! defined(NDEBUG)
|
|
|
|
@synthesize debugError = _debugError;
|
|
@synthesize debugDelay = _debugDelay;
|
|
@synthesize debugDelayTimer = _debugDelayTimer;
|
|
|
|
- (void)debugDelayTimerDone:(NSTimer *)timer
|
|
{
|
|
NSError * error;
|
|
|
|
assert(timer == self.debugDelayTimer);
|
|
|
|
error = [[[timer userInfo] retain] autorelease];
|
|
assert( (error == nil) || [error isKindOfClass:[NSError class]] );
|
|
|
|
[self.debugDelayTimer invalidate];
|
|
self.debugDelayTimer = nil;
|
|
|
|
[self finishWithError:error];
|
|
}
|
|
|
|
#endif
|
|
|
|
#pragma mark * NSURLConnection delegate callbacks
|
|
|
|
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
|
|
// See comment in header.
|
|
{
|
|
BOOL result;
|
|
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert(protectionSpace != nil);
|
|
#pragma unused(protectionSpace)
|
|
|
|
result = NO;
|
|
if (self.authenticationDelegate != nil) {
|
|
result = [self.authenticationDelegate httpOperation:self canAuthenticateAgainstProtectionSpace:protectionSpace];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
|
|
// See comment in header.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert(challenge != nil);
|
|
#pragma unused(challenge)
|
|
|
|
if (self.authenticationDelegate != nil) {
|
|
[self.authenticationDelegate httpOperation:self didReceiveAuthenticationChallenge:challenge];
|
|
} else {
|
|
if ( [challenge previousFailureCount] == 0 ) {
|
|
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
|
|
} else {
|
|
[[challenge sender] cancelAuthenticationChallenge:challenge];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
|
|
// See comment in header.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert( (response == nil) || [response isKindOfClass:[NSHTTPURLResponse class]] );
|
|
|
|
self.lastRequest = request;
|
|
self.lastResponse = (NSHTTPURLResponse *) response;
|
|
return request;
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
|
|
// See comment in header.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert([response isKindOfClass:[NSHTTPURLResponse class]]);
|
|
|
|
self.lastResponse = (NSHTTPURLResponse *) response;
|
|
|
|
// We don't check the status code here because we want to give the client an opportunity
|
|
// to get the data of the error message. Perhaps we /should/ check the content type
|
|
// here, but I'm not sure whether that's the right thing to do.
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
|
|
// See comment in header.
|
|
{
|
|
BOOL success;
|
|
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert(data != nil);
|
|
|
|
// If we don't yet have a destination for the data, calculate one. Note that, even
|
|
// if there is an output stream, we don't use it for error responses.
|
|
|
|
success = YES;
|
|
if (self.firstData) {
|
|
assert(self.dataAccumulator == nil);
|
|
|
|
if ( (self.responseOutputStream == nil) || ! self.isStatusCodeAcceptable ) {
|
|
long long length;
|
|
|
|
assert(self.dataAccumulator == nil);
|
|
|
|
length = [self.lastResponse expectedContentLength];
|
|
if (length == NSURLResponseUnknownLength) {
|
|
length = self.defaultResponseSize;
|
|
}
|
|
if (length <= (long long) self.maximumResponseSize) {
|
|
self.dataAccumulator = [NSMutableData dataWithCapacity:(NSUInteger)length];
|
|
} else {
|
|
[self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorResponseTooLarge userInfo:nil]];
|
|
success = NO;
|
|
}
|
|
}
|
|
|
|
// If the data is going to an output stream, open it.
|
|
|
|
if (success) {
|
|
if (self.dataAccumulator == nil) {
|
|
assert(self.responseOutputStream != nil);
|
|
[self.responseOutputStream open];
|
|
}
|
|
}
|
|
|
|
self.firstData = NO;
|
|
}
|
|
|
|
// Write the data to its destination.
|
|
|
|
if (success) {
|
|
if (self.dataAccumulator != nil) {
|
|
if ( ([self.dataAccumulator length] + [data length]) <= self.maximumResponseSize ) {
|
|
[self.dataAccumulator appendData:data];
|
|
} else {
|
|
[self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorResponseTooLarge userInfo:nil]];
|
|
}
|
|
} else {
|
|
NSUInteger dataOffset;
|
|
NSUInteger dataLength;
|
|
const uint8_t * dataPtr;
|
|
NSError * error;
|
|
NSInteger bytesWritten;
|
|
|
|
assert(self.responseOutputStream != nil);
|
|
|
|
dataOffset = 0;
|
|
dataLength = [data length];
|
|
dataPtr = [data bytes];
|
|
error = nil;
|
|
do {
|
|
if (dataOffset == dataLength) {
|
|
break;
|
|
}
|
|
bytesWritten = [self.responseOutputStream write:&dataPtr[dataOffset] maxLength:dataLength - dataOffset];
|
|
if (bytesWritten <= 0) {
|
|
error = [self.responseOutputStream streamError];
|
|
if (error == nil) {
|
|
error = [NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorOnOutputStream userInfo:nil];
|
|
}
|
|
break;
|
|
} else {
|
|
dataOffset += bytesWritten;
|
|
}
|
|
} while (YES);
|
|
|
|
if (error != nil) {
|
|
[self finishWithError:error];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
|
|
// See comment in header.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
|
|
assert(self.lastResponse != nil);
|
|
|
|
// Swap the data accumulator over to the response data so that we don't trigger a copy.
|
|
|
|
assert(self->_responseBody == nil);
|
|
self->_responseBody = self->_dataAccumulator;
|
|
self->_dataAccumulator = nil;
|
|
|
|
// Because we fill out _dataAccumulator lazily, an empty body will leave _dataAccumulator
|
|
// set to nil. That's not what our clients expect, so we fix it here.
|
|
|
|
if (self->_responseBody == nil) {
|
|
self->_responseBody = [[NSData alloc] init];
|
|
assert(self->_responseBody != nil);
|
|
}
|
|
|
|
if ( ! self.isStatusCodeAcceptable ) {
|
|
[self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:self.lastResponse.statusCode userInfo:nil]];
|
|
} else if ( ! self.isContentTypeAcceptable ) {
|
|
[self finishWithError:[NSError errorWithDomain:kQHTTPOperationErrorDomain code:kQHTTPOperationErrorBadContentType userInfo:nil]];
|
|
} else {
|
|
[self finishWithError:nil];
|
|
}
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
|
|
// See comment in header.
|
|
{
|
|
assert(self.isActualRunLoopThread);
|
|
assert(connection == self.connection);
|
|
#pragma unused(connection)
|
|
assert(error != nil);
|
|
|
|
[self finishWithError:error];
|
|
}
|
|
|
|
@end
|
|
|
|
NSString * kQHTTPOperationErrorDomain = @"kQHTTPOperationErrorDomain";
|