I have UIWebview that makes AJAX calls to external services. When offline i need to catch theses requests and return local json.
I implemented a NSURLProtocol and i manage to catch the AJAX request, the problem is jquery always return a 0 error code :
$.ajax({
url: url,
dataType: 'json',
contentType: "application/json",
success: function(jsonData){
alert("success :");
},
error: function (request, status, error) {
alert("failure :" + request.status );
}
});
I always get a request.status = 0
To test my protocol I tried to mock an image inside my html and it works great.
- HTML request to an image from google.fr => works fine
- AJAX call to a json on amazon => fails
Here is my full implementation :
#import "EpubProtocol.h"
@implementation EpubProtocol
#pragma mark - NSURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
BOOL awsRequest = [self request:request contains:@"s3.amazonaws.com"];
BOOL imgRequest = [self request:request contains:@"google.fr"];
BOOL match = awsRequest || imgRequest;
return match;
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest
{
return theRequest;
}
- (void)startLoading {
NSURLRequest *request = [self request];
//Mock Amazon call
if([EpubProtocol request:request contains:@"s3.amazonaws.com"]) {
NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"epub1" ofType:@"json"];
NSData *data = [NSData dataWithContentsOfFile:path];
[self mockRequest:request mimeType:@"application/json" data:data];
}
//Mock image call
else if([EpubProtocol request:request contains:@"google.fr"]) {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.itespresso.fr/wp-content/gallery/yahoo/1-yahoo-logo.jpg"]] queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self mockRequest:request mimeType:@"image/jpeg" data:data];
}];
}
}
- (void)stopLoading
{
NSLog(@"Did stop loading");
}
#pragma mark - Request utils
+ (BOOL) request:(NSURLRequest*)request contains:(NSString*)domain {
NSString *str = [[request URL] absoluteString];
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", domain];
return [pred evaluateWithObject:str];
}
#pragma mark - Mock responses
-(void) mockRequest:(NSURLRequest*)request mimeType:(NSString*)mimeType data:(NSData*)data {
id client = [self client];
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[request URL] MIMEType:mimeType expectedContentLength:-1 textEncodingName:nil];
[client URLProtocol:self didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[client URLProtocol:self didLoadData:data];
[client URLProtocolDidFinishLoading:self];
}
@end
The problem comes from webkit which blocks the response because of cross domain origin request. Since we mock the response we have to force the Access-Control-Allow-Origin.
Then we also need to force the content-type of the response.
Here is where the magic happens :
The final implementation of the protocol :
Nothing special in the JS :
I had to do a similar stuff some time ago.
First I managed to find code that can make the UIWebViewDelegate catch the ajax call, so in my webapp part I had :
Then in iOS I return NO in the UIWebViewDelegate shouldStartLoad (my condition was a bit ugly) :
In top of that I would have register my protocol :
With a StartLoad implementation. You also should have subclass the canInitWithRequest
To tell the protocol that it should be use for the request.
And don't forget to unregister when you have network.
If you don't want to bother with NSURLProtocol implémentation you can use my own : https://github.com/bcharp/BOURLProtocol ;)
Hope it helps !