Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIWebView capturing the response headers

I searched/googled a lot but could not get the answer on how to capture HTTP response headers in UIWebview. Say I redirect to user to registration gateway(which is already active) in UIWebview on App Launch and when user finishes the registration, the app should be notified with the successful unique id assigned to the user on registration which is passed back in HTTP Response Headers.

Is there any direct way to capture/print the HTTP Response headers using UIWebview?

like image 687
ACP Avatar asked Mar 18 '13 12:03

ACP


3 Answers

There is no way to get the response object from the UIWebView (file a bug with apple for that, id say)

BUT two workarounds

1) via the shared NSURLCache

- (void)viewDidAppear:(BOOL)animated {
    NSURL *u = [NSURL URLWithString:@"http://www.google.de"];
    NSURLRequest *r = [NSURLRequest requestWithURL:u];
    [self.webView loadRequest:r];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSCachedURLResponse *resp = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
    NSLog(@"%@",[(NSHTTPURLResponse*)resp.response allHeaderFields]);
}
@end

if this works for you this is ideal


ELSE

  1. you could use NSURLConnection altogether and then just use the NSData you downloaded to feed the UIWebView :)

that'd be a bad workaround for this! (as Richard pointed out in the comments.) It DOES have major drawbacks and you have to see if it is a valid solution in your case

NSURL *u = [NSURL URLWithString:@"http://www.google.de"];
NSURLRequest *r = [NSURLRequest requestWithURL:u];
[NSURLConnection sendAsynchronousRequest:r queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *resp, NSData *d, NSError *e) {
    [self.webView loadData:d MIMEType:nil textEncodingName:nil baseURL:u];
    NSLog(@"%@", [(NSHTTPURLResponse*)resp allHeaderFields]);
}];
like image 127
Daij-Djan Avatar answered Oct 26 '22 00:10

Daij-Djan


I love the objective-c runtime. Is there something that you want to do but don't have an API for it? DM;HR.

Alright, on a more serious note, here's the solution to this. It will capture every URL Response that initiated from CFNetwork, which is what UIWebView happens to use behind the scenes. It will also capture AJAX requests, image loads, and more.

Adding a filter to this should probably be as easy as doing a regex on the contents of the headers.

@implementation NSURLResponse(webViewHack)

static IMP originalImp;

static char *rot13decode(const char *input)
{
    static char output[100];

    char *result = output;

    // rot13 decode the string
    while (*input) {
        if (isalpha(*input))
        {
            int inputCase = isupper(*input) ? 'A' : 'a';

            *result = (((*input - inputCase) + 13) % 26) + inputCase;
        }
        else {
            *result = *input;
        }

        input++;
        result++;
    }

    *result = '\0';
    return output;
}

+(void) load {
    SEL oldSel = sel_getUid(rot13decode("_vavgJvguPSHEYErfcbafr:"));

    Method old = class_getInstanceMethod(self, oldSel);
    Method new = class_getInstanceMethod(self, @selector(__initWithCFURLResponse:));

    originalImp = method_getImplementation(old);
    method_exchangeImplementations(old, new);
}

-(id) __initWithCFURLResponse:(void *) cf {
    if ((self = originalImp(self, _cmd, cf))) {
        printf("-[%s %s]: %s", class_getName([self class]), sel_getName(_cmd), [[[self URL] description] UTF8String]);

        if ([self isKindOfClass:[NSHTTPURLResponse class]])
        {
            printf(" - %s", [[[(NSHTTPURLResponse *) self allHeaderFields] description] UTF8String]);
        }

        printf("\n");
    }

    return self;
}

@end
like image 37
Richard J. Ross III Avatar answered Oct 25 '22 23:10

Richard J. Ross III


If you want a more high level API code of what @Richard J. Ross III's wrote, you need to subclass NSURLProtocol.

An NSURLProtocol is an object which handles URL Requests. So you can use it for specific tasks which are described better on NSHipster and Ray Wenderlich, which includes your case of getting the HTTP Headers from the Response.

Code

Create a new class subclassing from NSURLProtocol and your .h file should look like this:

@interface CustomURLProtocol : NSURLProtocol <NSURLConnectionDelegate>

@property (nonatomic, strong) NSURLConnection *connection;

@end

Your .m file should have these methods to handle what you wish for

@implementation CustomURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    // Here you can add custom filters to init or not specific requests
    return YES;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    // Here you can modify your request
    return request;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
    return [super requestIsCacheEquivalent:a toRequest:b];
}

- (void)startLoading {
    // Start request
    self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
}

- (void) stopLoading {
    [self.connection cancel];
    self.connection = nil;
}

#pragma mark - Delegation

#pragma mark NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        
        // Here we go with the headers
        NSDictionary *allHeaderFields = [httpResponse allHeaderFields];
    }
    
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}

Also last thing to do is to register this protocol to the loading system which is easy achievable on AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[CustomURLProtocol class]];
    return YES;
}
like image 1
E-Riddie Avatar answered Oct 25 '22 23:10

E-Riddie