AFNetworking: обрабатывать ошибку во всем мире и повторять запрос
У меня есть прецедент, который должен быть довольно распространенным я не могу найти простой способ справиться с ним с AFNetworking:
Всякий раз, когда сервер возвращает определенный код состояния для любого запроса, я хочу:
- удалить кеш-аутентификацию аутентификации
- повторно аутентифицировать (который является отдельным запросом)
- повторить неудавшийся запрос.
Я думал, что это можно сделать с помощью некоторого глобального обработчика завершения/ошибки в AFHTTPClient
, но я не нашел ничего полезного. Итак, какой "правильный" способ делать то, что я хочу? Переопределить enqueueHTTPRequestOperation:
в моем подклассе AFHTTPClient
, скопировать операцию и обернуть обработчик исходного завершения блоком, который делает то, что я хочу (повторная аутентификация, операция копирования в очереди)? Или я вообще ошибаюсь?
Спасибо!
EDIT: удалена ссылка на код состояния 401, так как это, вероятно, зарезервировано для HTTP basic, пока я использую токен auth.
Ответы
Ответ 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.