mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-02 22:42:45 +08:00
523 lines
15 KiB
Objective-C
523 lines
15 KiB
Objective-C
//
|
|
// RKResponse.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 7/28/09.
|
|
// Copyright (c) 2009-2012 RestKit. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#import "RKResponse.h"
|
|
#import "RKNotifications.h"
|
|
#import "RKLog.h"
|
|
#import "RKParserRegistry.h"
|
|
#import "RKRequestCache.h"
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent lcl_cRestKitNetwork
|
|
|
|
#define RKResponseIgnoreDelegateIfCancelled(...) \
|
|
if (self.request && [self.request isCancelled]) { \
|
|
RKLogDebug(@"%s: Ignoring NSURLConnection delegate message sent after cancel.", __PRETTY_FUNCTION__); \
|
|
return __VA_ARGS__; \
|
|
}
|
|
|
|
@implementation RKResponse
|
|
|
|
@synthesize body = _body;
|
|
@synthesize request = _request;
|
|
@synthesize failureError = _failureError;
|
|
|
|
- (id)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_body = [[NSMutableData alloc] init];
|
|
_failureError = nil;
|
|
_loading = NO;
|
|
_responseHeaders = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithRequest:(RKRequest *)request
|
|
{
|
|
self = [self init];
|
|
if (self) {
|
|
// We don't retain here as we're letting RKRequestQueue manage
|
|
// request ownership
|
|
_request = request;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithRequest:(RKRequest *)request body:(NSData *)body headers:(NSDictionary *)headers
|
|
{
|
|
self = [self initWithRequest:request];
|
|
if (self) {
|
|
[_body release];
|
|
_body = [[NSMutableData dataWithData:body] retain];
|
|
_responseHeaders = [headers retain];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithSynchronousRequest:(RKRequest *)request URLResponse:(NSHTTPURLResponse *)URLResponse body:(NSData *)body error:(NSError *)error
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_request = request;
|
|
_httpURLResponse = [URLResponse retain];
|
|
_failureError = [error retain];
|
|
_body = [[NSMutableData dataWithData:body] retain];
|
|
_loading = NO;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
_request = nil;
|
|
[_httpURLResponse release];
|
|
_httpURLResponse = nil;
|
|
[_body release];
|
|
_body = nil;
|
|
[_failureError release];
|
|
_failureError = nil;
|
|
[_responseHeaders release];
|
|
_responseHeaders = nil;
|
|
[super dealloc];
|
|
}
|
|
|
|
- (BOOL)hasCredentials
|
|
{
|
|
return _request.username && _request.password;
|
|
}
|
|
|
|
- (BOOL)isServerTrusted:(SecTrustRef)trust
|
|
{
|
|
BOOL proceed = NO;
|
|
|
|
if (_request.disableCertificateValidation) {
|
|
proceed = YES;
|
|
} else if ([_request.additionalRootCertificates count] > 0) {
|
|
CFArrayRef rootCerts = (CFArrayRef)[_request.additionalRootCertificates allObjects];
|
|
SecTrustResultType result;
|
|
OSStatus returnCode;
|
|
|
|
if (rootCerts && CFArrayGetCount(rootCerts)) {
|
|
// this could fail, but the trust evaluation will proceed (it's likely to fail, of course)
|
|
SecTrustSetAnchorCertificates(trust, rootCerts);
|
|
}
|
|
|
|
returnCode = SecTrustEvaluate(trust, &result);
|
|
|
|
if (returnCode == errSecSuccess) {
|
|
proceed = (result == kSecTrustResultProceed || result == kSecTrustResultConfirm || result == kSecTrustResultUnspecified);
|
|
if (result == kSecTrustResultRecoverableTrustFailure) {
|
|
// TODO: should try to recover here
|
|
// call SecTrustGetCssmResult() for more information about the failure
|
|
}
|
|
}
|
|
}
|
|
|
|
return proceed;
|
|
}
|
|
|
|
// Handle basic auth & SSL certificate validation
|
|
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
RKLogDebug(@"Received authentication challenge");
|
|
|
|
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
|
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
|
|
if ([self isServerTrusted:trust]) {
|
|
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust] forAuthenticationChallenge:challenge];
|
|
} else {
|
|
[[challenge sender] cancelAuthenticationChallenge:challenge];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ([challenge previousFailureCount] == 0) {
|
|
NSURLCredential *newCredential;
|
|
newCredential = [NSURLCredential credentialWithUser:[NSString stringWithFormat:@"%@", _request.username]
|
|
password:[NSString stringWithFormat:@"%@", _request.password]
|
|
persistence:NSURLCredentialPersistenceNone];
|
|
[[challenge sender] useCredential:newCredential
|
|
forAuthenticationChallenge:challenge];
|
|
} else {
|
|
RKLogWarning(@"Failed authentication challenge after %ld failures", (long)[challenge previousFailureCount]);
|
|
[[challenge sender] cancelAuthenticationChallenge:challenge];
|
|
}
|
|
}
|
|
|
|
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled(NO);
|
|
RKLogDebug(@"Asked if canAuthenticateAgainstProtectionSpace: with authenticationMethod = %@", [space authenticationMethod]);
|
|
if ([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
|
// server is using an SSL certificate that the OS can't validate
|
|
// see whether the client settings allow validation here
|
|
if (_request.disableCertificateValidation || [_request.additionalRootCertificates count] > 0) {
|
|
return YES;
|
|
} else {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
// Handle non-SSL challenges
|
|
BOOL hasCredentials = [self hasCredentials];
|
|
if (! hasCredentials) {
|
|
RKLogWarning(@"Received an authentication challenge without any credentials to satisfy the request.");
|
|
}
|
|
|
|
return hasCredentials;
|
|
}
|
|
|
|
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
|
|
{
|
|
if (nil == response || _request.followRedirect) {
|
|
RKLogDebug(@"Proceeding with request to %@", request);
|
|
return request;
|
|
} else {
|
|
RKLogDebug(@"Not following redirect to %@", request);
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
[_body appendData:data];
|
|
[_request invalidateTimeoutTimer];
|
|
if ([[_request delegate] respondsToSelector:@selector(request:didReceiveData:totalBytesReceived:totalBytesExpectedToReceive:)]) {
|
|
[[_request delegate] request:_request didReceiveData:[data length] totalBytesReceived:[_body length] totalBytesExpectedToReceive:_httpURLResponse.expectedContentLength];
|
|
}
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
RKLogDebug(@"NSHTTPURLResponse Status Code: %ld", (long)[response statusCode]);
|
|
RKLogDebug(@"Headers: %@", [response allHeaderFields]);
|
|
_httpURLResponse = [response retain];
|
|
[_request invalidateTimeoutTimer];
|
|
if ([[_request delegate] respondsToSelector:@selector(request:didReceiveResponse:)]) {
|
|
[[_request delegate] request:_request didReceiveResponse:self];
|
|
}
|
|
}
|
|
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
RKLogTrace(@"Read response body: %@", [self bodyAsString]);
|
|
[_request didFinishLoad:self];
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
_failureError = [error retain];
|
|
[_request invalidateTimeoutTimer];
|
|
[_request didFailLoadWithError:_failureError];
|
|
}
|
|
|
|
- (NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled(nil);
|
|
RKLogWarning(@"RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?");
|
|
return nil;
|
|
}
|
|
|
|
// In the event that the url request is a post, this delegate method will be called before
|
|
// either connection:didReceiveData: or connection:didReceiveResponse:
|
|
// However this method is only called if there is payload data to be sent.
|
|
// Therefore, we ensure the delegate recieves the did start loading here and
|
|
// in connection:didReceiveResponse: to ensure that the RKRequestDelegate
|
|
// callbacks get called in the correct order.
|
|
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
|
|
{
|
|
RKResponseIgnoreDelegateIfCancelled();
|
|
[_request invalidateTimeoutTimer];
|
|
|
|
if ([[_request delegate] respondsToSelector:@selector(request:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) {
|
|
[[_request delegate] request:_request didSendBodyData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
|
|
}
|
|
}
|
|
|
|
- (NSString *)localizedStatusCodeString
|
|
{
|
|
return [NSHTTPURLResponse localizedStringForStatusCode:[self statusCode]];
|
|
}
|
|
|
|
- (NSData *)body
|
|
{
|
|
return _body;
|
|
}
|
|
|
|
- (NSString *)bodyEncodingName
|
|
{
|
|
return [_httpURLResponse textEncodingName];
|
|
}
|
|
|
|
- (NSStringEncoding)bodyEncoding
|
|
{
|
|
CFStringEncoding cfEncoding = kCFStringEncodingInvalidId;
|
|
NSString *textEncodingName = [self bodyEncodingName];
|
|
if (textEncodingName) {
|
|
cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)textEncodingName);
|
|
}
|
|
return (cfEncoding == kCFStringEncodingInvalidId) ? self.request.defaultHTTPEncoding : CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
|
}
|
|
|
|
- (NSString *)bodyAsString
|
|
{
|
|
return [[[NSString alloc] initWithData:self.body encoding:[self bodyEncoding]] autorelease];
|
|
}
|
|
|
|
- (id)bodyAsJSON
|
|
{
|
|
[NSException raise:nil format:@"Reimplemented as parsedBody"];
|
|
return nil;
|
|
}
|
|
|
|
- (id)parsedBody:(NSError **)error
|
|
{
|
|
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:[self MIMEType]];
|
|
if (! parser) {
|
|
RKLogWarning(@"Unable to parse response body: no parser registered for MIME Type '%@'", [self MIMEType]);
|
|
return nil;
|
|
}
|
|
id object = [parser objectFromString:[self bodyAsString] error:error];
|
|
if (object == nil) {
|
|
if (error && *error) {
|
|
RKLogError(@"Unable to parse response body: %@", [*error localizedDescription]);
|
|
}
|
|
return nil;
|
|
}
|
|
return object;
|
|
}
|
|
|
|
- (NSString *)failureErrorDescription
|
|
{
|
|
if ([self isFailure]) {
|
|
return [_failureError localizedDescription];
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (BOOL)wasLoadedFromCache
|
|
{
|
|
return (_responseHeaders != nil);
|
|
}
|
|
|
|
- (NSURL *)URL
|
|
{
|
|
if ([self wasLoadedFromCache]) {
|
|
return [NSURL URLWithString:[_responseHeaders valueForKey:RKRequestCacheURLHeadersKey]];
|
|
}
|
|
return [_httpURLResponse URL];
|
|
}
|
|
|
|
- (NSString *)MIMEType
|
|
{
|
|
if ([self wasLoadedFromCache]) {
|
|
return [_responseHeaders valueForKey:RKRequestCacheMIMETypeHeadersKey];
|
|
}
|
|
return [_httpURLResponse MIMEType];
|
|
}
|
|
|
|
- (NSInteger)statusCode
|
|
{
|
|
if ([self wasLoadedFromCache]) {
|
|
return [[_responseHeaders valueForKey:RKRequestCacheStatusCodeHeadersKey] intValue];
|
|
}
|
|
return ([_httpURLResponse respondsToSelector:@selector(statusCode)] ? [_httpURLResponse statusCode] : 200);
|
|
}
|
|
|
|
- (NSDictionary *)allHeaderFields
|
|
{
|
|
if ([self wasLoadedFromCache]) {
|
|
return _responseHeaders;
|
|
}
|
|
return ([_httpURLResponse respondsToSelector:@selector(allHeaderFields)] ? [_httpURLResponse allHeaderFields] : nil);
|
|
}
|
|
|
|
- (NSArray *)cookies
|
|
{
|
|
return [NSHTTPCookie cookiesWithResponseHeaderFields:self.allHeaderFields forURL:self.URL];
|
|
}
|
|
|
|
- (BOOL)isFailure
|
|
{
|
|
return (nil != _failureError);
|
|
}
|
|
|
|
- (BOOL)isInvalid
|
|
{
|
|
return ([self statusCode] < 100 || [self statusCode] > 600);
|
|
}
|
|
|
|
- (BOOL)isInformational
|
|
{
|
|
return ([self statusCode] >= 100 && [self statusCode] < 200);
|
|
}
|
|
|
|
- (BOOL)isSuccessful
|
|
{
|
|
return (([self statusCode] >= 200 && [self statusCode] < 300) || ([self wasLoadedFromCache]));
|
|
}
|
|
|
|
- (BOOL)isRedirection
|
|
{
|
|
return ([self statusCode] >= 300 && [self statusCode] < 400);
|
|
}
|
|
|
|
- (BOOL)isClientError
|
|
{
|
|
return ([self statusCode] >= 400 && [self statusCode] < 500);
|
|
}
|
|
|
|
- (BOOL)isServerError
|
|
{
|
|
return ([self statusCode] >= 500 && [self statusCode] < 600);
|
|
}
|
|
|
|
- (BOOL)isError
|
|
{
|
|
return ([self isClientError] || [self isServerError]);
|
|
}
|
|
|
|
- (BOOL)isOK
|
|
{
|
|
return ([self statusCode] == 200);
|
|
}
|
|
|
|
- (BOOL)isCreated
|
|
{
|
|
return ([self statusCode] == 201);
|
|
}
|
|
|
|
- (BOOL)isNoContent
|
|
{
|
|
return ([self statusCode] == 204);
|
|
}
|
|
|
|
- (BOOL)isNotModified
|
|
{
|
|
return ([self statusCode] == 304);
|
|
}
|
|
|
|
- (BOOL)isUnauthorized
|
|
{
|
|
return ([self statusCode] == 401);
|
|
}
|
|
|
|
- (BOOL)isForbidden
|
|
{
|
|
return ([self statusCode] == 403);
|
|
}
|
|
|
|
- (BOOL)isNotFound
|
|
{
|
|
return ([self statusCode] == 404);
|
|
}
|
|
|
|
- (BOOL)isConflict
|
|
{
|
|
return ([self statusCode] == 409);
|
|
}
|
|
|
|
- (BOOL)isGone
|
|
{
|
|
return ([self statusCode] == 410);
|
|
}
|
|
|
|
- (BOOL)isUnprocessableEntity
|
|
{
|
|
return ([self statusCode] == 422);
|
|
}
|
|
|
|
- (BOOL)isRedirect
|
|
{
|
|
return ([self statusCode] == 301 || [self statusCode] == 302 || [self statusCode] == 303 || [self statusCode] == 307);
|
|
}
|
|
|
|
- (BOOL)isEmpty
|
|
{
|
|
return ([self statusCode] == 201 || [self statusCode] == 204 || [self statusCode] == 304);
|
|
}
|
|
|
|
- (BOOL)isServiceUnavailable
|
|
{
|
|
return ([self statusCode] == 503);
|
|
}
|
|
|
|
- (NSString *)contentType
|
|
{
|
|
return ([[self allHeaderFields] objectForKey:@"Content-Type"]);
|
|
}
|
|
|
|
- (NSString *)contentLength
|
|
{
|
|
return ([[self allHeaderFields] objectForKey:@"Content-Length"]);
|
|
}
|
|
|
|
- (NSString *)location
|
|
{
|
|
return ([[self allHeaderFields] objectForKey:@"Location"]);
|
|
}
|
|
|
|
- (BOOL)isHTML
|
|
{
|
|
NSString *contentType = [self contentType];
|
|
return (contentType && ([contentType rangeOfString:@"text/html"
|
|
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0 ||
|
|
[self isXHTML]));
|
|
}
|
|
|
|
- (BOOL)isXHTML
|
|
{
|
|
NSString *contentType = [self contentType];
|
|
return (contentType &&
|
|
[contentType rangeOfString:@"application/xhtml+xml"
|
|
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
|
}
|
|
|
|
- (BOOL)isXML
|
|
{
|
|
NSString *contentType = [self contentType];
|
|
return (contentType &&
|
|
[contentType rangeOfString:@"application/xml"
|
|
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
|
}
|
|
|
|
- (BOOL)isJSON
|
|
{
|
|
NSString *contentType = [self contentType];
|
|
return (contentType &&
|
|
[contentType rangeOfString:@"application/json"
|
|
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
|
}
|
|
|
|
@end
|