2012-10-15 20 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错误时)并再次发出请求。 –