Подтвердить что ты не робот

AFNetworking: обрабатывать ошибку во всем мире и повторять запрос

У меня есть прецедент, который должен быть довольно распространенным я не могу найти простой способ справиться с ним с AFNetworking:

Всякий раз, когда сервер возвращает определенный код состояния для любого запроса, я хочу:

  • удалить кеш-аутентификацию аутентификации
  • повторно аутентифицировать (который является отдельным запросом)
  • повторить неудавшийся запрос.

Я думал, что это можно сделать с помощью некоторого глобального обработчика завершения/ошибки в AFHTTPClient, но я не нашел ничего полезного. Итак, какой "правильный" способ делать то, что я хочу? Переопределить enqueueHTTPRequestOperation: в моем подклассе AFHTTPClient, скопировать операцию и обернуть обработчик исходного завершения блоком, который делает то, что я хочу (повторная аутентификация, операция копирования в очереди)? Или я вообще ошибаюсь?

Спасибо!

EDIT: удалена ссылка на код состояния 401, так как это, вероятно, зарезервировано для HTTP basic, пока я использую токен auth.

4b9b3361

Ответ 1

В регистре метода инициализации AFHTTPClient для AFNetworkingOperationDidFinishNotification, который будет размещен после завершения запроса.

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

В обработчике уведомлений проверьте код состояния и copy AFHTTPRequestOperation или создайте новый.

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

Как правило, вам не нужно это делать и просто обрабатывать аутентификацию с помощью этого метода AFNetworking:

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

Ответ 2

Я использую альтернативное средство для этого с 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;

}

Ответ 3

Принял аналогичный подход, но я не смог получить объект кода статуса с ответом 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");
    }
}

Ответ 4

Вот реализация Swift пользователя @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 (...) - это метод расширения, который я написал, чтобы получить новый токен с сервера.

Ответ 5

Если вы подклассифицируете 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) {

        }
     }
}];

EDIT: На самом деле, если вам нужно обработать ошибку, возвращенную в объекте ответа, вышеуказанный метод не будет выполнять задание. Один из способов, если вы являетесь подклассом AFHTTPSessionManager, может быть подклассом и установить настраиваемый сериализатор ответов с его перегрузкой responseObjectForResponse:data:error: следующим образом:

@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

и установите его в подкласс AFHTTPSessionManager:

@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

Ответ 6

Чтобы гарантировать, что несколько обновлений токенов не выдаются примерно в одно и то же время, полезно либо поставить очередь на сетевые запросы, либо заблокировать очередь при обновлении токена, либо добавить блокировку мьютекса (директива @synchronized) к вашему токену refresh.