2012-10-15 21 views
35

我有,應該是相當常見的用例,但我無法找到一個簡單的方法與AFNetworking來處理它:AFNetworking:全球處理錯誤,並重復要求

每當服務器返回一個特定的狀態代碼任何要求,我想:

  • 刪除緩存的身份驗證令牌
  • 重新認證(這是一個單獨的請求)
  • 重複失敗的請求。

我認爲這可以通過AFHTTPClient中的某個全局完成/錯誤處理程序完成,但是我沒有找到任何有用的東西。那麼,做我想做的事情的「正確」方式是什麼?在我的AFHTTPClient子類中覆蓋enqueueHTTPRequestOperation:,複製操作並將原始完成處理程序與一個可以執行我想要的操作的塊(重新驗證,排入複製的操作)進行包裝?或者我完全在錯誤的軌道上?

謝謝!

編輯:刪除對401狀態碼的引用,因爲這可能保留爲HTTP基本,而我使用令牌認證。

回答

20

在AFHTTPClient的init方法註冊表AFNetworkingOperationDidFinishNotification將在請求完成後發佈。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil]; 

在通知處理器檢查狀態代碼和copyAFHTTPRequestOperation或創建一個新的。

- (void)HTTPOperationDidFinish:(NSNotification *)notification { 
    AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object]; 

    if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) { 
     return; 
    } 

    if ([operation.response statusCode] == 401) { 
     // enqueue a new request operation here 
    } 
} 

編輯:

一般來說,你不應該需要做的,只是處理這個AFNetworking方法的認證:

- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; 
+0

我還沒有想到通知 - 比自定義排隊請求更好,也更少侵入。非常感謝!至於驗證質詢塊:我實際上使用的是令牌驗證而不是基本驗證,所以我猜這是行不通的,對吧?對不起,您提到了401.您會誤導獎勵問題:「無效令牌」的正確響應代碼是什麼? 400? –

+0

我不確定「無效令牌」的正確響應代碼是什麼。也許403更合適。 – Felix

+1

AFAIK 403更多的是失敗*授權*而不是認證(「認證成功(如果有的話),但你不能這樣做」)。但沒關係,那是另一個問題。再次感謝你的幫助。 –

27

我使用的替代方法與AFNetworking 2.0這樣做。

您可以繼承dataTaskWithRequest:success:failure:幷包裝傳遞的完成塊並進行一些錯誤檢查。例如,如果您使用OAuth,則可以查看401錯誤(到期)並刷新您的訪問令牌。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)urlRequest completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))originalCompletionHandler{ 

    //create a completion block that wraps the original 
    void (^authFailBlock)(NSURLResponse *response, id responseObject, NSError *error) = ^(NSURLResponse *response, id responseObject, NSError *error) 
    { 
     NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; 
     if([httpResponse statusCode] == 401){ 
      NSLog(@"401 auth error!"); 
      //since there was an error, call you refresh method and then redo the original task 
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 

       //call your method for refreshing OAuth tokens. This is an example: 
       [self refreshAccessToken:^(id responseObject) { 

        NSLog(@"response was %@", responseObject); 
        //store your new token 

        //now, queue up and execute the original task    
        NSURLSessionDataTask *originalTask = [super dataTaskWithRequest:urlRequest completionHandler:originalCompletionHandler]; 
        [originalTask resume]; 
       }];      
      }); 
     }else{ 
      NSLog(@"no auth error"); 
      originalCompletionHandler(response, responseObject, error); 
     } 
    }; 

    NSURLSessionDataTask *task = [super dataTaskWithRequest:urlRequest completionHandler:authFailBlock]; 

    return task; 

} 
+0

令人驚歎的實施和方法。謝謝。 –

2

採取了類似的做法,但這樣我需要採取行動的不同方案我無法得到與phix23的回答狀態代碼對象。 AFNetworking 2.0改變了一些事情。

-(void)networkRequestDidFinish: (NSNotification *) notification 
{ 
    NSError *error = [notification.userInfo objectForKey:AFNetworkingTaskDidCompleteErrorKey]; 
    NSHTTPURLResponse *httpResponse = error.userInfo[AFNetworkingOperationFailingURLResponseErrorKey]; 
    if (httpResponse.statusCode == 401){ 
     NSLog(@"Error was 401"); 
    } 
} 
+0

