Comment se moquer de l'appel AJAX avec NSURLProtocol?

J'ai UIWebview qui fait des appels AJAX aux services externes. Lorsque je suis déconnecté, j'ai besoin d'attraper ces requêtes et de renvoyer le file json local.

J'ai implémenté un NSURLProtocol et j'ai réussi à attraper la requête AJAX, le problème est que jquery returnne toujours un code d'erreur 0:

$.ajax({ url: url, dataType: 'json', contentType: "application/json", success: function(jsonData){ alert("success :"); }, error: function (request, status, error) { alert("failure :" + request.status ); } 

});

Je reçois toujours un request.status = 0

Pour tester mon protocole, j'ai essayé de me moquer d'une image dans mon code HTML et ça marche très bien.

  • Demande HTML à une image de google.fr => fonctionne bien
  • AJAX appel à un json sur amazon => échoue

Voici ma mise en œuvre complète:

 #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"]) { NSSsortingng *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 URLWithSsortingng:@"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:(NSSsortingng*)domain { NSSsortingng *str = [[request URL] absoluteSsortingng]; NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", domain]; return [pred evaluateWithObject:str]; } #pragma mark - Mock responses -(void) mockRequest:(NSURLRequest*)request mimeType:(NSSsortingng*)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 

Le problème vient de webkit qui bloque la réponse en raison de la request d'origine interdomaine. Puisque nous nous moquons de la réponse, nous devons forcer l'access-contrôle-autorisation-origine.

Ensuite, nous devons également forcer le type de contenu de la réponse.

Voici où la magie se produit:

 NSDictionary *headers = @{@"Access-Control-Allow-Origin" : @"*", @"Access-Control-Allow-Headers" : @"Content-Type"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:headers]; 

L'implémentation finale du protocole:

 #import "EpubProtocol.h" @implementation EpubProtocol #pragma mark - NSURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { BOOL isAwsRequest = [self request:request contains:@"s3.amazonaws.com"]; return isAwsRequest; } + (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest { return theRequest; } - (void)startLoading { NSURLRequest *request = [self request]; //Mock Amazon call if([EpubProtocol request:request contains:@"s3.amazonaws.com"]) { NSSsortingng *path = [[NSBundle bundleForClass:self.class] pathForResource:@"epub1" ofType:@"json"]; NSData *data = [NSData dataWithContentsOfFile:path]; [self mockRequest:request data:data]; } } - (void)stopLoading { NSLog(@"Did stop loading"); } #pragma mark - Request utils + (BOOL) request:(NSURLRequest*)request contains:(NSSsortingng*)domain { NSSsortingng *str = [[request URL] absoluteSsortingng]; NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", domain]; return [pred evaluateWithObject:str]; } #pragma mark - Mock responses -(void) mockRequest:(NSURLRequest*)request data:(NSData*)data { id client = [self client]; NSDictionary *headers = @{@"Access-Control-Allow-Origin" : @"*", @"Access-Control-Allow-Headers" : @"Content-Type"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:headers]; [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [client URLProtocol:self didLoadData:data]; [client URLProtocolDidFinishLoading:self]; } @end 

Rien de spécial dans le JS:

 function loadJSONDoc() { var url = "https://s3.amazonaws.com/youboox_recette/epub.json"; $.ajax({ url: url, dataType: 'json', contentType: "application/json", success: function(jsonData){ alert('success'); document.getElementById("myDiv").innerHTML='<p>'+$.param(jsonData)+'</p>'; }, error: function (request, status, error) { alert("failure :" + request.status ); } }); } 

J'ai dû faire un truc similaire il y a quelque time.

D'abord j'ai réussi à find le code qui peut faire que l'UIWebViewDelegate attrape l'appel ajax, donc dans ma partie webapp j'avais:

 //code to extend XMLHttpRequest, and have ajax call available in uiwebviewdelegate. var s_ajaxListener = new Object(); s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; s_ajaxListener.callback = function () { window.location=this.url; }; XMLHttpRequest.prototype.open = function(a,b) { if (!a) var a=''; if (!b) var b=''; s_ajaxListener.tempOpen.apply(this, arguments); s_ajaxListener.method = a; s_ajaxListener.url = b; if (a.toLowerCase() == 'get') { s_ajaxListener.data = b.split('?'); s_ajaxListener.data = s_ajaxListener.data[1]; } } XMLHttpRequest.prototype.send = function(a,b) { if (!a) var a=''; if (!b) var b=''; s_ajaxListener.tempSend.apply(this, arguments); if(s_ajaxListener.method.toLowerCase() == 'post')s_ajaxListener.data = a; s_ajaxListener.callback(); } 

Ensuite, dans iOS, je renvoie NO dans le UIWebViewDelegate shouldStartLoad (mon état était un peu moche):

 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if ([[[request URL] scheme] rangeOfSsortingng:@"https"].length > 0) { return NO; } return YES; } 

En plus de cela, je devrais save mon protocole:

 [NSURLProtocol registerClass:[MyProtocol class]]; 

Avec une implémentation StartLoad. Vous devriez également avoir la sous-class canInitWithRequest

 + (BOOL)canInitWithRequest:(NSURLRequest *)request { if ([request.URL.scheme rangeOfSsortingng:@"https"].length > 0) { return YES; } return NO; } 

Pour indiquer au protocole qu'il doit être utilisé pour la request.

Et n'oubliez pas de vous désinscrire lorsque vous avez un réseau.

Si vous ne voulez pas vous ennuyer avec l'implémentation de NSURLProtocol, vous pouvez utiliser le mien: https://github.com/bcharp/BOURLProtocol 😉

J'espère que cela aide !