Files
firebase-ios-sdk/FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.m

308 lines
14 KiB
Objective-C

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
#import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegate.h"
#import "FirebasePerformance/Sources/Instrumentation/Network/FPRNetworkInstrumentHelpers.h"
#pragma mark - NSURLConnectionDelegate methods
/** Returns the dispatch queue for all instrumentation to occur on. */
static dispatch_queue_t GetInstrumentationQueue(void) {
static dispatch_queue_t queue;
static dispatch_once_t token;
dispatch_once(&token, ^{
queue = dispatch_queue_create("com.google.FPRNSURLConnectionDelegateInstrument",
DISPATCH_QUEUE_SERIAL);
});
return queue;
}
/** Instruments connection:didFailWithError:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFailWithError(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didFailWithError:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSError *error) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:error];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSError *);
((OriginalImp)currentIMP)(object, selector, connection, error);
}];
}
}
#pragma mark - NSURLConnectionDataDelegate methods
/** Instruments connection:willSendRequest:redirectResponse:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionWillSendRequestRedirectResponse(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:willSendRequest:redirectResponse:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection,
NSURLRequest *request, NSURLResponse *response) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
typedef NSURLRequest *(*OriginalImp)(id, SEL, NSURLConnection *, NSURLRequest *,
NSURLResponse *);
return ((OriginalImp)currentIMP)(object, selector, connection, request, response);
}];
}
}
/** Instruments connection:didReceiveResponse:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidReceiveResponse(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didReceiveResponse:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSURLResponse *response) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
trace.responseCode = (int32_t)((NSHTTPURLResponse *)response).statusCode;
}
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURLResponse *);
((OriginalImp)currentIMP)(object, selector, connection, response);
}];
}
}
/** Instruments connection:didReceiveData:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidReceiveData(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didReceiveData:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSData *data) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
trace.responseSize += data.length;
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSData *);
((OriginalImp)currentIMP)(object, selector, connection, data);
}];
}
}
/** Instruments connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionAllTheTotals(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSInteger bytesWritten,
NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
if (totalBytesWritten >= totalBytesExpectedToWrite) {
[trace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
}
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSInteger, NSInteger, NSInteger);
((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
totalBytesExpectedToWrite);
}];
}
}
/** Instruments connectionDidFinishLoading:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFinishLoading(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connectionDidFinishLoading:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *);
((OriginalImp)currentIMP)(object, selector, connection);
}];
}
}
/** Instruments connection:didWriteData:totalBytesWritten:expectedTotalBytes:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(
FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, long long bytesWritten,
long long totalBytesWritten, long long expectedTotalBytes) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
trace.requestSize = totalBytesWritten;
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, long long, long long, long long);
((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
expectedTotalBytes);
}];
}
}
/** Instruments connectionDidFinishDownloading:destinationURL:.
*
* @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
*/
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void InstrumentConnectionDidFinishDownloadingDestinationURL(FPRClassInstrumentor *instrumentor) {
SEL selector = @selector(connectionDidFinishDownloading:destinationURL:);
FPRSelectorInstrumentor *selectorInstrumentor =
[instrumentor instrumentorForInstanceSelector:selector];
if (selectorInstrumentor) {
IMP currentIMP = selectorInstrumentor.currentIMP;
[selectorInstrumentor
setReplacingBlock:^(id object, NSURLConnection *connection, NSURL *destinationURL) {
FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
[trace didReceiveFileURL:destinationURL];
[trace didCompleteRequestWithResponse:nil error:nil];
[FPRNetworkTrace removeNetworkTraceFromObject:connection];
typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURL *);
((OriginalImp)currentIMP)(object, selector, connection, destinationURL);
}];
}
}
#pragma mark - Helper functions
FOUNDATION_STATIC_INLINE
NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
void CopySelector(SEL selector, FPRObjectInstrumentor *instrumentor) {
static Class fromClass = Nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fromClass = [FPRNSURLConnectionDelegate class];
});
if (![instrumentor.instrumentedObject respondsToSelector:selector]) {
[instrumentor copySelector:selector fromClass:fromClass isClassSelector:NO];
}
}
#pragma mark - FPRNSURLConnectionDelegateInstrument
@implementation FPRNSURLConnectionDelegateInstrument
- (void)registerInstrumentors {
// Do nothing by default. classes will be instrumented on-demand upon discovery.
}
- (void)registerClass:(Class)aClass {
dispatch_sync(GetInstrumentationQueue(), ^{
// If this class has already been instrumented, just return.
FPRClassInstrumentor *instrumentor = [[FPRClassInstrumentor alloc] initWithClass:aClass];
if (![self registerClassInstrumentor:instrumentor]) {
return;
}
InstrumentConnectionDidFailWithError(instrumentor);
InstrumentConnectionWillSendRequestRedirectResponse(instrumentor);
InstrumentConnectionDidReceiveResponse(instrumentor);
InstrumentConnectionDidReceiveData(instrumentor);
InstrumentConnectionAllTheTotals(instrumentor);
InstrumentConnectionDidFinishLoading(instrumentor);
InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(instrumentor);
InstrumentConnectionDidFinishDownloadingDestinationURL(instrumentor);
[instrumentor swizzle];
});
}
- (void)registerObject:(id)object {
dispatch_sync(GetInstrumentationQueue(), ^{
if ([object respondsToSelector:@selector(gul_class)]) {
return;
}
if (![self isObjectInstrumentable:object]) {
return;
}
FPRObjectInstrumentor *instrumentor = [[FPRObjectInstrumentor alloc] initWithObject:object];
// Register the non-swizzled versions of these methods.
CopySelector(@selector(connection:didFailWithError:), instrumentor);
CopySelector(@selector(connection:willSendRequest:redirectResponse:), instrumentor);
CopySelector(@selector(connection:didReceiveResponse:), instrumentor);
CopySelector(@selector(connection:didReceiveData:), instrumentor);
CopySelector(@selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:),
instrumentor);
if (![object respondsToSelector:@selector(connectionDidFinishDownloading:destinationURL:)]) {
CopySelector(@selector(connectionDidFinishLoading:), instrumentor);
}
CopySelector(@selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:),
instrumentor);
if (![object respondsToSelector:@selector(connectionDidFinishLoading:)]) {
CopySelector(@selector(connectionDidFinishDownloading:destinationURL:), instrumentor);
}
[instrumentor swizzle];
});
}
@end