你能把這個放在appDelegate中嗎?還是需要進入你調用請求的viewcontroller? –

+1

@TriannBrannon,你用它來補充選定的答案。而不是使用@selector(HTTPOperationDidFinish :)你使用@selector(networkRequestDidFinish :)而不是AFNetworkingOperationDidFinishNotification你使用AFNetworkingTaskDidCompleteNotification。這就是我的工作原理 –

1

如果你繼承AFHTTPSessionManager或直接使用一個AFURLSessionManager你可以使用下面的方法來設置完成一個任務後執行的塊:

/** 
Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`. 

@param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task. 
*/ 
- (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block; 

只是執行任何你想做的在它的會話的每個任務:

[self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) { 
    if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) { 
     NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; 
     if (httpResponse.statusCode == 500) { 

     } 
    } 
}]; 

編輯: 事實上,如果您需要處理響應對象中返回的錯誤,則上述方法將不會執行此任務。如果 一種方式,你都繼承AFHTTPSessionManager可能是子類,並設置自定義響應串行與它的過載像responseObjectForResponse:data:error::在您的AFHTTPSessionManager

@interface MyJSONResponseSerializer : AFJSONResponseSerializer 
@end 

@implementation MyJSONResponseSerializer 

#pragma mark - AFURLResponseSerialization 
- (id)responseObjectForResponse:(NSURLResponse *)response 
          data:(NSData *)data 
          error:(NSError *__autoreleasing *)error 
{ 
    id responseObject = [super responseObjectForResponse:response data:data error:error]; 

    if ([responseObject isKindOfClass:[NSDictionary class]] 
     && /* .. check for status or error fields .. */) 
    { 
     // Handle error globally here 
    } 

    return responseObject; 
} 

@end 

,並設置:

@interface MyAPIClient : AFHTTPSessionManager 
+ (instancetype)sharedClient; 
@end 

@implementation MyAPIClient 

+ (instancetype)sharedClient { 
    static MyAPIClient *_sharedClient = nil; 
    static dispatch_once_t onceToken; 

    dispatch_once(&onceToken, ^{ 
     _sharedClient = [[MyAPIClient alloc] initWithBaseURL:[NSURL URLWithString:MyAPIBaseURLString]]; 
     _sharedClient.responseSerializer = [MyJSONResponseSerializer serializer]; 
    }); 

    return _sharedClient; 
} 

@end 
2

這裏是斯威夫特實現用戶@adamup的answer

class SessionManager:AFHTTPSessionManager{ 
static let sharedInstance = SessionManager() 
override func dataTaskWithRequest(request: NSURLRequest!, completionHandler: ((NSURLResponse!, AnyObject!, NSError!) -> Void)!) -> NSURLSessionDataTask! { 

    var authFailBlock : (response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void = {(response:NSURLResponse!, responseObject:AnyObject!, error:NSError!) -> Void in 

     var httpResponse = response as! NSHTTPURLResponse 

     if httpResponse.statusCode == 401 { 
      //println("auth failed") 

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), {() -> Void in 

       self.refreshToken(){ token -> Void in 
        if let tkn = token{ 
         var mutableRequest = request.mutableCopy() as! NSMutableURLRequest 
         mutableRequest.setValue(tkn, forHTTPHeaderField: "Authorization") 
         var newRequest = mutableRequest.copy() as! NSURLRequest 
         var originalTask = super.dataTaskWithRequest(newRequest, completionHandler: completionHandler) 
         originalTask.resume() 
        }else{ 
         completionHandler(response,responseObject,error) 
        } 

       } 
      }) 
     } 
     else{ 
      //println("no auth error") 
      completionHandler(response,responseObject,error) 
     } 
    } 
    var task = super.dataTaskWithRequest(request, completionHandler:authFailBlock) 

    return task 
}} 

其中,refreshToken(...)是我寫來從服務器獲取新令牌的擴展方法。

+0

你知道Alamofire如何實現這個嗎? – horseshoe7

0

爲了確保多個令牌刷新不會在大約同一時間發出,在令牌刷新時排隊您的網絡請求並阻止隊列,或向您的計算機添加互斥鎖(@synchronized指令)令牌刷新方法。

+0

你能幫助AFNetworking訪問令牌週期實現嗎?我們不希望爲每個請求添加授權頭。必須有一個簡潔的方式來刷新令牌(在收到401錯誤時)並再次發出請求。 –