blob: e51df6e5a5dc395298358eab9a2318745a53bb1a [file] [log] [blame]
//
// WXAFNetworkDomainController.m
// PonyDebugger
//
// Created by Mike Lewis on 2/27/12.
//
// Licensed to Square, Inc. under one or more contributor license agreements.
// See the LICENSE file distributed with this work for the terms under
// which Square, Inc. licenses this file to you.
//
#import "WXNetworkDomainController.h"
#import "WXPrettyStringPrinter.h"
#import "NSDate+WXDebugger.h"
#import "NSData+WXDebugger.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import <dispatch/queue.h>
#import "WXNetworkRecorder.h"
#import "WXTracingUtility.h"
#import "WXDebugger.h"
static NSString *seed = nil;
static NSInteger sequenceNumber = 0;
// For reference from the private class dump
//@interface __NSCFURLSessionConnection : NSObject
//
//- (void)_redirectRequest:(id)arg1 redirectResponse:(id)arg2 completion:(void (^)(id arg))arg3;
//- (void)_conditionalRequirementsChanged:(BOOL)arg1;
//- (void)_connectionIsWaiting;
//- (void)_willSendRequestForEstablishedConnection:(id)arg1 completion:(void (^)(NSURLRequest *arg3))arg2;
//- (void)_didReceiveConnectionCacheKey:(struct HTTPConnectionCacheKey *)arg1;
//- (void)_didFinishWithError:(id)arg1;
//- (void)_didSendBodyData:(struct UploadProgressInfo)arg1;
//- (void)_didReceiveData:(id)arg1;
//- (void)_didReceiveResponse:(id)arg1 sniff:(BOOL)arg2;
//
//@end
@interface __NSCFURLSessionConnection_Swizzles : NSObject
@property(copy) NSURLSessionTask *task; // @synthesize task=_task;
@end
@implementation __NSCFURLSessionConnection_Swizzles
@dynamic task;
- (void)WX__redirectRequest:(NSURLRequest *)arg1 redirectResponse:(NSURLResponse *)arg2 completion:(id)arg3;
{
[[WXNetworkDomainController defaultInstance] URLSession:[self.task valueForKey:@"session"] task:self.task willPerformHTTPRedirection:(id)arg2 newRequest:arg1];
[self WX__redirectRequest:arg1 redirectResponse:arg2 completion:arg3];
}
- (void)WX__didReceiveData:(id)arg1;
{
[[WXNetworkDomainController defaultInstance] URLSession:[self.task valueForKey:@"session"] dataTask:(id)self.task didReceiveData:arg1];
[self WX__didReceiveData:arg1];
}
- (void)WX__didReceiveResponse:(NSURLResponse *)response sniff:(BOOL)sniff;
{
// This can be called multiple times for the same request. Make sure it doesn't
[[WXNetworkDomainController defaultInstance] URLSession:[self.task valueForKey:@"session"] dataTask:(id)self.task didReceiveResponse:response];
[self WX__didReceiveResponse:response sniff:sniff];
}
- (void)WX__didFinishWithError:(NSError *)arg1;
{
[[WXNetworkDomainController defaultInstance] URLSession:[self.task valueForKey:@"session"] task:self.task didCompleteWithError:arg1];
[self WX__didFinishWithError:arg1];
}
@end
@interface NSURLSessionTask (PrivateStuff)
- (NSTimeInterval)startTime;
@end
@interface _WXRequestState : NSObject
@property (nonatomic, copy) NSURLRequest *request;
@property (nonatomic, copy) NSURLResponse *response;
@property (nonatomic, strong) NSMutableData *dataAccumulator;
@property (nonatomic, copy) NSString *requestID;
@end
@interface WXNetworkDomainController ()
- (void)setResponse:(NSData *)responseBody forRequestID:(NSString *)requestID response:(NSURLResponse *)response request:(NSURLRequest *)request;
- (void)performBlock:(dispatch_block_t)block;
@end
@implementation WXNetworkDomainController {
NSCache *_responseCache;
NSMutableDictionary *_connectionStates;
dispatch_queue_t _queue;
}
@dynamic domain;
#pragma mark - Statics
+ (WXNetworkDomainController *)defaultInstance;
{
static WXNetworkDomainController *defaultInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultInstance = [[WXNetworkDomainController alloc] init];
});
return defaultInstance;
}
+ (NSString *)nextRequestID;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFUUIDRef uuid = CFUUIDCreate(CFAllocatorGetDefault());
seed = (__bridge NSString *)CFUUIDCreateString(CFAllocatorGetDefault(), uuid);
CFRelease(uuid);
});
return [[NSString alloc] initWithFormat:@"%@-%ld", seed, (long)(++sequenceNumber)];
}
+ (NSString *)currentRequestID;
{
return [[NSString alloc] initWithFormat:@"%@-%ld", seed, (long)(sequenceNumber)];
}
+ (Class)domainClass;
{
return [WXNetworkDomain class];
}
#pragma mark Pretty String Printing registration and usage
// This is replaced atomically to avoid having to lock when looking up the printers instead of being mutable.
static NSArray *prettyStringPrinters = nil;
+ (NSArray*)_currentPrettyStringPrinters;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Always register the default to differentiate text vs binary data
id<WXPrettyStringPrinting> textPrettyStringPrinter = [[WXTextPrettyStringPrinter alloc] init];
prettyStringPrinters = [[NSArray alloc] initWithObjects:textPrettyStringPrinter, nil];
});
return prettyStringPrinters;
}
+ (void)registerPrettyStringPrinter:(id<WXPrettyStringPrinting>)prettyStringPrinter;
{
@synchronized(prettyStringPrinters) {
NSMutableArray *newPrinters = [[WXNetworkDomainController _currentPrettyStringPrinters] mutableCopy];
[newPrinters addObject:prettyStringPrinter];
prettyStringPrinters = newPrinters;
}
}
+ (void)unregisterPrettyStringPrinter:(id<WXPrettyStringPrinting>)prettyStringPrinter;
{
@synchronized(prettyStringPrinters) {
NSMutableArray *newPrinters = [[WXNetworkDomainController _currentPrettyStringPrinters] mutableCopy];
[newPrinters removeObjectIdenticalTo:prettyStringPrinter];
prettyStringPrinters = newPrinters;
}
}
+ (id<WXPrettyStringPrinting>)prettyStringPrinterForRequest:(NSURLRequest *)request;
{
for(id<WXPrettyStringPrinting> prettyStringPrinter in [[WXNetworkDomainController _currentPrettyStringPrinters] reverseObjectEnumerator]) {
if ([prettyStringPrinter canPrettyStringPrintRequest:request]) {
return prettyStringPrinter;
}
}
return nil;
}
+ (id<WXPrettyStringPrinting>)prettyStringPrinterForResponse:(NSURLResponse *)response withRequest:(NSURLRequest *)request;
{
for(id<WXPrettyStringPrinting> prettyStringPrinter in [[WXNetworkDomainController _currentPrettyStringPrinters] reverseObjectEnumerator]) {
if ([prettyStringPrinter canPrettyStringPrintResponse:response withRequest:request]) {
return prettyStringPrinter;
}
}
return nil;
}
#pragma mark Delegate Injection Convenience Methods
+ (SEL)swizzledSelectorForSelector:(SEL)selector;
{
return NSSelectorFromString([NSString stringWithFormat:@"_pd_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)]);
}
/// All swizzled delegate methods should make use of this guard.
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled.
/// The superclass implementation (and implementations in classes above that) will be executed without inteference if called from the original implementation.
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock
{
const void *key = selector;
// Don't run the sniffing block if we're inside a nested call
if (!objc_getAssociatedObject(object, key)) {
sniffingBlock();
}
// Mark that we're calling through to the original so we can detect nested calls
objc_setAssociatedObject(object, key, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
originalImplementationBlock();
objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls;
{
if ([cls instancesRespondToSelector:selector]) {
unsigned int numMethods = 0;
Method *methods = class_copyMethodList(cls, &numMethods);
BOOL implementsSelector = NO;
for (int index = 0; index < numMethods; index++) {
SEL methodSelector = method_getName(methods[index]);
if (selector == methodSelector) {
implementsSelector = YES;
break;
}
}
free(methods);
if (!implementsSelector) {
return YES;
}
}
return NO;
}
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock;
{
if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
return;
}
#ifdef __IPHONE_6_0
IMP implementation = imp_implementationWithBlock((id)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock));
#else
IMP implementation = imp_implementationWithBlock((__bridge void *)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock));
#endif
Method oldMethod = class_getInstanceMethod(cls, selector);
if (oldMethod) {
class_addMethod(cls, swizzledSelector, implementation, methodDescription.types);
Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(oldMethod, newMethod);
} else {
class_addMethod(cls, selector, implementation, methodDescription.types);
}
}
#pragma mark - Delegate Injection
+ (void)swizzleNSURLSessionClasses;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _swizzleNSURLSessionClasses];
});
}
+ (void)_swizzleNSURLSessionClasses;
{
// On iOS 8 we want to swizzle __NSCFURLSessionConnection. On 9 we want to swizzle its subclass, __NSCFURLLocalSessionConnection
Class cfURLSessionConnectionClass = NSClassFromString(@"__NSCFURLLocalSessionConnection");
if (cfURLSessionConnectionClass == nil) {
cfURLSessionConnectionClass = NSClassFromString(@"__NSCFURLSessionConnection");
}
if (!cfURLSessionConnectionClass) {
WXDevLog(@"Could not find __NSCFURLSessionConnection or __NSCFURLLocalSessionConnection");
return;
}
unsigned int outCount = 0;
Method *methods = class_copyMethodList([__NSCFURLSessionConnection_Swizzles class], &outCount);
for (int i = 0; i < outCount; i++) {
Method m = methods[i];
SEL sourceMethod = method_getName(m);
const char *encoding = method_getTypeEncoding(m);
NSString *sourceMethodName = NSStringFromSelector(sourceMethod);
NSAssert([sourceMethodName hasPrefix:@"WX_"], @"Expecting swizzle methods only");
NSString *originalMethodName = [sourceMethodName substringFromIndex:3];
SEL originalMethod = NSSelectorFromString(originalMethodName);
NSAssert(originalMethod, @"Must find selector");
IMP sourceImp = method_getImplementation(m);
IMP originalImp = class_getMethodImplementation(cfURLSessionConnectionClass, originalMethod);
NSAssert(originalImp, @"Must find imp");
BOOL success = class_addMethod(cfURLSessionConnectionClass, sourceMethod, originalImp, encoding);
if (!success) {
NSAssert(success, @"Should be successful");
}
IMP replacedImp = class_replaceMethod(cfURLSessionConnectionClass, originalMethod, sourceImp, encoding);
if (!replacedImp) {
NSAssert(replacedImp, @"Expected originam method to have been replaced");
}
}
if (methods) {
free(methods);
}
}
+ (void)injectIntoAllNSURLConnectionDelegateClasses;
{
// Only allow swizzling once.
static BOOL swizzled = NO;
if (swizzled) {
return;
}
swizzled = YES;
// Swizzle any classes that implement one of these selectors.
const SEL selectors[] = {
@selector(connectionDidFinishLoading:),
@selector(connection:didReceiveResponse:),
@selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
@selector(URLSession:task:didCompleteWithError:),
@selector(URLSession:downloadTask:didFinishDownloadingToURL:),
@selector(URLSession:task:didFinishCollectingMetrics:)
};
const int numSelectors = sizeof(selectors) / sizeof(SEL);
Class *classes = NULL;
int numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (NSInteger classIndex = 0; classIndex < numClasses; ++classIndex) {
Class class = classes[classIndex];
if (class_getClassMethod(class, @selector(isSubclassOfClass:)) == NULL) {
continue;
}
//Temporarily filter classes beginning FBSDK
if([NSStringFromClass(class) rangeOfString:@"FBSDK"].location != NSNotFound) {
continue;
}
//Temporarily filter classes beginning ANManager
if([NSStringFromClass(class) rangeOfString:@"ANManager"].location != NSNotFound) {
continue;
}
if (![class isSubclassOfClass:[NSObject class]]) {
continue;
}
if ([class isSubclassOfClass:[WXNetworkDomainController class]]) {
continue;
}
for (int selectorIndex = 0; selectorIndex < numSelectors; ++selectorIndex) {
if ([class instancesRespondToSelector:selectors[selectorIndex]]) {
[self injectIntoDelegateClass:class];
break;
}
}
}
free(classes);
}
[self injectIntoNSURLSessionTaskResume];
[self injectIntoNSURLConnectionAsynchronousClassMethod];
[self injectIntoNSURLConnectionSynchronousClassMethod];
}
+ (void)injectIntoNSURLSessionTaskResume
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// In iOS 7 resume lives in __NSCFLocalSessionTask
// In iOS 8 resume lives in NSURLSessionTask
// In iOS 9 resume lives in __NSCFURLSessionTask
Class class = Nil;
if (![[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
class = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]);
} else if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 9) {
class = [NSURLSessionTask class];
} else {
class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]);
}
SEL selector = @selector(resume);
SEL swizzledSelector = [WXTracingUtility swizzledSelectorForSelector:selector];
Method originalResume = class_getInstanceMethod(class, selector);
void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) {
[[WXNetworkDomainController defaultInstance] URLSessionTaskWillResume:slf];
((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
};
IMP implementation = imp_implementationWithBlock(swizzleBlock);
class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalResume));
Method newResume = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalResume, newResume);
});
}
+ (void)injectIntoDelegateClass:(Class)cls;
{
// Connections
[self injectWillSendRequestIntoDelegateClass:cls];
[self injectDidReceiveDataIntoDelegateClass:cls];
[self injectDidReceiveResponseIntoDelegateClass:cls];
[self injectDidFinishLoadingIntoDelegateClass:cls];
[self injectDidFailWithErrorIntoDelegateClass:cls];
[self injectDidFinishCollectingMetrics:cls];
}
+ (void)injectWillSendRequestIntoDelegateClass:(Class)cls;
{
SEL selector = @selector(connection:willSendRequest:redirectResponse:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
if (!protocol) {
protocol = @protocol(NSURLConnectionDelegate);
}
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef NSURLRequest *(^NSURLConnectionWillSendRequestBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response);
NSURLConnectionWillSendRequestBlock undefinedBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) {
[[WXNetworkDomainController defaultInstance] connection:connection willSendRequest:request redirectResponse:response];
return request;
};
NSURLConnectionWillSendRequestBlock implementationBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) {
__block NSURLRequest *returnValue = nil;
[self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
undefinedBlock(slf, connection, request, response);
} originalImplementationBlock:^{
returnValue = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, connection, request, response);
}];
return returnValue;
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
+ (void)injectDidReceiveResponseIntoDelegateClass:(Class)cls;
{
SEL selector = @selector(connection:didReceiveResponse:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
if (!protocol) {
protocol = @protocol(NSURLConnectionDelegate);
}
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef void (^NSURLConnectionDidReceiveResponseBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response);
NSURLConnectionDidReceiveResponseBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response) {
[[WXNetworkDomainController defaultInstance] connection:connection didReceiveResponse:response];
};
NSURLConnectionDidReceiveResponseBlock implementationBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response) {
[self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
undefinedBlock(slf, connection, response);
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, response);
}];
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
+ (void)injectDidReceiveDataIntoDelegateClass:(Class)cls;
{
SEL selector = @selector(connection:didReceiveData:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
if (!protocol) {
protocol = @protocol(NSURLConnectionDelegate);
}
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef void (^NSURLConnectionDidReceiveDataBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data);
NSURLConnectionDidReceiveDataBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data) {
[[WXNetworkDomainController defaultInstance] connection:connection didReceiveData:data];
};
NSURLConnectionDidReceiveDataBlock implementationBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data) {
[self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
undefinedBlock(slf, connection, data);
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, data);
}];
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
+ (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls;
{
SEL selector = @selector(connectionDidFinishLoading:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
if (!protocol) {
protocol = @protocol(NSURLConnectionDelegate);
}
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef void (^NSURLConnectionDidFinishLoadingBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection);
NSURLConnectionDidFinishLoadingBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection) {
[[WXNetworkDomainController defaultInstance] connectionDidFinishLoading:connection];
};
NSURLConnectionDidFinishLoadingBlock implementationBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection) {
[self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
undefinedBlock(slf, connection);
} originalImplementationBlock:^{
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSelector, connection);
}];
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
+ (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls;
{
SEL selector = @selector(connection:didFailWithError:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLConnectionDelegate);
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef void (^NSURLConnectionDidFailWithErrorBlock)(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error);
NSURLConnectionDidFailWithErrorBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error) {
[[WXNetworkDomainController defaultInstance] connection:connection didFailWithError:error];
};
NSURLConnectionDidFailWithErrorBlock implementationBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error) {
[self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{
undefinedBlock(slf, connection, error);
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, error);
}];
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
+(void)injectDidFinishCollectingMetrics:(Class)cls
{
NSLog(@"injectDidFinishCollectingMetrics");
SEL selector = @selector(URLSession:task:didFinishCollectingMetrics:);
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
Protocol *protocol = @protocol(NSURLSessionTaskDelegate);
if (![cls conformsToProtocol:protocol]) {
return;
}
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
typedef void (^NSURLConnectionDidFinishCollectingMetricsBlock)(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask* task, NSURLSessionTaskMetrics *metrics);
NSURLConnectionDidFinishCollectingMetricsBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask* task, NSURLSessionTaskMetrics *metrics) {
[[WXNetworkDomainController defaultInstance] URLSession:session task:task didFinishCollectingMetrics:metrics];
};
NSURLConnectionDidFinishCollectingMetricsBlock implementationBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask* task, NSURLSessionTaskMetrics *metrics) {
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
undefinedBlock(slf, session, task,metrics);
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id,id))objc_msgSend)(slf, swizzledSelector, session, task,metrics);
}];
};
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
}
- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task
{
// Since resume can be called multiple times on the same task, only treat the first resume as
// the equivalent to connection:willSendRequest:...
[self performBlock:^{
NSString *requestID =[self requestStateForTask:task].requestID;
_WXRequestState *requestState = [self requestStateForTask:task];
if (!requestState.request) {
requestState.request = task.currentRequest;
[[WXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:task.currentRequest redirectResponse:nil];
}
}];
}
+ (void)injectIntoNSURLConnectionAsynchronousClassMethod
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:);
SEL swizzledSelector = [WXTracingUtility swizzledSelectorForSelector:selector];
typedef void (^NSURLConnectionAsyncCompletion)(NSURLResponse* response, NSData* data, NSError* connectionError);
void (^asyncSwizzleBlock)(Class, NSURLRequest *, NSOperationQueue *, NSURLConnectionAsyncCompletion) = ^(Class slf, NSURLRequest *request, NSOperationQueue *queue, NSURLConnectionAsyncCompletion completion) {
if ([WXDebugger isEnabled]) {
NSString *requestID = [self nextRequestID];
[[WXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
[[WXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[[WXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
[[WXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
if (connectionError) {
[[WXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:connectionError];
} else {
[[WXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
}
// Call through to the original completion handler
if (completion) {
completion(response, data, connectionError);
}
};
((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completionWrapper);
} else {
((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completion);
}
};
[WXTracingUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector];
});
}
+ (void)injectIntoNSURLConnectionSynchronousClassMethod
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
SEL selector = @selector(sendSynchronousRequest:returningResponse:error:);
SEL swizzledSelector = [WXTracingUtility swizzledSelectorForSelector:selector];
NSData *(^syncSwizzleBlock)(Class, NSURLRequest *, NSURLResponse **, NSError **) = ^NSData *(Class slf, NSURLRequest *request, NSURLResponse **response, NSError **error) {
NSData *data = nil;
if ([WXDebugger isEnabled]) {
NSString *requestID = [self nextRequestID];
[[WXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
[[WXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSError *temporaryError = nil;
NSURLResponse *temporaryResponse = nil;
data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, &temporaryResponse, &temporaryError);
[[WXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:temporaryResponse];
[[WXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
if (temporaryError) {
[[WXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:temporaryError];
} else {
[[WXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
}
if (error) {
*error = temporaryError;
}
if (response) {
*response = temporaryResponse;
}
} else {
data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, response, error);
}
return data;
};
[WXTracingUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector];
});
}
+ (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class
{
return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
}
#pragma mark - Initialization
- (id)init;
{
self = [super init];
if (!self) {
return nil;
}
_connectionStates = [[NSMutableDictionary alloc] init];
_responseCache = [[NSCache alloc] init];
_queue = dispatch_queue_create("com.squareup.ponydebugger.WXNetworkDomainController", DISPATCH_QUEUE_SERIAL);
return self;
}
- (void)dealloc;
{
if (_queue) {
// dispatch_release(_queue);
_queue = nil;
}
}
#pragma mark - WXNetworkCommandDelegate
- (void)domain:(WXNetworkDomain *)domain canClearBrowserCacheWithCallback:(void (^)(NSNumber *, id))callback;
{
callback([NSNumber numberWithBool:NO], nil);
}
- (void)domain:(WXNetworkDomain *)domain canClearBrowserCookiesWithCallback:(void (^)(NSNumber *, id))callback
{
callback([NSNumber numberWithBool:NO], nil);
}
- (void)domain:(WXNetworkDomain *)domain clearBrowserCacheWithCallback:(void (^)(id))callback;
{
[[NSURLCache sharedURLCache] removeAllCachedResponses];
callback(nil);
}
- (void)domain:(WXNetworkDomain *)domain getResponseBodyWithRequestId:(NSString *)requestId callback:(void (^)(NSString *, NSNumber *, id))callback;
{
NSDictionary *response = [_responseCache objectForKey:requestId];
callback([response objectForKey:@"body"], [response objectForKey:@"base64Encoded"], nil);
}
#pragma mark - public Methods
- (NSCache *)getNetWorkResponseCache {
return _responseCache;
}
#pragma mark - Private Methods
- (void)setResponse:(NSData *)responseBody forRequestID:(NSString *)requestID response:(NSURLResponse *)response request:(NSURLRequest *)request;
{
id<WXPrettyStringPrinting> prettyStringPrinter = [WXNetworkDomainController prettyStringPrinterForResponse:response withRequest:request];
NSString *encodedBody;
BOOL isBinary;
if (!prettyStringPrinter) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
encodedBody = [responseBody base64EncodedStringWithOptions:0];
#else
encodedBody = responseBody.base64Encoding;
#endif
isBinary = YES;
} else {
encodedBody = [prettyStringPrinter prettyStringForData:responseBody forResponse:response request:request];
isBinary = NO;
}
NSDictionary *responseDict = [NSDictionary dictionaryWithObjectsAndKeys:
encodedBody, @"body",
[NSNumber numberWithBool:isBinary], @"base64Encoded",
nil];
[_responseCache setObject:responseDict forKey:requestID cost:[responseBody length]];
}
- (void)performBlock:(dispatch_block_t)block;
{
dispatch_async(_queue, block);
}
#pragma mark - Private Methods (Connections)
- (_WXRequestState *)requestStateForConnection:(NSURLConnection *)connection;
{
NSValue *key = [NSValue valueWithNonretainedObject:connection];
_WXRequestState *state = [_connectionStates objectForKey:key];
if (!state) {
state = [[_WXRequestState alloc] init];
state.requestID = [[self class] nextRequestID];
[_connectionStates setObject:state forKey:key];
}
return state;
}
- (NSString *)requestIDForConnection:(NSURLConnection *)connection;
{
return [self requestStateForConnection:connection].requestID;
}
- (void)setResponse:(NSURLResponse *)response forConnection:(NSURLConnection *)connection;
{
[self requestStateForConnection:connection].response = response;
}
- (NSURLResponse *)responseForConnection:(NSURLConnection *)connection;
{
return [self requestStateForConnection:connection].response;
}
- (void)setRequest:(NSURLRequest *)request forConnection:(NSURLConnection *)connection;
{
[self requestStateForConnection:connection].request = request;
}
- (NSURLRequest *)requestForConnection:(NSURLConnection *)connection;
{
return [self requestStateForConnection:connection].request;
}
- (void)setAccumulatedData:(NSMutableData *)data forConnection:(NSURLConnection *)connection;
{
_WXRequestState *requestState = [self requestStateForConnection:connection];
requestState.dataAccumulator = data;
}
- (void)addAccumulatedData:(NSData *)data forConnection:(NSURLConnection *)connection;
{
NSMutableData *dataAccumulator = [self requestStateForConnection:connection].dataAccumulator;
[dataAccumulator appendData:data];
}
- (NSData *)accumulatedDataForConnection:(NSURLConnection *)connection;
{
return [self requestStateForConnection:connection].dataAccumulator;
}
// This removes storing the accumulated request/response from the dictionary so we can release connection
- (void)connectionFinished:(NSURLConnection *)connection;
{
NSValue *key = [NSValue valueWithNonretainedObject:connection];
[_connectionStates removeObjectForKey:key];
}
#pragma mark - Private Methods (Tasks)
- (_WXRequestState *)requestStateForTask:(NSURLSessionTask *)task;
{
NSValue *key = [NSValue valueWithNonretainedObject:task];
_WXRequestState *state = [_connectionStates objectForKey:key];
if (!state) {
state = [[_WXRequestState alloc] init];
state.requestID = [[self class] nextRequestID];
[_connectionStates setObject:state forKey:key];
}
return state;
}
- (_WXRequestState *)requestStateForMetricsTask:(NSURLSessionTask *)task;
{
NSValue *key = [NSValue valueWithNonretainedObject:task];
_WXRequestState *state = [_connectionStates objectForKey:key];
if (!state) {
state = [[_WXRequestState alloc] init];
state.requestID = [[self class] currentRequestID];
[_connectionStates setObject:state forKey:key];
}
return state;
}
- (NSString *)requestIDForTask:(NSURLSessionTask *)task;
{
return [self requestStateForTask:task].requestID;
}
- (NSString *)requestIDForMetricsTask:(NSURLSessionTask *)task;
{
return [self requestStateForMetricsTask:task].requestID;
}
- (void)setResponse:(NSURLResponse *)response forTask:(NSURLSessionTask *)task;
{
[self requestStateForTask:task].response = response;
}
- (NSURLResponse *)responseForTask:(NSURLSessionTask *)task
{
return [self requestStateForTask:task].response;
}
- (void)setRequest:(NSURLRequest *)request forTask:(NSURLSessionTask *)task;
{
[self requestStateForTask:task].request = request;
}
- (NSURLRequest *)requestForTask:(NSURLSessionTask *)task;
{
return [self requestStateForTask:task].request;
}
- (void)setAccumulatedData:(NSMutableData *)data forTask:(NSURLSessionTask *)task;
{
_WXRequestState *requestState = [self requestStateForTask:task];
requestState.dataAccumulator = data;
}
- (void)addAccumulatedData:(NSData *)data forTask:(NSURLSessionTask *)task;
{
NSMutableData *dataAccumulator = [self requestStateForTask:task].dataAccumulator;
[dataAccumulator appendData:data];
}
- (NSData *)accumulatedDataForTask:(NSURLSessionTask *)task;
{
return [self requestStateForTask:task].dataAccumulator;
}
// This removes storing the accumulated request/response from the dictionary so we can release task
- (void)taskFinished:(NSURLSessionTask *)task;
{
NSValue *key = [NSValue valueWithNonretainedObject:task];
[_connectionStates removeObjectForKey:key];
}
@end
@implementation WXNetworkDomainController (NSURLConnectionHelpers)
- (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response;
{
[self performBlock:^{
[self setRequest:request forConnection:connection];
WXNetworkRequest *networkRequest = [WXNetworkRequest networkRequestWithURLRequest:request];
WXNetworkResponse *networkRedirectResponse = response ? [[WXNetworkResponse alloc] initWithURLResponse:response request:request] : nil;
[self.domain requestWillBeSentWithRequestId:[self requestIDForConnection:connection]
frameId:@"3888.3"
loaderId:@"11111"
documentURL:[request.URL absoluteString]
request:networkRequest
timestamp:[NSDate WX_timestamp]
initiator:nil
redirectResponse:networkRedirectResponse];
if ([WXDebugger isEnabled]) {
NSString *requestID = [self requestIDForConnection:connection];
[[WXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
}
}];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
[self performBlock:^{
if ([response respondsToSelector:@selector(copyWithZone:)]) {
// If the request wasn't generated yet, then willSendRequest was not called. This appears to be an inconsistency in documentation
// and behavior.
NSURLRequest *request = [self requestForConnection:connection];
if (!request && [connection respondsToSelector:@selector(currentRequest)]) {
NSLog(@"PonyDebugger Warning: -[WXNetworkDomainController connection:willSendRequest:redirectResponse:] not called, request timestamp may be inaccurate. See Known Issues in the README for more information.");
request = connection.currentRequest;
[self setRequest:request forConnection:connection];
WXNetworkRequest *networkRequest = [WXNetworkRequest networkRequestWithURLRequest:request];
[self.domain requestWillBeSentWithRequestId:[self requestIDForConnection:connection]
frameId:@"3888.3"
loaderId:@"11111"
documentURL:[request.URL absoluteString]
request:networkRequest
timestamp:[NSDate WX_timestamp]
initiator:nil
redirectResponse:nil];
}
[self setResponse:response forConnection:connection];
NSMutableData *dataAccumulator = nil;
if (response.expectedContentLength < 0) {
dataAccumulator = [[NSMutableData alloc] init];
} else {
dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength];
}
[self setAccumulatedData:dataAccumulator forConnection:connection];
NSString *requestID = [self requestIDForConnection:connection];
WXNetworkResponse *networkResponse = [WXNetworkResponse networkResponseWithURLResponse:response request:[self requestForConnection:connection]];
[self.domain responseReceivedWithRequestId:requestID
frameId:@"3888.3"
loaderId:@"11111"
timestamp:[NSDate WX_timestamp]
type:response.WX_responseType
response:networkResponse];
if([WXDebugger isEnabled]){
[[WXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
}
}
}];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
{
// Just to be safe since we're doing this async
data = [data copy];
[self performBlock:^{
[self addAccumulatedData:data forConnection:connection];
if ([self accumulatedDataForConnection:connection] == nil) return;
NSNumber *length = [NSNumber numberWithInteger:data.length];
NSString *requestID = [self requestIDForConnection:connection];
[self.domain dataReceivedWithRequestId:requestID
timestamp:[NSDate WX_timestamp]
dataLength:length
encodedDataLength:length];
if([WXDebugger isEnabled]){
[[WXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
}
}];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
{
[self performBlock:^{
NSURLResponse *response = [self responseForConnection:connection];
NSString *requestID = [self requestIDForConnection:connection];
NSData *accumulatedData = [self accumulatedDataForConnection:connection];
[self setResponse:accumulatedData
forRequestID:requestID
response:response
request:[self requestForConnection:connection]];
[self.domain loadingFinishedWithRequestId:requestID
timestamp:[NSDate WX_timestamp]];
[self connectionFinished:connection];
if([WXDebugger isEnabled]){
[[WXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:accumulatedData];
}
}];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
{
[self performBlock:^{
[self.domain loadingFailedWithRequestId:[self requestIDForConnection:connection]
timestamp:[NSDate WX_timestamp]
errorText:[error localizedDescription]
canceled:[NSNumber numberWithBool:NO]];
[self connectionFinished:connection];
if([WXDebugger isEnabled]){
NSString *requestID = [self requestIDForConnection:connection];
[[WXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error];
}
}];
}
@end
@implementation WXNetworkDomainController (NSURLSessionTaskHelpers)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request
{
[self performBlock:^{
NSMutableURLRequest *newRequest = [request mutableCopy];
[session.configuration.HTTPAdditionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (![newRequest valueForHTTPHeaderField:key]) {
[newRequest setValue:obj forHTTPHeaderField:key];
}
}];
[self setRequest:newRequest forTask:task];
WXNetworkRequest *networkRequest = [WXNetworkRequest networkRequestWithURLRequest:request];
WXNetworkResponse *networkRedirectResponse = response ? [[WXNetworkResponse alloc] initWithURLResponse:response request:request] : nil;
NSDate *startDate = nil;
if ([task respondsToSelector:@selector(startTime)]) {
startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:[task startTime]];
} else {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"PonyDebugger Warning: Some requests' timestamps may be inaccurate. See Known Issues in the README for more information.");
});
startDate = [NSDate date];
}
[self.domain requestWillBeSentWithRequestId:[self requestIDForTask:task]
frameId:@"3888.3"
loaderId:@"11111"
documentURL:[request.URL absoluteString]
request:networkRequest
timestamp:@(startDate.timeIntervalSince1970)
initiator:nil
redirectResponse:networkRedirectResponse];
if([WXDebugger isEnabled]){
[[WXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:[self requestIDForTask:task] request:request redirectResponse:response];
}
}];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response;
{
if ([response respondsToSelector:@selector(copyWithZone:)]) {
// willSendRequest does not exist in NSURLSession. Here's a workaround.
NSURLRequest *request = [self requestForTask:dataTask];
if (!request && [dataTask respondsToSelector:@selector(currentRequest)]) {
/// We need to set headers from the session configuration
NSMutableURLRequest *request = [dataTask.currentRequest mutableCopy];
/// FOr some reason, the currentRequest doesn't always keep the HTTPBody around.
if (request.HTTPBody == nil && dataTask.originalRequest.HTTPBody) {
request.HTTPBody = dataTask.originalRequest.HTTPBody;
}
[session.configuration.HTTPAdditionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (![request valueForHTTPHeaderField:key]) {
[request setValue:obj forHTTPHeaderField:key];
}
}];
[self setRequest:request forTask:dataTask];
NSDate *startDate = nil;
if ([dataTask respondsToSelector:@selector(startTime)]) {
startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:[dataTask startTime]];
} else {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"PonyDebugger Warning: Some requests' timestamps may be inaccurate. See Known Issues in the README for more information.");
});
startDate = [NSDate date];
}
WXNetworkRequest *networkRequest = [WXNetworkRequest networkRequestWithURLRequest:request];
[self.domain requestWillBeSentWithRequestId:[self requestIDForTask:dataTask]
frameId:@"3888.3"
loaderId:@"11111"
documentURL:[request.URL absoluteString]
request:networkRequest
timestamp:@(startDate.timeIntervalSince1970)
initiator:nil
redirectResponse:nil];
}
[self setResponse:response forTask:dataTask];
NSMutableData *dataAccumulator = nil;
if (response.expectedContentLength < 0) {
dataAccumulator = [[NSMutableData alloc] init];
} else {
dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength];
}
[self setAccumulatedData:dataAccumulator forTask:dataTask];
NSString *requestID = [self requestIDForTask:dataTask];
WXNetworkResponse *networkResponse = [WXNetworkResponse networkResponseWithURLResponse:response request:[self requestForTask:dataTask]];
[self.domain responseReceivedWithRequestId:requestID
frameId:@"3888.3"
loaderId:@"11111"
timestamp:[NSDate WX_timestamp]
type:response.WX_responseType
response:networkResponse];
if([WXDebugger isEnabled]){
NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDataTask (delegate: %@)", NSStringFromSelector(_cmd)];
[[WXNetworkRecorder defaultRecorder] recordMechanism:requestMechanism forRequestID:requestID];
[[WXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
}
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
// Just to be safe since we're doing this async
data = [data copy];
[self performBlock:^{
[self addAccumulatedData:data forTask:dataTask];
if ([self accumulatedDataForTask:dataTask] == nil) return;
NSNumber *length = [NSNumber numberWithInteger:data.length];
NSString *requestID = [self requestIDForTask:dataTask];
[self.domain dataReceivedWithRequestId:requestID
timestamp:[NSDate WX_timestamp]
dataLength:length
encodedDataLength:length];
if([WXDebugger isEnabled]){
[[WXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:data.length];
}
}];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
{
[self performBlock:^{
NSURLResponse *response = [self responseForTask:task];
NSString *requestID = [self requestIDForTask:task];
NSData *accumulatedData = [self accumulatedDataForTask:task];
if (error) {
[self.domain loadingFailedWithRequestId:[self requestIDForTask:task]
timestamp:[NSDate WX_timestamp]
errorText:[error localizedDescription]
canceled:[NSNumber numberWithBool:NO]];
} else {
[self setResponse:accumulatedData
forRequestID:requestID
response:response
request:[self requestForTask:task]];
}
// [self.domain loadingFinishedWithRequestId:requestID timestamp:[NSDate WX_timestamp]];
[self taskFinished:task];
if([WXDebugger isEnabled]){
if (error) {
[[WXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error];
} else {
[[WXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:accumulatedData];
}
}
}];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
NSLog(@"injectDidFinishCollectingMetrics excute");
__weak typeof(self) weakSelf = self;
[self performBlock:^{
if(metrics && metrics.transactionMetrics && [metrics.transactionMetrics count]>0){
for (NSURLSessionTaskTransactionMetrics *transactionMetrics in metrics.transactionMetrics) {
[weakSelf parseMetrics:task transactionMetrics:transactionMetrics];
}
}
// [self.domain loadingFinishedWithRequestId:[self requestStateForMetricsTask:task].requestID timestamp:[NSDate WX_timestamp]];
}];
}
-(void)parseMetrics:(NSURLSessionTask *)task transactionMetrics:(NSURLSessionTaskTransactionMetrics *)transactionMetrics
{
NSString *requestID = [self requestStateForMetricsTask:task].requestID;
NSURLRequest *request = [self requestForTask:task];
WXNetworkResponse *networkResponse = [WXNetworkResponse networkResponseWithURLResponse:transactionMetrics.response request:request];
WXNetworkResourceTiming *timeline = [[WXNetworkResourceTiming alloc] init];
timeline.requestTime = [NSNumber numberWithDouble:transactionMetrics.requestStartDate.timeIntervalSince1970];//[NSDate WX_timestamp];
timeline.proxyStart = [NSNumber numberWithInt:0];
timeline.proxyEnd = [NSNumber numberWithInt:5];
timeline.dnsStart = [NSNumber numberWithInt:10];
timeline.dnsEnd = [NSNumber numberWithInt:20];
timeline.connectStart = [NSNumber numberWithDouble:transactionMetrics.connectStartDate.timeIntervalSince1970];
timeline.connectEnd = [NSNumber numberWithDouble:transactionMetrics.connectEndDate.timeIntervalSince1970];
timeline.sslStart = [NSNumber numberWithDouble:transactionMetrics.secureConnectionStartDate.timeIntervalSince1970];
timeline.sslEnd = [NSNumber numberWithDouble:transactionMetrics.secureConnectionEndDate.timeIntervalSince1970];
timeline.sendStart = [NSNumber numberWithDouble:transactionMetrics.requestStartDate.timeIntervalSince1970];
timeline.sendEnd = [NSNumber numberWithDouble:transactionMetrics.requestEndDate.timeIntervalSince1970];
timeline.receiveHeadersEnd = [NSNumber numberWithDouble:transactionMetrics.responseEndDate.timeIntervalSince1970];
networkResponse.timing = timeline;
[self.domain responseReceivedWithRequestId:requestID
frameId:@"3888.3"
loaderId:@"11111"
timestamp:[NSDate WX_timestamp]
type:transactionMetrics.response.WX_responseType
response:networkResponse];
}
@end
@implementation WXNetworkRequest (WXNetworkHelpers)
- (id)initWithURLRequest:(NSURLRequest *)request
{
self = [super init];
if (!self) {
return nil;
}
self.url = [request.URL absoluteString];
self.method = request.HTTPMethod;
self.headers = request.allHTTPHeaderFields;
NSData *body = request.HTTPBody;
// pretty print and redact sensitive fields
id<WXPrettyStringPrinting> prettyStringPrinter = [WXNetworkDomainController prettyStringPrinterForRequest:request];
if (prettyStringPrinter) {
self.postData = [prettyStringPrinter prettyStringForData:body forRequest:request];
} else {
// If the data isn't UTF-8 it will just be nil;
self.postData = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
}
return self;
}
+ (WXNetworkRequest *)networkRequestWithURLRequest:(NSURLRequest *)request;
{
return [[[self class] alloc] initWithURLRequest:request];
}
@end
@implementation WXNetworkResponse (WXNetworkHelpers)
- (id)initWithURLResponse:(NSURLResponse *)response request:(NSURLRequest *)request
{
self = [super init];
if (!self) {
return nil;
}
self.url = [response.URL absoluteString];
// Set statusText if this was a HTTP Response
self.statusText = @"";
self.mimeType = response.MIMEType;
self.requestHeaders = request.allHTTPHeaderFields;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
self.status = [NSNumber numberWithInteger:httpResponse.statusCode];
self.statusText = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
self.headers = httpResponse.allHeaderFields;
}
return self;
}
+ (WXNetworkResponse *)networkResponseWithURLResponse:(NSURLResponse *)response request:(NSURLRequest *)request;
{
return [[[self class] alloc] initWithURLResponse:response request:request];
}
@end
@implementation NSURLResponse (WXNetworkHelpers)
- (NSString *)WX_responseType;
{
NSString *MIMEType = self.MIMEType;
NSString *contentType = [MIMEType lowercaseString];
NSString *type = @"Other";
if ([contentType rangeOfString:@"image"].length != 0) {
type = @"Image";
} else if ([contentType rangeOfString:@"json"].length != 0) {
type = @"XHR";
} else if ([contentType rangeOfString:@"javascript"].length != 0) {
type = @"Script";
} else if ([contentType rangeOfString:@"css"].length != 0) {
type = @"Stylesheet";
}
return type;
}
@end
@implementation _WXRequestState
@synthesize request = _request;
@synthesize response = _response;
@synthesize requestID = _requestID;
@synthesize dataAccumulator = _dataAccumulator;
@end