2013-03-05 11 views
14

Mam UIWebview, który sprawia, że ​​połączenia AJAX do usług zewnętrznych. Kiedy jestem w trybie offline, muszę złapać te żądania i zwrócić lokalny json.Jak wyśmiać wywołanie AJAX za pomocą NSURLProtocol?

I wdrożone NSURLProtocol i udaje mi się złapać żądania AJAX, jQuery problem jest zawsze zwraca kod błędu 0:

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

});

zawsze uzyskać request.status = 0

Aby przetestować protokół Próbowałem drwić obraz w moim html i działa świetnie.

  • żądania HTML do obrazu z google.fr =>działa dobrze
  • AJAX wezwanie do json na Amazon =>nie

Oto moja pełna realizacja:

#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 

Odpowiedz

19

Problem pochodzi z zestawu Webkit, który blokuje odpowiedź z powodu żądania pochodzenia w różnych domenach. Ponieważ szydzimy z reakcji, musimy wymusić Access-Control-Allow-Origin.

Następnie musimy również wymusić typ zawartości odpowiedzi.

Oto gdzie magia dzieje:

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

Ostateczna realizacja protokołu:

#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 

nic specjalnego w 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

Dobra robota. Bardzo pomocna odpowiedź! –

+0

To uratowało mój dzień. BTW dla osób zastanawiających się, skoro nie można wywołać obu inicjalizatorów (mimeType jeden, a headers/statusCode jeden) można ustawić mimeType, kodowanie, długość itp. Przy użyciu standardowych nagłówków HTTP: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields. Po prostu dodaj do słownika nagłówka –

2

Musiałem zrobić podobny materiał jakiś czas temu.

pierwsze udało mi się znaleźć kod, który może sprawić, że UIWebViewDelegate złapać ajax wywołanie, więc w mojej części webapp miałem:

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

Następnie w iOS wrócę NO w UIWebViewDelegate shouldStartLoad (mój stan był nieco brzydki):

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

    return YES; 
} 

W górze, że będę musiał zarejestrować protokół:

[NSURLProtocol registerClass:[MyProtocol class]]; 

Z implementacją StartLoad. też powinien mieć podklasy canInitWithRequest

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

    return NO; 
} 

Aby powiedzieć protokół, który powinien być użyty do wniosku.

Nie zapomnij wyrejestrować się, gdy masz sieć.

Jeśli nie chcą zajmować się NSURLProtocol implementacja można użyć własnego: https://github.com/bcharp/BOURLProtocol;)

Nadzieję, że to pomaga!

+0

wciąż otrzymuję 0 statusu ale myślę, że jestem blisko. Edytowałem swoją odpowiedź. – vdaubry

Powiązane problemy