2012-10-15 24 views
35

Ho un caso d'uso che dovrebbe essere piuttosto comune, ma non riesco a trovare un modo semplice per gestire con AFNetworking:AFNetworking: Maniglia errore a livello globale e ripetere richiesta

Ogni volta che il server restituisce un codice di stato specifico per qualsiasi richiesta, voglio:

  • rimuovere una di autenticazione nella cache del token
  • riautenticare (che è una richiesta separata)
  • ripetere la richiesta non riuscita.

Ho pensato che questo potrebbe essere fatto tramite un completamento globale/gestore di errori in AFHTTPClient, ma non ho trovato nulla di utile. Quindi, qual è il modo "giusto" per fare ciò che voglio? Sostituire enqueueHTTPRequestOperation: nella sottoclasse AFHTTPClient, copiare l'operazione e avvolgere il gestore di completamento originale con un blocco che esegue ciò che desidero (re-autenticare, accodare l'operazione copiata)? O sono sulla strada sbagliata del tutto?

Grazie!

MODIFICA: Rimosso il riferimento al codice di stato 401, poiché è probabilmente riservato per HTTP basic mentre sto usando l'autenticazione token.

risposta

20

Nel registro metodo init di AFHTTPClient per il AFNetworkingOperationDidFinishNotification che verrà pubblicato dopo che una richiesta è stata completata.

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

Nel gestore di notifica controllare il codice di stato e copy la AFHTTPRequestOperation o crearne uno nuovo.

- (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 
    } 
} 

EDIT:

In generale non dovreste aver bisogno di farlo e solo gestire l'autenticazione con questo metodo AFNetworking:

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

Non ho pensato alle notifiche: molto meglio e meno invadente della personalizzazione dell'accodamento delle richieste. Molte grazie! Per quanto riguarda il blocco challenge di autenticazione: in realtà sto usando l'autenticazione token piuttosto che l'autenticazione di base, quindi immagino che non funzionerà, giusto? Ci scusiamo per averti ingannato menzionando 401. Domanda bonus: quale sarebbe il codice di risposta corretto per "token non valido"? 400? –

+0

Non sono sicuro di quale sia il codice di risposta corretto per "token non valido". Forse il 403 è più appropriato. – Felix

+1

AFAIK 403 è più per l'autorizzazione * non riuscita * piuttosto che per l'autenticazione ("autenticazione riuscita (se presente), ma non è consentito farlo"). Ma non importa, questa è un'altra domanda. Grazie ancora per il vostro aiuto. –

27

Io uso significa un'alternativa per fare questo con AFNetworking 2.0 .

È possibile creare una sottoclasse di dataTaskWithRequest:success:failure: e avvolgere il blocco di completamento passato con un controllo degli errori. Ad esempio, se stai lavorando con OAuth, potresti vedere un errore 401 (scadenza) e aggiornare il tuo token di accesso.

- (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

Incredibile implementazione e approccio. Grazie. –

2

ha adottato un approccio simile, ma non ho potuto ottenere l'oggetto codice di stato con la risposta di phix23 quindi ho bisogno di un piano diverso di azione. AFNetworking 2.0 ha cambiato un paio di cose.

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

Sei in grado di inserirlo nell'appDelegate o è necessario andare nel viewcontroller in cui hai chiamato la richiesta? –

+1

@TriannBrannon, lo hai usato per completare la risposta selezionata. Invece di usare @selector (HTTPOperationDidFinish :) si usa @selector (networkRequestDidFinish :) e invece di AFNetworkingOperationDidFinishNotification si usa AFNetworkingTaskDidCompleteNotification. Ecco come ho funzionato –

1

Se si sottoclassi AFHTTPSessionManager o utilizzando direttamente una AFURLSessionManager è possibile utilizzare il seguente metodo per impostare un blocco eseguito dopo il completamento di un compito:

/** 
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; 

solo eseguire quello che vuoi fare per ogni attività della sessione in esso:

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

     } 
    } 
}]; 

EDIT: Infatti, se è necessario gestire un errore restituito nell'oggetto risposta, il metodo sopra non eseguirà il lavoro. Un modo se si sta sottoclasse AFHTTPSessionManager potrebbe essere quella di sottoclasse e impostare un serializzatore di risposta personalizzato con il suo responseObjectForResponse:data:error: sovraccarico del genere:

@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 

e metterlo nel vostro AFHTTPSessionManager sottoclasse:

@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

Ecco il Swift realizzazione di 's 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 
}} 
@adamup utente

dove refreshToken (...) è un metodo di estensione che ho scritto per ottenere un nuovo token dal server.

+0

Sai come è stato realizzato con Alamofire? – horseshoe7

0

Per garantire che più aggiornamenti di token non vengano rilasciati più o meno allo stesso tempo, è utile accodare le richieste di rete e bloccare la coda quando il token si aggiorna oppure aggiungere un blocco mutex (@synchronized directive) al metodo di aggiornamento dei token.

+0

puoi aiutare con l'implementazione del ciclo di accesso token con AFNetworking. Non vogliamo aggiungere l'intestazione di autorizzazione a ogni richiesta. Deve esserci un modo pulito per aggiornare il token (ricevendo l'errore 401) ed effettuare nuovamente la richiesta. –

Problemi correlati