Skip to content

Instantly share code, notes, and snippets.

@gtasko
Last active January 22, 2016 01:22
Show Gist options
  • Save gtasko/8822394 to your computer and use it in GitHub Desktop.
Save gtasko/8822394 to your computer and use it in GitHub Desktop.
NSURLProtocol implementation to intercept HTTP networking and continue with original call.
//
// SLKURLProtocol.m
// InterceptionHttp
//
// Created by G.Tas on 1/14/14.
// Copyright (c) 2014. All rights reserved.
//
#import "SLKURLProtocol.h"
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define WORKAROUND_MUTABLE_COPY_LEAK 1
#if WORKAROUND_MUTABLE_COPY_LEAK
// required to workaround http://openradar.appspot.com/11596316
@interface NSURLRequest(MutableCopyWorkaround)
- (id) mutableCopyWorkaround;
@end
#endif
@interface SLKURLProtocol ()
@property (nonatomic, readwrite, strong) NSURLConnection* connection;
@end
static NSString* SLKInterceptionURLHeader = @"X-SLKIntercepted";
@implementation SLKURLProtocol
+ (NSURLRequest*) canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
+ (BOOL) canInitWithRequest:(NSURLRequest *)request
{
// only handle http requests we haven't marked with our header.
if ([[[request URL] scheme] isEqualToString:@"http"] &&
([request valueForHTTPHeaderField:SLKInterceptionURLHeader] == nil))
{
return YES;
}
return NO;
}
- (void) startLoading
{
// dispatch_async(kBgQueue,
// ^{
NSString* dataString = [NSString stringWithUTF8String:[self.request.HTTPBody bytes]];
NSUInteger length = [self.request.HTTPBody length];
NSData* data = [NSData dataWithBytes:[self.request.HTTPBody bytes] length:length];
NSLog(@"Intercept Request startLoading");
NSURLRequest* request = self.request;
NSLog(@"URL: %@", request.URL);
NSLog(@"HTTP Method: %@", request.HTTPMethod);
NSLog(@"Data: %@", dataString);
NSMutableURLRequest *connectionRequest =
#if WORKAROUND_MUTABLE_COPY_LEAK
[[self request] mutableCopyWorkaround];
#else
[[self request] mutableCopy];
#endif
[connectionRequest setValue:@"" forHTTPHeaderField:SLKInterceptionURLHeader];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
delegate:self];
[self setConnection:connection];
[[self client] URLProtocol:self didReceiveResponse:[[NSURLResponse alloc] init] cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
// });
}
- (void) stopLoading
{
// dispatch_async(kBgQueue,
// ^{
[self.connection cancel];
NSLog(@"Intercept Request stopLoading");
// });
}
@end
#if WORKAROUND_MUTABLE_COPY_LEAK
@implementation NSURLRequest(MutableCopyWorkaround)
- (id) mutableCopyWorkaround {
NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:[self URL]
cachePolicy:[self cachePolicy]
timeoutInterval:[self timeoutInterval]];
[mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]];
return mutableURLRequest;
}
@end
#endif
@gtasko
Copy link
Author

gtasko commented Feb 5, 2014

Results:

2014-02-05 14:12:59.764 InterceptionHttp[685:3a03] Intercept Request startLoading
2014-02-05 14:12:59.766 InterceptionHttp[685:3a03] URL: http://requestb.in/tsvc14ts
2014-02-05 14:12:59.766 InterceptionHttp[685:3a03] HTTP Method: POST
2014-02-05 14:12:59.767 InterceptionHttp[685:3a03] Data: { 'message': 'JSON_MESSAGE' }
2014-02-05 14:12:59.769 InterceptionHttp[685:3a03] Intercept Request stopLoading
2014-02-05 14:12:59.769 InterceptionHttp[685:70b] AFNetworking Response: { 'message': 'JSON_MESSAGE' }

If you notice the AFNetworking response of the original caller (AFNetworking) is the body content data!!! Can't explain this behaviour.

@yaominator
Copy link

I am trying to understand what you mean about "intercept" here. As far as I understand, you are trying to be the middle man between the app and low level network stack and make some changes to the request or response. In this case, you need to do following

  1. In canInitWithRequest to claim you want to process the request
  2. In startLoading to tag the request so you won't be in the infinite loop
  3. In startLoading to start a connection and set self as delegate which will do the actual connection for you
  4. In the code to implement all the delegate functions including connection:didReceiveResponse: , connection:didReceiveData: and so on.
  5. In those delegate methods, you call NSURLProtocolClient to pass the data back to the app ( basically the app has delegate methods that are waiting to be called ).
  6. In stopLoading to close the connection if it's done

I would suggest you to modify the code of RNCachingURLProtocol and test it out. That would save your time.

@gtasko
Copy link
Author

gtasko commented Feb 6, 2014

With "intercept" I mean, examine the request and let the original caller continue with the operation, so, I want to get info about the request using NSURLProtocol and then AFNetworking continue normally the operation.

Will try with copying and pasting the whole RNCachingURLProtocol. I don't actually need caching, I will keep the info to a file but I want the request of the original caller to continue.

@yaominator
Copy link

In theory , you can just modify RNCachingURLProtocol for your purpose. You can just remove the logic for caching. I am working on a very similar project , but I am not allowed to open source it without permission. I can tell you that the modification of RNCachingURLProtocol works just fine for me in GET/PUT/POST and so on. It also works for AFNetworking and NSURLConnection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment