1

I want to log all the NSURLRequests which are initiated from within my app and response for those requests. I wrote a custom NSURLProtocol to achieve this. I was able to trap all the request but not the response.

In canInitWithRequest method I can log all the requests regardless of method returns YES/NO. Right now I am returning YES in hope the of actual response.

In -startLoading method I am supposed to inform NSURLProtocolClient with response and progress. I am not interested in modifying/creating my own response instead I am interested in actual response and want to log it. When and where would I find actual response for the request?

I am not interested in modifying URL loading behavior.

Am I on the right track with custom protocol or is there something else I need to do to log all the requests and responses?

@implementation TestURLProtocol

+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
//here i can log all the requests    
NSLog(@"TestURLProtocol : canInitWithRequest");
    return YES;
}

+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}


-(void)startLoading
{
// I don't want to create or modify response. 
//I have nothing todo with response instead I need actual response for logging purpose.
    NSURLResponse * response = [[NSURLResponse alloc] initWithURL:[self.request URL] MIMEType:@"text/html" expectedContentLength:-1 textEncodingName:@"utf8"];
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocolDidFinishLoading:self];
}

-(void)stopLoading
{

}
@end
dev gr
  • 2,409
  • 1
  • 21
  • 33

2 Answers2

0

Well, I dropped the idea of custom NSURLProtocol. I wrote a class which confirms to NSURLConnectionDelegate, NSURLConnectionDataDelegate etc as per your need. This class serves as a common delegate to all NSURLConnection instances. It has a property of type id(depend upon requirements), this property holds the object which is creating NSURLConnection and interested in delegate callbacks as well.

URLRequestLogger instance serves as a common delegate to all NSURLConnection instances. Each NSURLConnection has a URLRequestLogger instance as a delegate.

#import <Foundation/Foundation.h>


@interface URLRequestLogger : NSObject<NSURLConnectionDataDelegate>

-(id)initWithConnectionDelegate:(id<NSURLConnectionDataDelegate>)connectionDelegate;

@end

implementation file

#import "URLRequestLogger.h"

@interface URLRequestLogger ()

@property(nonatomic, assign) id<NSURLConnectionDataDelegate> connectionDelegate;

@end

@implementation URLRequestLogger

-(id)initWithConnectionDelegate:(id<NSURLConnectionDataDelegate>)connectionDelegate
{
    self = [super init];
    if (self)
    {
        _connectionDelegate = connectionDelegate;
    }

    return self;
}

//one can implement all the delegates related to NSURLConnection as per need

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //log reponse info
    if ([response isKindOfClass:[NSHTTPURLResponse class]])
    {
        NSHTTPURLResponse * httpResponse  = (NSHTTPURLResponse *)response;
        NSDictionary * responseHeaders = [httpResponse allHeaderFields];
        NSInteger statusCode = [httpResponse statusCode];
        //save as many info as we can
    }

    //if connectionDelegate is interested in it, inform it
    if ([self.connectionDelegate respondsToSelector:@selector(connection:didReceiveResponse:)])
    {
        [self.connectionDelegate connection:connection didReceiveResponse:response];
    }
}


-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

   //log the error and other info

    //if connectionDelegate is interested in it, inform it
    if ([self.connectionDelegate respondsToSelector:@selector(connection:didFailWithError:)])
    {
        [self.connectionDelegate connection:connection didFailWithError:error];
    }
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //log the connection finish info
    //response time and download time etc

    //if connectionDelegate is interested in it, inform it
    if ([self.connectionDelegate respondsToSelector:@selector(connectionDidFinishLoading:)])
    {
        [self.connectionDelegate connectionDidFinishLoading:connection];
    }
}
@end

MyViewController creates an NSURLConnection and is interested in delegate methods as well. At the same time we want to log all the details about request and response.

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController<NSURLConnectionDataDelegate>

@end

implementation file

#import "MyViewController.h"
#import "URLRequestLogger.h"

@implementation MyViewController

//MyViewController class creates a request and interested in connection delegate callbacks
//MyViewController confirms to NSURLConnectionDelegate.
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self sendRequest];
}

-(void)sendRequest
{
    /*
    connection delegate here would be our URLRequestLogger class instance which holds MyViewController instance
    to return the callbacks here after logging operations are finished
     */
    URLRequestLogger * requestLogger = [[URLRequestLogger alloc] initWithConnectionDelegate:self];

    [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.org"]] delegate:requestLogger];
}

#pragma mark - NSURLConnection delegate methods

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //this callback is received from URLRequestLogger after logging operation is completed

    //do something. Update UI etc...
}

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    //this callback is received from URLRequestLogger after logging operation is completed

    //do something. Update UI etc...
}

@end
dev gr
  • 2,409
  • 1
  • 21
  • 33
0

Better solution with custom NSURLProtocol: One can log all outgoing URL request and alter request properties, response and do other things like loading encrypted html files from disk into webview etc.

.h file

#import <Foundation/Foundation.h>

@interface URLRequestLoggerProtocol : NSURLProtocol

@end

.m file

#import "URLRequestLoggerProtocol.h"

@interface URLRequestLoggerProtocol ()<NSURLConnectionDelegate,NSURLConnectionDataDelegate>

@property(nonatomic, strong) NSURLConnection * connection;

@end


@implementation URLRequestLoggerProtocol
@synthesize connection;

+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
     //logging only HTTP and HTTPS requests which are not being logged
    NSString * urlScheme = request.URL.scheme;
    if (([urlScheme isEqualToString:@"http"] || [urlScheme isEqualToString:@"https"]) && ![[NSURLProtocol propertyForKey:@"isBeingLogged" inRequest:request] boolValue])
    {
        return YES;
    }

    return NO;

}

+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}


-(void)startLoading
{
    NSMutableURLRequest * newRequest = [self.request mutableCopy];
    [NSURLProtocol setProperty:[NSNumber numberWithBool:YES] forKey:@"isBeingLogged" inRequest:newRequest];
    self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];
}

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


#pragma mark - NSURLConnectionDelegate methods

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

    //log all things along with error and finally inform URL client that you are done

    [self.client URLProtocol:self didFailWithError:error];
}

#pragma mark - NSURLConnectionDataDelegate methods

-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
    //start logging a request properties from here

    return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{


    //log the response and other things and inform client that you are done.

    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];

}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{

    //log the data and other things and inform client that you are done.

    [self.client URLProtocol:self didLoadData:data];
}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{

    //log the finish info and other things and inform client that you are done.

    [self.client URLProtocolDidFinishLoading:self];
}


@end
dev gr
  • 2,409
  • 1
  • 21
  • 33