mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-03 17:55:10 +08:00
* Introduces RKRequestCache for cacheing responses (supports ETag conditional GET, use cache if available, use cache on error, etc.) closes #75 * Updates to Three20 layer to eliminate need for intermediary TTTableItem classes closes #76 * Fixes to ensure iOS 3.x compatability: * Switched compiler to Clang * Updated conditional checks for UIBackgroundTask symbols to ensure runtime safety on iOS 3.x * Removed unnecessary linkage against UIKit and CoreFoundation from library targets * Fix for issue where RKRequest objects could become stuck in infinite loop within RKRequestQueue loadNextInQueue if you start a request and then cancel immediately. On cancel only decrement loadCount if the request has start loading. refs #122
463 lines
15 KiB
Objective-C
463 lines
15 KiB
Objective-C
//
|
|
// RKRequest.m
|
|
// RestKit
|
|
//
|
|
// Created by Jeremy Ellison on 7/27/09.
|
|
// Copyright 2009 Two Toasters. All rights reserved.
|
|
//
|
|
|
|
#import "RKRequest.h"
|
|
#import "RKRequestQueue.h"
|
|
#import "RKResponse.h"
|
|
#import "NSDictionary+RKRequestSerialization.h"
|
|
#import "RKNotifications.h"
|
|
#import "RKClient.h"
|
|
#import "../Support/Support.h"
|
|
#import "RKURL.h"
|
|
#import "NSData+MD5.h"
|
|
#import "NSString+MD5.h"
|
|
#import "Logging.h"
|
|
#import "RKRequestCache.h"
|
|
|
|
@implementation RKRequest
|
|
|
|
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders,
|
|
params = _params, userData = _userData, username = _username, password = _password, method = _method,
|
|
forceBasicAuthentication = _forceBasicAuthentication, cachePolicy = _cachePolicy, cache = _cache;
|
|
|
|
#if TARGET_OS_IPHONE
|
|
@synthesize backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier;
|
|
#endif
|
|
|
|
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate {
|
|
return [[[RKRequest alloc] initWithURL:URL delegate:delegate] autorelease];
|
|
}
|
|
|
|
- (id)initWithURL:(NSURL*)URL {
|
|
self = [self init];
|
|
if (self) {
|
|
_URL = [URL retain];
|
|
_URLRequest = [[NSMutableURLRequest alloc] initWithURL:_URL];
|
|
[_URLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
|
_connection = nil;
|
|
_isLoading = NO;
|
|
_isLoaded = NO;
|
|
_forceBasicAuthentication = NO;
|
|
_cachePolicy = RKRequestCachePolicyDefault;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate {
|
|
self = [self initWithURL:URL];
|
|
if (self) {
|
|
_delegate = delegate;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if (self) {
|
|
#if TARGET_OS_IPHONE
|
|
_backgroundPolicy = RKRequestBackgroundPolicyNone;
|
|
_backgroundTaskIdentifier = 0;
|
|
BOOL backgroundOK = &UIBackgroundTaskInvalid != NULL;
|
|
if (backgroundOK) {
|
|
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)cleanupBackgroundTask {
|
|
#if TARGET_OS_IPHONE
|
|
BOOL backgroundOK = &UIBackgroundTaskInvalid != NULL;
|
|
if (backgroundOK && UIBackgroundTaskInvalid == self.backgroundTaskIdentifier) {
|
|
return;
|
|
}
|
|
|
|
UIApplication* app = [UIApplication sharedApplication];
|
|
if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
|
|
[app endBackgroundTask:_backgroundTaskIdentifier];
|
|
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
self.delegate = nil;
|
|
[_connection cancel];
|
|
[_connection release];
|
|
_connection = nil;
|
|
[_userData release];
|
|
_userData = nil;
|
|
[_URL release];
|
|
_URL = nil;
|
|
[_URLRequest release];
|
|
_URLRequest = nil;
|
|
[_params release];
|
|
_params = nil;
|
|
[_additionalHTTPHeaders release];
|
|
_additionalHTTPHeaders = nil;
|
|
[_username release];
|
|
_username = nil;
|
|
[_password release];
|
|
_password = nil;
|
|
[_cache release];
|
|
_cache = nil;
|
|
|
|
// Cleanup a background task if there is any
|
|
[self cleanupBackgroundTask];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)setRequestBody {
|
|
if (_params && (_method != RKRequestMethodGET)) {
|
|
// Prefer the use of a stream over a raw body
|
|
if ([_params respondsToSelector:@selector(HTTPBodyStream)]) {
|
|
[_URLRequest setHTTPBodyStream:[_params HTTPBodyStream]];
|
|
} else {
|
|
[_URLRequest setHTTPBody:[_params HTTPBody]];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)addHeadersToRequest {
|
|
NSString* header;
|
|
for (header in _additionalHTTPHeaders) {
|
|
[_URLRequest setValue:[_additionalHTTPHeaders valueForKey:header] forHTTPHeaderField:header];
|
|
}
|
|
|
|
if (_params != nil) {
|
|
// Temporarily support older RKRequestSerializable implementations
|
|
if ([_params respondsToSelector:@selector(HTTPHeaderValueForContentType)]) {
|
|
[_URLRequest setValue:[_params HTTPHeaderValueForContentType] forHTTPHeaderField:@"Content-Type"];
|
|
} else if ([_params respondsToSelector:@selector(ContentTypeHTTPHeader)]) {
|
|
[_URLRequest setValue:[_params performSelector:@selector(ContentTypeHTTPHeader)] forHTTPHeaderField:@"Content-Type"];
|
|
}
|
|
if ([_params respondsToSelector:@selector(HTTPHeaderValueForContentLength)]) {
|
|
[_URLRequest setValue:[NSString stringWithFormat:@"%d", [_params HTTPHeaderValueForContentLength]] forHTTPHeaderField:@"Content-Length"];
|
|
}
|
|
}
|
|
|
|
// Add authentication headers so we don't have to deal with an extra cycle for each message requiring basic auth.
|
|
if (self.forceBasicAuthentication) {
|
|
CFHTTPMessageRef dummyRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self HTTPMethod], (CFURLRef)[self URL], kCFHTTPVersion1_1);
|
|
|
|
CFHTTPMessageAddAuthentication(dummyRequest, nil, (CFStringRef)_username, (CFStringRef)_password,kCFHTTPAuthenticationSchemeBasic, FALSE);
|
|
CFStringRef authorizationString = CFHTTPMessageCopyHeaderFieldValue(dummyRequest, CFSTR("Authorization"));
|
|
[_URLRequest setValue:(NSString *)authorizationString forHTTPHeaderField:@"Authorization"];
|
|
CFRelease(dummyRequest);
|
|
CFRelease(authorizationString);
|
|
}
|
|
|
|
if (self.cachePolicy & RKRequestCachePolicyEtag) {
|
|
NSString* etag = [self.cache etagForRequest:self];
|
|
if (etag) {
|
|
[_URLRequest setValue:etag forHTTPHeaderField:@"If-None-Match"];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Setup the NSURLRequest. The request must be prepared right before dispatching
|
|
- (void)prepareURLRequest {
|
|
[_URLRequest setHTTPMethod:[self HTTPMethod]];
|
|
[self setRequestBody];
|
|
[self addHeadersToRequest];
|
|
}
|
|
|
|
- (void)cancelAndInformDelegate:(BOOL)informDelegate {
|
|
[_connection cancel];
|
|
[_connection release];
|
|
_connection = nil;
|
|
_isLoading = NO;
|
|
|
|
if (informDelegate && [_delegate respondsToSelector:@selector(requestDidCancelLoad:)]) {
|
|
[_delegate requestDidCancelLoad:self];
|
|
}
|
|
}
|
|
|
|
- (NSString*)HTTPMethod {
|
|
switch (_method) {
|
|
case RKRequestMethodGET:
|
|
return @"GET";
|
|
break;
|
|
case RKRequestMethodPOST:
|
|
return @"POST";
|
|
break;
|
|
case RKRequestMethodPUT:
|
|
return @"PUT";
|
|
break;
|
|
case RKRequestMethodDELETE:
|
|
return @"DELETE";
|
|
break;
|
|
default:
|
|
return nil;
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)send {
|
|
[[RKRequestQueue sharedQueue] addRequest:self];
|
|
}
|
|
|
|
- (void)fireAsynchronousRequest {
|
|
[self prepareURLRequest];
|
|
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
|
|
NSLog(@"Sending %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
|
|
[body release];
|
|
|
|
_isLoading = YES;
|
|
|
|
if ([self.delegate respondsToSelector:@selector(requestDidStartLoad:)]) {
|
|
[self.delegate requestDidStartLoad:self];
|
|
}
|
|
|
|
RKResponse* response = [[[RKResponse alloc] initWithRequest:self] autorelease];
|
|
_connection = [[NSURLConnection connectionWithRequest:_URLRequest delegate:response] retain];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
|
|
}
|
|
|
|
- (BOOL)shouldDispatchRequest {
|
|
return [RKClient sharedClient] == nil || [[RKClient sharedClient] isNetworkAvailable];
|
|
}
|
|
|
|
- (void)sendAsynchronously {
|
|
_sentSynchronously = NO;
|
|
if (self.cachePolicy & RKRequestCachePolicyEnabled) {
|
|
if ([self.cache hasResponseForRequest:self]) {
|
|
NSLog(@"Found cached content, loading...");
|
|
_isLoading = YES;
|
|
[self didFinishLoad:[self.cache responseForRequest:self]];
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ([self shouldDispatchRequest]) {
|
|
#if TARGET_OS_IPHONE
|
|
// Background Request Policy support
|
|
UIApplication* app = [UIApplication sharedApplication];
|
|
if (self.backgroundPolicy == RKRequestBackgroundPolicyNone ||
|
|
NO == [app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
|
|
// No support for background (iOS 3.x) or the policy is none -- just fire the request
|
|
[self fireAsynchronousRequest];
|
|
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyCancel || self.backgroundPolicy == RKRequestBackgroundPolicyRequeue) {
|
|
// For cancel or requeue behaviors, we watch for background transition notifications
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(appDidEnterBackgroundNotification:)
|
|
name:UIApplicationDidEnterBackgroundNotification
|
|
object:nil];
|
|
[self fireAsynchronousRequest];
|
|
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyContinue) {
|
|
NSLog(@"Beginning background task to perform processing...");
|
|
|
|
// Fork a background task for continueing a long-running request
|
|
_backgroundTaskIdentifier = [app beginBackgroundTaskWithExpirationHandler:^{
|
|
NSLog(@"Background request time expired, canceling request.");
|
|
|
|
[self cancelAndInformDelegate:NO];
|
|
[self cleanupBackgroundTask];
|
|
|
|
if ([_delegate respondsToSelector:@selector(requestDidTimeout:)]) {
|
|
[_delegate requestDidTimeout:self];
|
|
}
|
|
}];
|
|
|
|
// Start the potentially long-running request
|
|
[self fireAsynchronousRequest];
|
|
}
|
|
#else
|
|
[self fireAsynchronousRequest];
|
|
#endif
|
|
} else {
|
|
if (_cachePolicy & RKRequestCachePolicyLoadIfOffline &&
|
|
[self.cache hasResponseForRequest:self]) {
|
|
|
|
_isLoading = YES;
|
|
[self didFinishLoad:[self.cache responseForRequest:self]];
|
|
|
|
} else {
|
|
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
errorMessage, NSLocalizedDescriptionKey,
|
|
nil];
|
|
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
|
[self didFailLoadWithError:error];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (RKResponse*)sendSynchronously {
|
|
NSURLResponse* URLResponse = nil;
|
|
NSError* error;
|
|
NSData* payload = nil;
|
|
RKResponse* response = nil;
|
|
_sentSynchronously = YES;
|
|
|
|
if ([self shouldDispatchRequest]) {
|
|
[self prepareURLRequest];
|
|
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
|
|
NSLog(@"Sending synchronous %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
|
|
[body release];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
|
|
|
|
_isLoading = YES;
|
|
if ([self.delegate respondsToSelector:@selector(requestDidStartLoad:)]) {
|
|
[self.delegate requestDidStartLoad:self];
|
|
}
|
|
|
|
payload = [NSURLConnection sendSynchronousRequest:_URLRequest returningResponse:&URLResponse error:&error];
|
|
if (payload != nil) error = nil;
|
|
|
|
response = [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
|
|
|
|
if (payload == nil) {
|
|
[self didFailLoadWithError:error];
|
|
} else {
|
|
[self didFinishLoad:response];
|
|
}
|
|
|
|
} else {
|
|
if (_cachePolicy & RKRequestCachePolicyLoadIfOffline &&
|
|
[self.cache hasResponseForRequest:self]) {
|
|
|
|
response = [self.cache responseForRequest:self];
|
|
|
|
} else {
|
|
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
errorMessage, NSLocalizedDescriptionKey,
|
|
nil];
|
|
error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
|
[self didFailLoadWithError:error];
|
|
|
|
// TODO: Is this needed here? Or can we just return a nil response and everyone will be happy??
|
|
response = [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
- (void)cancel {
|
|
[self cancelAndInformDelegate:YES];
|
|
}
|
|
|
|
// TODO: Isn't this code duplicated higher up???
|
|
- (void)didFailLoadWithError:(NSError*)error {
|
|
if (_cachePolicy & RKRequestCachePolicyLoadOnError &&
|
|
[self.cache hasResponseForRequest:self]) {
|
|
|
|
[self didFinishLoad:[self.cache responseForRequest:self]];
|
|
} else {
|
|
_isLoading = NO;
|
|
|
|
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
|
|
[_delegate request:self didFailLoadWithError:error];
|
|
}
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestFailedWithErrorNotification object:self userInfo:nil];
|
|
}
|
|
}
|
|
|
|
- (void)didFinishLoad:(RKResponse*)response {
|
|
_isLoading = NO;
|
|
_isLoaded = YES;
|
|
|
|
RKLOG_NETWORK(RKLogLevelInfo, @"Status Code: %d", [response statusCode]);
|
|
RKLOG_NETWORK(RKLogLevelInfo, @"Body: %@", [response bodyAsString]);
|
|
|
|
RKResponse* finalResponse = response;
|
|
|
|
if ((_cachePolicy & RKRequestCachePolicyEtag) && [response isNotModified]) {
|
|
finalResponse = [self.cache responseForRequest:self];
|
|
}
|
|
|
|
if (![response wasLoadedFromCache] && [response isSuccessful] && (_cachePolicy != RKRequestCachePolicyNone)) {
|
|
[self.cache storeResponse:response forRequest:self];
|
|
}
|
|
|
|
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
|
|
[_delegate request:self didLoadResponse:finalResponse];
|
|
}
|
|
|
|
if ([response isServiceUnavailable]) {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self];
|
|
}
|
|
|
|
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:finalResponse forKey:@"response"];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification object:self userInfo:userInfo];
|
|
|
|
// NOTE: This notification must be posted last as the request queue releases the request when it
|
|
// receives the notification
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKResponseReceivedNotification object:response userInfo:nil];
|
|
}
|
|
|
|
- (BOOL)isGET {
|
|
return _method == RKRequestMethodGET;
|
|
}
|
|
|
|
- (BOOL)isPOST {
|
|
return _method == RKRequestMethodPOST;
|
|
}
|
|
|
|
- (BOOL)isPUT {
|
|
return _method == RKRequestMethodPUT;
|
|
}
|
|
|
|
- (BOOL)isDELETE {
|
|
return _method == RKRequestMethodDELETE;
|
|
}
|
|
|
|
- (BOOL)isLoading {
|
|
return _isLoading;
|
|
}
|
|
|
|
- (BOOL)isLoaded {
|
|
return _isLoaded;
|
|
}
|
|
|
|
- (NSString*)resourcePath {
|
|
NSString* resourcePath = nil;
|
|
if ([self.URL isKindOfClass:[RKURL class]]) {
|
|
RKURL* url = (RKURL*)self.URL;
|
|
resourcePath = url.resourcePath;
|
|
}
|
|
return resourcePath;
|
|
}
|
|
|
|
- (BOOL)wasSentToResourcePath:(NSString*)resourcePath {
|
|
return [[self resourcePath] isEqualToString:resourcePath];
|
|
}
|
|
|
|
- (void)appDidEnterBackgroundNotification:(NSNotification*)notification {
|
|
#if TARGET_OS_IPHONE
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
|
|
if (self.backgroundPolicy == RKRequestBackgroundPolicyCancel) {
|
|
[self cancel];
|
|
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyRequeue) {
|
|
// Cancel the existing request
|
|
[self cancelAndInformDelegate:NO];
|
|
[self send];
|
|
}
|
|
#endif
|
|
}
|
|
|
|
- (NSString*)cacheKey {
|
|
if (_method == RKRequestMethodDELETE) {
|
|
return nil;
|
|
}
|
|
NSString* compositCacheKey = [NSString stringWithFormat:@"%@-%d-%@", self.URL, _method, [_URLRequest HTTPBody]];
|
|
return [compositCacheKey MD5];
|
|
}
|
|
|
|
@end
|