Episode #42

Retrying HTTP Requests

30 minutes
Published on November 16, 2012

This video is only available to subscribers. Get access to this video and 572 others.

We pick up where we left off in Episode 41 and implement a mechanism to automatically detect expired authentication tokens, re-login the user automatically, and retry the original request. This takes a bit of refactoring and use of blocks, but allows for transparent HTTP retries.

Episode Links

The code here builds off of what was covered in Episode 41.

Retrying requests

Retrying the requests takes a bit of refactoring support. We'll add this behavior to our new UserAuthenticator class.

- (void)refreshTokenAndRetryOperation:(AFHTTPRequestOperation *)operation
                              success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
    NSString *username = [self.credentialStore username];
    NSString *password = [self.credentialStore password];

    [self loginWithUsername:username
                   password:password
                    success:^{
                        // retry request
                        NSLog(@"RETRYING REQUEST");
                        AFHTTPRequestOperation *retryOperation = [self retryOperationForOperation:operation];
                        [retryOperation setCompletionBlockWithSuccess:success
                                                              failure:failure];

                        [retryOperation start];

                    } failure:^(NSString *errorMessage) {
                        failure(operation, nil);
                    }];
}

- (AFHTTPRequestOperation *)retryOperationForOperation:(AFHTTPRequestOperation *)operation {
    NSMutableURLRequest *request = [operation.request mutableCopy];
    [request addValue:nil forHTTPHeaderField:@"auth_token"];
    [request addValue:[self.credentialStore authToken] forHTTPHeaderField:@"auth_token"];

    AFHTTPRequestOperation *retryOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    return retryOperation;
}

Note that we can't just simply clone the original operation, nor reuse the original completion block. Instead we pass in two blocks that we can use for success / failure. To create the new operation, we first make a mutable copy of the url request. This is so that we can alter the HTTP Headers and make sure we replace the old token with the new one.

Using the retry behavior

- (void)fetchSecureMessageWithSuccess:(SecureMessageBlock)success
                              failure:(SecureMessageErrorBlock)failure {
    void (^processSuccessBlock)(AFHTTPRequestOperation *operation, id responseObject) = ^(AFHTTPRequestOperation *operation, id responseObject) {
        success(operation.responseString);
    };

    void (^processFailureBlock)(AFHTTPRequestOperation *operation, NSError *error) = ^(AFHTTPRequestOperation *operation, NSError *error) {
        if (operation.response.statusCode == 500) {
            failure(@"Something went wrong!");
        } else {
            NSString *errorMessage = [self errorMessageForResponse:operation];
            failure(errorMessage);
        }
    };

    [[AuthAPIClient sharedClient] getPath:@"/home/index.json"
                               parameters:nil
                                  success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                      processSuccessBlock(operation, responseObject);
                                  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                      processFailureBlock(operation, error);

                                      if (operation.response.statusCode == 401) {
                                          [self.credentialStore setAuthToken:nil];

                                          // retry logic
                                          UserAuthenticator *auth = [[UserAuthenticator alloc] init];
                                          [auth refreshTokenAndRetryOperation:operation
                                                                      success:processSuccessBlock
                                                                      failure:processFailureBlock];
                                      }
                                  }];
}

Here we take our original success & failure behavior and wrap them in blocks. This is so they can be used in the call to refreshTokenAndRetryOperation:success:failure:.