2013-03-05 10 views
14

Ho UIWebview che effettua chiamate AJAX a servizi esterni. Quando non in linea ho bisogno di prendere queste richieste e restituire il json locale.Come prendere in giro la chiamata AJAX con NSURLProtocol?

ho implementato un NSURLProtocol e riesco a prendere la richiesta AJAX, il problema è jquery restituisce sempre un codice 0 errore:

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

});

ottengo sempre un request.status = 0

Per testare il mio protocollo ho cercato di deridere l'immagine dentro il mio html e funziona benissimo.

  • richiesta HTML per un'immagine da google.fr =>funziona bene
  • chiamata AJAX a un JSON su amazon =>fallisce

Ecco la mia piena attuazione:

#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 

risposta

19

Il problema deriva dal webkit che blocca la risposta a causa della richiesta di origine tra domini. Dal momento che prendiamo in giro la risposta, dobbiamo forzare Access-Control-Allow-Origin.

Quindi dobbiamo anche forzare il tipo di contenuto della risposta.

Qui è dove avviene la magia:

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

La realizzazione finale del protocollo:

#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 

Niente di speciale nella 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

Bel lavoro. Risposta molto utile! –

+0

Questo mi ha salvato la giornata. A proposito di persone che si chiedono, dal momento che non è possibile chiamare entrambi gli inizializzatori (mimeType uno, vs header/statusCode uno) è possibile impostare mimeType, encoding, length, ecc. Usando le intestazioni HTTP standard: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields. Basta aggiungere al tuo dizionario di intestazione –

2

Ho dovuto fare una cosa simile qualche tempo fa.

prima sono riuscito a trovare il codice che può fare l'UIWebViewDelegate prendere la chiamata ajax, così nella mia parte webapp ho avuto:

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

Poi in iOS ho ritorno NO nel UIWebViewDelegate shouldStartLoad (la mia condizione era un po 'brutto):

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

    return YES; 
} 

In cima a che avrei dovuto registrare il mio protocollo:

[NSURLProtocol registerClass:[MyProtocol class]]; 

Con un'implementazione StartLoad. È inoltre dovrebbe avere la sottoclasse canInitWithRequest

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

    return NO; 
} 

A dire il protocollo che dovrebbe essere utilizzato per la richiesta.

E non dimenticare di annullare la registrazione quando si dispone di rete.

Se non si vuole perdere tempo con NSURLProtocol implementazione è possibile usare la mia: https://github.com/bcharp/BOURLProtocol;)

Speranza che aiuta!

+0

ho ancora ottenere uno stato di 0 ma penso di essere vicino Ho modificato la mia risposta. – vdaubry

Problemi correlati