iosobjective-cwebviewshouldstartload

Modify Request of webView shouldStartLoadWithRequest:


Currently I am developing an hybrid app which uses webView shouldStartLoadWithRequest: to provide a token for the login. My function works fine for every normal request I make (a click e.g.)

 - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request 
        NSLog([NSString stringWithFormat:@"Loading View: %@",[[request URL] absoluteString]]);
        if ([[[request URL] absoluteString] rangeOfString:BASE_URL].location != NSNotFound) {
            NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginToken];
            NSString *hash = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginHash];
            NSString *params = [NSString stringWithFormat:@"mobile=app&user_token=%@&user_hash=%@",token,hash];
            if([[request URL] query] == nil) {
                [self LoadUrl:[[request URL] absoluteString] withGetParams:params append:NO];
                return NO;
            }else{
                if([[[request URL] absoluteString] rangeOfString:params].location == NSNotFound){
                    [self LoadUrl:[[request URL] absoluteString] withGetParams:params append:YES];
                    return NO;
                }
            }

    }

-(void)LoadUrl:(NSString *)url withGetParams:(NSString *)params append:(BOOL)append{
    NSString *PreUrl;
    if(append == YES) PreUrl = [NSString stringWithFormat:@"%@&%@",url,params];
    else  PreUrl = [NSString stringWithFormat:@"%@?%@",url,params];
    NSURL *nsurl = [NSURL URLWithString: PreUrl];
    NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
    [self.WebView loadRequest:request];
}

The Problem I have with this Code is that if I load an Image e.g. it will be detected as "to be hashed-appended" (which is correct, I want every request to have the Auth included) BUT the Image will get loaded in the Webview itself.

My first try (before I switched to this model) was to modify the request parsed. But every change got Ignored....

Has anyone an Idea how I could fix this problem? Is there a way to really modify requests? Or if not, can I at least determine the "target" of the request or forward it?

Thanks for any help


Solution

  • I found a Solution for my Problem. Sublcassing was the right approach but not UIWebView but a own NSURLProtocol.

    So what I did:

    Create an own Sublcass of NSURLProtocol

    @interface MyURL : NSURLProtocol <NSURLConnectionDelegate>
    

    Add some standard handling for HTTP-Connections

    @interface MyURL () <NSURLConnectionDelegate>
    
    @property (nonatomic, strong) NSURLConnection *connection;
    @property (nonatomic, strong) NSMutableData *mutableData;
    @property (nonatomic, strong) NSURLResponse *response;
    
    @end
    
    @implementation MyURL
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
    {
        return request;
    }
    - (void)stopLoading
    {
        [self.connection cancel];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        [self.client URLProtocol:self didLoadData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        [self.client URLProtocol:self didFailWithError:error];
        self.connection = nil;
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        [self.client URLProtocolDidFinishLoading:self];
        self.connection = nil;
    }
    @end
    

    And now the interesting part - Modifying every request that goes to my server

    So first: Check if this Request goes to my Server and determine if my protocol should take care of it

    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    {
        NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginToken];
        NSString *hash = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginHash];
        if([NSURLProtocol propertyForKey:@"TokenSet" inRequest:request]) return NO; // We already handled it
        if((hash == nil) || (token == nil) ) return NO; // We are not logged in
        NSString *params = [NSString stringWithFormat:@"mobile=app&user_token=%@&user_hash=%@",token,hash];
        if (([[[request URL] absoluteString] rangeOfString:BASE_URL].location != NSNotFound) && ([[[request URL] absoluteString] rangeOfString:@"/assets/"].location == NSNotFound)){
                if([[[request URL] absoluteString] rangeOfString:params].location == NSNotFound){
                    return YES; // URL does not contain the login token & we're not requesting an asset (js/img/etc.)
                }
    
    
        }
        return NO;
    }
    

    So if + (BOOL)canInitWithRequest:(NSURLRequest *)request returned yes, I have to handle the request. I already know that it does not contain the login token & hash so I've got to determine if it has to be appended or not. To modify the request in general, I create a MutableCopy of our request, Modify it and set our URLConnection to the request.

    - (void)startLoading
    {
        NSMutableURLRequest *newRequest = [self.request mutableCopy];
        NSString *PreURL;
        NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginToken];
        NSString *hash = [[NSUserDefaults standardUserDefaults] stringForKey:kDefaultsKeyLoginHash];
        NSString *params = [NSString stringWithFormat:@"mobile=app&user_token=%@&user_hash=%@",token,hash];
            if([[newRequest URL] query] == nil) {
                PreURL = [NSString stringWithFormat:@"%@?%@",[[newRequest URL] absoluteString],params];
            }else{
                if([[[newRequest URL] absoluteString] rangeOfString:params].location == NSNotFound){
                    PreURL = [NSString stringWithFormat:@"%@&%@",[[newRequest URL] absoluteString],params];
                }
            }
        NSURL *nsurl = [NSURL URLWithString: PreURL];
        [newRequest setURL:nsurl];
        [NSURLProtocol setProperty:@"YES" forKey:@"TokenSet" inRequest:newRequest];
        self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
    
    
    
    }
    

    And to finish it all, we register our URL-Protocol as Protocol in AppDelegate.

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [NSURLProtocol registerClass:[MyURL class]];
    }
    

    With this Solution I first have the advantage of having my login token in ANY request ANY part of my App sends to my Server. No more worries about this. And I can do cool stuff, like saving resources after loading them the first time or even use Images from my App-Bundle in Webviews...

    I hope this helps someone.