How to Mock Ajax Call with Nsurlprotocol

How to mock AJAX call with NSURLProtocol?

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

Loading text file by AJAX call in UIWebView using custom NSURLProtocol fails

I had the same problem and finally solved it after days of hair pulling :

Your problem comes from the way you create the response, you have to create a status 200 response and force the WebView to allow cross-domain request if necessary :

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

You can see my full working implementation in my answer here :

How to mock AJAX call with NSURLProtocol?

Hope this helps,
Vincent

Editing response for synchronous ajax request in UIWebView

I had the exact same need : i wanted to catch AJAX request in objective-c and return some cached response to the javascript handler. I managed to get it working using a NSURLProtocol, you can see the full implementation here :

How to mock AJAX call with NSURLProtocol?

Is there a way to capture all NSURLRequests while loading a html-page?

I had to do something very similar and managed to get it working by implementing a NSURLProtocol and forcing the response to load a local json file.

You can see the full implementation here :

How to mock AJAX call with NSURLProtocol?

NSURLProtocol isn't asked to load after YES response to canInitWithRequest

I was able to workaround this issue by cache-busting the UIWebView cache, while not busting the NSURLCache.

  1. Add a unique param to the query params of the original request. I chose 'key=000000' where the value is zero-led six digit random number.
  2. In the protocol, strip the key in canonicalRequestForRequest: and in initWithRequest:cachedResponse:client

My stripping code looks like this (there might be a cleaner way to strip the param, but this works):

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
NSURLRequest *canonicalRequest = request;
BOOL myProtocolRequest = [[NSURLProtocol propertyForKey:kMYProtocolRequest inRequest:request] boolValue];
if (myProtocolRequest)
{
NSMutableURLRequest *mutableRequest = [request mutableCopyWorkaround];
NSString *originalURLString = mutableRequest.URL.absoluteString;
NSString *regexString = [NSString stringWithFormat:@"(?:[?&])(key=[[:digit:]]{%d}&*)", kMYKeyLength];

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:0];
NSTextCheckingResult *result = [regex firstMatchInString:originalURLString options:0 range:NSMakeRange(0, originalURLString.length)];
if (result.numberOfRanges > 1)
{
NSRange keyRange = [result rangeAtIndex:1];
NSLog(@"Removing '%@' from request", [originalURLString substringWithRange:keyRange]);
NSString *replacementURLString = [originalURLString stringByReplacingCharactersInRange:keyRange withString:@""];
mutableRequest.URL = [NSURL URLWithString:replacementURLString];
canonicalRequest = mutableRequest;
}
}

return canonicalRequest;
}

My init code looks like this:

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
self = [super initWithRequest:[MYURLProtocol canonicalRequestForRequest:request] cachedResponse:cachedResponse client:client];
return self;
}

I don't like that I have to do this, but I'm finally getting exactly the behavior I want. Hopefully it helps someone out there.

How do you change the content-type of a custom NSURLProtocol?

The content-type is actually carried by NSURLResponse, which you can modify by using the NSURLProtocolClient method URLProtocol:didReceiveResponse:cacheStoragePolicy: for instance, to set to text/plain

NSURLResponse *textResponse = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"text/plain" expectedContentLength:100 textEncodingName:@"UTF-8"];
[self.client URLProtocol:self didReceiveResponse:textResponse cacheStoragePolicy:NSURLCacheStorageAllowedInMemoryOnly];


Related Topics



Leave a reply



Submit