How to mock AJAX call with NSURLProtocol?

前端 未结 2 557
独厮守ぢ
独厮守ぢ 2020-12-08 22:43

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

相关标签:
2条回答
  • 2020-12-08 23:15

    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 :

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

    Then in iOS I return NO in the UIWebViewDelegate shouldStartLoad (my condition was a bit ugly) :

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

    In top of that I would have register my protocol :

    [NSURLProtocol registerClass:[MyProtocol class]];
    

    With a StartLoad implementation. You also should have subclass the canInitWithRequest

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

    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 !

    0 讨论(0)
  • 2020-12-08 23:17

    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 :

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

    The final implementation of the protocol :

    #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
    

    Nothing special in the 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 );
          }
      });
    }
    
    0 讨论(0)
提交回复
热议问题