【问题标题】:How to mock AJAX call with NSURLProtocol?如何使用 NSURLProtocol 模拟 AJAX 调用?
【发布时间】:2013-03-05 12:40:43
【问题描述】:

我有 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

为了测试我的协议,我尝试在我的 html 中模拟一张图片,效果很好。

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

这是我的完整实现:​​

#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

【问题讨论】:

    标签: ios ajax uiwebview nsurlprotocol


    【解决方案1】:

    问题来自 webkit,它因为跨域源请求而阻塞了响应。由于我们模拟了响应,我们必须强制使用 Access-Control-Allow-Origin。

    然后我们还需要强制响应的内容类型。

    这就是神奇发生的地方:

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

    【讨论】:

    • 这拯救了我的一天。顺便说一句,对于想知道的人来说,由于您不能同时调用两个初始化程序(mimeType 一个,而不是 headers/statusCode 一个),您可以使用标准 HTTP 标头设置 mimeType、编码、长度等:en.wikipedia.org/wiki/List_of_HTTP_header_fields。只需添加到您的标题字典
    【解决方案2】:

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

    首先我设法找到可以使 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 中,我在 UIWebViewDelegate shouldStartLoad 中返回 NO(我的情况有点难看):

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

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

    当你有网络时不要忘记取消注册。

    如果您不想打扰 NSURLProtocol 实现,可以使用我自己的:https://github.com/bcharp/BOURLProtocol ;)

    希望对你有帮助!

    【讨论】:

    • 我的状态仍然为 0,但我认为我已经接近了。我编辑了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 2023-03-31
    • 2016-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    相关资源
    最近更新 更多