AFJSONRequestOperation has a successBlock block for when the request succeeds, but there is no way to set the "Authentication Block" other than on the operation itself, so if that fails then there is no way to send back to the caller of the AFJSONRequestOperation. Is that correct, or am I missing something?
I would expect to be able to tell the caller that the whole operation failed because authorization failed.
Actually the method takes two block as a parameters success and failure
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:successBlock failure:failureBlock];
So in the failure block you should be able to notify the caller of the failure
Related
Sending few post request using
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
I want to perform one get request once all of the above request have processed so I have created AFHTTPRequestOperation and adding dependency as
for (AFHTTPRequestOperation *operation in manager.operationQueue.operations ) {
[AFHTTPRequestOperationObject addDependency:operation];
}
[manager.operationQueue addOperation: AFHTTPRequestOperationObject];
But the operation is performed before the completion of existing post request.
You shouldn't use NSOperation dependencies to solve this problem. In your case, later operations rely on processing with completionBlock but NSOperationQueue and AFNetworking both consider that work a side effect.
completionBlock is:
the block to execute after the operation’s main task is completed.
(my emphasis).
In the case of AFHTTPRequestOperation, "the operation’s main task" is "making an HTTP request". The "main task" doesn't include parsing JSON, persisting data, checking HTTP status codes, etc. - that's all handled in completionBlock.
To perform one request after other requests have processed, you'll need to make that request in the completion handler, once you've verified you have all the data you need to proceed.
While using OperationQueues, it is not necessary that the responses will be received in the same order as the requests made. You will have to make sure that you make the GETrequest after complete execution of multiple POST requests. This explains the process to perform the operations serially with a completionHandler (which in your case should be the GET request that you want to execute at the completion of the POST requests.)
It appears that AFHTTPRequestOperation(s) complete as soon as the HTTP call is made and returns. The success and failure blocks are called after these operations are complete. So if you have NSOperation A dependent on B which is a AFHTTPRequestOperation then you may see B run, followed by A, followed by B's success block. Seems to work if you create a "finished" operation and make A dependent on it. Then have B enqueue the "finished" operation for running when B calls its success or failure block. The "finished" operation in effect signals A that B has done all you wanted it to do.
I tried AFHTTPRequestOperation objects combined with other NSOperation objects placed into a queue. But now I know that in AFHTTPRequestOperation only requests are performed in correct order (not response processing blocks).
I don't need the correct order of requests but I need to handle their responses in a correct order and send a "success" notification at the end. If one of the steps is failed then cancel the sequence. The only idea I have is the following:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//synchronous request1
...
//handle request1 response
...
//synchronous request2
...
//handle request2 response
...
//send notification about success or failure
...
}];
It looks crazy but correct. Is this code correct? Could you advice anything better?
You're right. The basic thing is that you have to synchronize the responses based on completion. and it does not matter if you use sync or async way. scheme: request(sync/async) -> completion -> request 2 (sync/async) -> completion...etc..
I couldn't find a reasonable answer so i'll ask for this specific case.
I must perform a http call (setting a specific header as a token value) and parse the returned json. The json can both return a "operation completed" message or a "token expired" message.
In case of token expired i must execute another http call which will provide me the refreshed token, set the token as header and re-execute the original http call.
I decided to adopt this solution: from the main thread, i'm gonna create another thread using
...
dispatch_async(feedQueue
...
and in this thread i'm gonna performing the above descripted calls as synchronous calls using
...
[NSURLConnection sendSynchronousRequest:urlRequest
...
and only at the end of the flow i call the main thread passing results to a block.
This way code is simple, easy to read, it has a linear flow, it is completely scoped inside a unique thread, and i don't mess with delegates and different "finite-states" to manage calls chain.
But i'm not sure if this is the best approach for my specific use-case as i've read similar questions where people suggest to adopt asynchronous calls, using both finite-states or NSOperation instances, but it seems to me that both these approaches based on asynchronous calls, are not the best solutions for a http calls chain.
What is the best approach? How can i correctly implement a chain of http call?
The correct way to set this up would be to use asynchronous calls for NSURLConnection. In the connectionDidFinishLoading you will intercept the connection that just finished and launch the next chained http connection.
Setup a private variable called _connection1;
So basically, you will do something like this
NSURLRequest * request1 = ...
_connection1 = [[NSURLConnection alloc] initWithRequest:request1 delegate:self];
In the
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if([connection isEqual:_connection1]){
NSURLRequest *request2 = ...
_connection2 = [NSURLConnection...]
}
if([connection isEqual:_connection2]){
NSURLRequest *request3 = ...
_connection2 = [NSURLConnection...request3 ]
// And so on ....
}
}
I am trying to make my app handle HTTP client errors. Ideally, I would like to run a static method whenever such an error occurs, regardless of the request that caused it.
It seems that I am doing something wrong -- I have tried to add a RKDynamicResponseDescriptor to call the method, but it doesn't get called. Whenever my server responds with an error, the request never finishes (the network activity indicator keeps spinning and the AFNetworkingOperationDidFinishNotification is never posted.)
I can reproduce the behavior by creating a new AFHTTPRequest request like this:
request = [[RKObjectManager sharedManager].HTTPClient requestWithMethod:#"GET" path:#"events" parameters:nil];
AFHTTPRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"%#", JSON);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
Neither the success nor the failure block is called. If I replace the first line with
request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
the blocks get called. How can I make RestKit handle the HTTP error response?
Edit 1: Apparently this happens in both cases when the server returns 401 with an authentication challenge. I have tried setting a breakpoint in RKHTTPRequestOperation in didReceiveAuthenticationChallenge, but it is not triggered. My Fiddler shows that only one request is sent.
After lots of fiddling, I think I have found the reason for this problem. The AFUrlConnectionRequest class implements the connection:willSendRequestForAuthenticationChallenge:delegate method when SSL pinning is enabled. The implementation simply returns if the authentication challenge is not related to SSL.
From the documentation of NSUrlConnectionDelegate:
This method allows the delegate to make an informed decision about connection authentication at once. If the delegate implements this method, it has no need to implement
connection:canAuthenticateAgainstProtectionSpace:,
connection:didReceiveAuthenticationChallenge:,
connectionShouldUseCredentialStorage:.
In fact, these other methods are not invoked.
The methods will not be invoked, and thus RestKit never knows that the request resulted in HTTP error 401.
I solved the problem by removing this define, so the delegate method is not implemented:
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_
The API I'm developing against requires me to present an authentication token in a custom HTTP header. This token expires every few minutes, and could happen while the user is still within the app as long as they have been idle long enough. When the token has expired I receive a 403 response but I only find out after attempting a request.
What's the best way to get RestKit to automatically reauthenticate and retry the request so I don't have to put in this logic everywhere I make a request? Responses to similar questions have suggested using the RKRequestDelegate protocol or the RKObjectLoaderDelegate protocol but unfortunately these are no longer part of RestKit (as of 0.20).
Any idea what the "correct" approach should be now? Should I be subclassing RKObjectManager and tacking on a retry to each of the request operations or should I provide a custom HTTPOperation or HTTPClient class or is there some better approach altogether? Thanks!
Catch it in Failure block , and check for the status code and re-do the authentication
RKObjectRequestOperation *requestOp = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[getObjResp]];
[requestOp setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
....
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
// Here your status code check
// Here your retry-code
}