RestKit requests hang on HTTP errors - ios

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_

Related

Caching in AFNetworking+UIImageView doesn't respect any http cache policy? (hopefully I'm wrong)

It seems if I call:
setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
That the resulting image will be put into AFNetworking's AFImageCache regardless of any cache policy set in HTTP response headers and will remain there and be fetched from there regardless of age, on subsequent requests to the same URL.
Is that accurate?
The only thing that forces it to ignore the cache, is to set NSURLRequestReloadIgnoringCacheData or NSURLRequestReloadIgnoringLocalAndRemoteCacheData in the request (by the way the resulting image will get re-cached by AFNetworking, so that's one way to force it to refresh it's cache).
Now, sure if the AFImageCache returns no hit, then AFNetworking will (I think), for exactly one request, use Apple's default NSURLCache which in theory does respect http cache headers. But that is just one request, because AFNetworking, then throws the result into its own permanent, non-http aware cache.
Let me know if I've got this wrong or missed anything.
According to Matt, the developer for AFNetworking, the solution to this is to override the behavior by setting the shared image cache to another class that conforms to the AFImageCache protocol. https://github.com/AFNetworking/AFNetworking/issues/2731

How do we mock an azure end point?

I am working with an azure API endpoint. ....azure-api.net/.... When I try to view a HTTP request / response using Charles proxy, the HTTP response is empty. The request works when I turn the proxy off.
I want to intercept the requests and mock the response for the purposes of automation tests.
I can view other HTTPs end-points to other servers using Charles proxy. So I believe there is something special about azure that is preventing the request from completing.
How does azure know there is a proxy in the middle and it is not talking to the client?
Is there anyway to configure the azure API to allow Charles to work? (Since viewing HTTP traffic is useful for development)
Is there another method that will allow the traffic to azure to be mocked? E.g. host redirect?
I am using standard iOS networking code
NSURL *url = [NSURL URLWithString:#"https://MyDomain.azure-api.net/a/b/2?subscription-key=myKey"];
[[[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"%#, error: %#", [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding], error);
;
}] resume];
Below are screenshots from Charles Proxy. There is no response after the connect request.
In order to intercept the request and return a fake response, you could use OHHTTPStubs
To be enable to do it, you should either log the response when debugging from Xcode or use the documentation.
Before you execute your test, call
stub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.absoluteString isEqualToString:#"https://MyDomain.azure-api.net/a/b/2?subscription-key=myKey"];
}
withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
NSString *fixture = OHPathForFileInBundle(#"fakeResponse.json", nil);
return [OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200
headers:#{#"Content-Type":#"text/json"}];
}];
And after you execute your test you should call
[OHHTTPStubs removeStub:stub];
"fakeResponse.json" must be a file added only to your test target and you can have different tests with different JSON files. For example you can pass a JSON with an error or empty content to test if your application behaves the way you want it to behave.

AFNetworking 2.0 - unexpected NSURLErrorDomain error -1012

We ran into the following issue with our app that uses AFNetworking 2.0.
When using AFHTTPRequestOperationManager's GET method, we got an error NSURLErrorDomain code -1012. The request used HTTPS and the server does not require user authentication. The request never reached the server by the way.
We have run several tests and this is the first time the error was produced and we are wondering how this error can get produced because it does not seem relevant.
Setup of AFHTTPRequestOperationManager :
httpOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:
[NSURL URLWithString: HTTPS_URL)]];
httpOperationManager.responseSerializer =
[AFXMLParserResponseSerializer serializer];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled: YES];
GET REQUEST
AFHTTPRequestOperation *op =[httpOperationManager GET:
[NSString stringWithFormat:SOME_PATH]
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code to setup NSXMLParser ...
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", [error localizedDescription]);
}];
I think you already solved the problem, but if you are trying to authenticate in a server that doesn't have a valid certificate you have to set YES for property allowInvalidCertificates in your AFHTTPRequestOperationManager object:
[yourManager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"your_username" password:#"your_password"];
[yourManager.securityPolicy setAllowInvalidCertificates:YES];
Also, as #a1phanumeric said, it can be necessary to include this line:
[yourManager.securityPolicy setValidatesDomainName:NO];
Cheers.
NSURLErrorDomain -1012 is NSURLErrorUserCancelledAuthentication. (See the error code list and search for -1012.)
You state, "the server does not require user authentication". But this error would not be called if that were true.
Possible causes:
Your server is erroneously requesting authorization (a server bug)
The URL formed with HTTPS_URL and SOME_PATH is not what you expect, and some other server is requesting authorization
Some intermediary (like a proxy server, or an access point) is requiring authorization.
Some debugging tips:
Set breakpoints inside the AFNetworking implementation to see which URL is being hit
Configure AFHTTPRequestOperationLogger so you can see the actual request body and response in your console log
Make the same request with curl or Advanced Rest Client and observe the server's response
Side note: I think [NSString stringWithFormat:SOME_PATH] is pointless - why not just use SOME_PATH?

AFNetworking JSONRequestOperation

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

RestKit: How to resubmit failed request after re-authenticating?

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
}

Resources