From 0de37463cc7dbaef9052c0483ad07ab4b61a5553 Mon Sep 17 00:00:00 2001 From: Mehdi Mirzaie Date: Sun, 9 Jan 2022 20:35:55 +0330 Subject: [PATCH] Split recording to logger, interceptor and listener (#9) * Split recording to logger, interceptor and listener * bug fix in swizzling and fake protocol * fix grpc observer --- .../URLSessionConfiguration+Extension.swift | 13 +- Sources/NetShears/NetShears.swift | 57 +++++-- Sources/NetShears/NetworkInterceptor.swift | 10 +- .../NetShears/NetworkRequestInterceptor.swift | 32 +++- .../NetworkLoggerUrlProtocol.swift | 1 - .../NetwrokListenerUrlProtocol.swift | 143 ++++++++++++++++++ 6 files changed, 222 insertions(+), 34 deletions(-) create mode 100644 Sources/NetShears/URLProtocol/NetwrokListenerUrlProtocol.swift diff --git a/Sources/NetShears/Extension/URLSessionConfiguration+Extension.swift b/Sources/NetShears/Extension/URLSessionConfiguration+Extension.swift index a1cdc52..2584e4a 100644 --- a/Sources/NetShears/Extension/URLSessionConfiguration+Extension.swift +++ b/Sources/NetShears/Extension/URLSessionConfiguration+Extension.swift @@ -14,10 +14,17 @@ extension URLSessionConfiguration { return [] } var originalProtocolClasses = fakeProcotolClasses.filter { - return $0 != NetworkInterceptorUrlProtocol.self && $0 != NetworkLoggerUrlProtocol.self + return $0 != NetworkInterceptorUrlProtocol.self && $0 != NetworkLoggerUrlProtocol.self && $0 != NetwrokListenerUrlProtocol.self + } + if NetShears.shared.loggerEnable { + originalProtocolClasses.insert(NetworkLoggerUrlProtocol.self, at: 0) + } + if NetShears.shared.listenerEnable { + originalProtocolClasses.insert(NetwrokListenerUrlProtocol.self, at: 0) + } + if NetShears.shared.interceptorEnable { + originalProtocolClasses.insert(NetworkInterceptorUrlProtocol.self, at: 0) } - originalProtocolClasses.insert(NetworkInterceptorUrlProtocol.self, at: 0) - originalProtocolClasses.insert(NetworkLoggerUrlProtocol.self, at: 0) return originalProtocolClasses } diff --git a/Sources/NetShears/NetShears.swift b/Sources/NetShears/NetShears.swift index ce70b40..313e8a0 100644 --- a/Sources/NetShears/NetShears.swift +++ b/Sources/NetShears/NetShears.swift @@ -10,6 +10,10 @@ import UIKit public final class NetShears: NSObject { public static let shared = NetShears() + internal var loggerEnable = false + internal var interceptorEnable = false + internal var listenerEnable = false + internal var swizzled = false let networkRequestInterceptor = NetworkRequestInterceptor() lazy var config: NetworkInterceptorConfig = { @@ -17,20 +21,40 @@ public final class NetShears: NSObject { return NetworkInterceptorConfig(modifiers: savedModifiers) }() - lazy var requestObserver: RequestObserverProtocol = { - RequestObserver(options: [ - RequestStorage.shared, - RequestBroadcast.shared - ]) - }() - - - public func startRecording(){ - self.networkRequestInterceptor.startRecording() + private func checkSwizzling() { + if swizzled == false { + self.networkRequestInterceptor.swizzleProtocolClasses() + swizzled = true + } } - - public func stopRecording(){ - self.networkRequestInterceptor.stopRecording() + public func startInterceptor() { + self.networkRequestInterceptor.startInterceptor() + checkSwizzling() + } + + public func stopInterceptor() { + self.networkRequestInterceptor.stopInterceptor() + checkSwizzling() + } + + public func startLogger() { + self.networkRequestInterceptor.startLogger() + checkSwizzling() + } + + public func stopLogger() { + self.networkRequestInterceptor.stopLogger() + checkSwizzling() + } + + public func startListener() { + self.networkRequestInterceptor.startListener() + checkSwizzling() + } + + public func stopListener() { + self.networkRequestInterceptor.stopListener() + checkSwizzling() } public func modify(modifier: RequestEvaluatorModifier) { @@ -64,6 +88,11 @@ public final class NetShears: NSObject { HPACKHeadersRequest: [String: String]?, HPACKHeadersResponse: [String: String]?){ let request = NetShearsRequestModel(url: url, host: host, requestObject: requestObject, responseObject: responseObject, success: success, statusCode: statusCode, duration: duration, HPACKHeadersRequest: HPACKHeadersRequest, HPACKHeadersResponse: HPACKHeadersResponse) - requestObserver.newRequestArrived(request) + if loggerEnable { + RequestStorage.shared.newRequestArrived(request) + } + if listenerEnable { + RequestBroadcast.shared.newRequestArrived(request) + } } } diff --git a/Sources/NetShears/NetworkInterceptor.swift b/Sources/NetShears/NetworkInterceptor.swift index 4dcc288..60e40ee 100644 --- a/Sources/NetShears/NetworkInterceptor.swift +++ b/Sources/NetShears/NetworkInterceptor.swift @@ -12,15 +12,7 @@ import Foundation @objc static let shared = NetworkInterceptor() let networkRequestInterceptor = NetworkRequestInterceptor() - - func startRecording(){ - self.networkRequestInterceptor.startRecording() - } - - func stopRecording(){ - self.networkRequestInterceptor.stopRecording() - } - + func shouldRequestModify(urlRequest: URLRequest) -> Bool { for modifer in NetShears.shared.config.modifiers { if modifer.isActionAllowed(urlRequest: urlRequest) { diff --git a/Sources/NetShears/NetworkRequestInterceptor.swift b/Sources/NetShears/NetworkRequestInterceptor.swift index 3c0ba07..c70232e 100644 --- a/Sources/NetShears/NetworkRequestInterceptor.swift +++ b/Sources/NetShears/NetworkRequestInterceptor.swift @@ -18,17 +18,35 @@ import Foundation method_exchangeImplementations(method1, method2) } - - func startRecording() { + + func startInterceptor() { + NetShears.shared.interceptorEnable = true URLProtocol.registerClass(NetworkInterceptorUrlProtocol.self) - URLProtocol.registerClass(NetworkLoggerUrlProtocol.self) - swizzleProtocolClasses() } - - func stopRecording() { + + func stopInterceptor() { + NetShears.shared.interceptorEnable = false URLProtocol.unregisterClass(NetworkInterceptorUrlProtocol.self) + } + + func startLogger() { + NetShears.shared.loggerEnable = true + URLProtocol.registerClass(NetworkLoggerUrlProtocol.self) + } + + func stopLogger() { + NetShears.shared.loggerEnable = false URLProtocol.unregisterClass(NetworkLoggerUrlProtocol.self) - swizzleProtocolClasses() + } + + func startListener() { + NetShears.shared.listenerEnable = true + URLProtocol.registerClass(NetwrokListenerUrlProtocol.self) + } + + func stopListener() { + NetShears.shared.listenerEnable = false + URLProtocol.unregisterClass(NetwrokListenerUrlProtocol.self) } } diff --git a/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift b/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift index f92e237..5c89595 100644 --- a/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift +++ b/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift @@ -19,7 +19,6 @@ class NetworkLoggerUrlProtocol: URLProtocol { lazy var requestObserver: RequestObserverProtocol = { RequestObserver(options: [ RequestStorage.shared, - RequestBroadcast.shared ]) }() diff --git a/Sources/NetShears/URLProtocol/NetwrokListenerUrlProtocol.swift b/Sources/NetShears/URLProtocol/NetwrokListenerUrlProtocol.swift new file mode 100644 index 0000000..7c4f2ed --- /dev/null +++ b/Sources/NetShears/URLProtocol/NetwrokListenerUrlProtocol.swift @@ -0,0 +1,143 @@ +// +// NetwrokListenerUrlProtocol.swift +// NetShears +// +// Created by Mehdi Mirzaie on 6/9/21. +// + +import Foundation + +class NetwrokListenerUrlProtocol: URLProtocol { + + struct Constants { + static let RequestHandledKey = "NetworkListenerUrlProtocol" + } + + var session: URLSession? + var sessionTask: URLSessionDataTask? + var currentRequest: NetShearsRequestModel? + lazy var requestObserver: RequestObserverProtocol = { + RequestObserver(options: [ + RequestBroadcast.shared + ]) + }() + + override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { + super.init(request: request, cachedResponse: cachedResponse, client: client) + + if session == nil { + session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } + } + + override class func canInit(with request: URLRequest) -> Bool { + + if NetwrokListenerUrlProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil { + return false + } + return true + } + + override class func canonicalRequest(for request: URLRequest) -> URLRequest { + return request + } + + override func startLoading() { + let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)! + NetwrokListenerUrlProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest) + sessionTask = session?.dataTask(with: newRequest as URLRequest) + sessionTask?.resume() + + currentRequest = NetShearsRequestModel(request: newRequest, session: session) + if let request = currentRequest { + requestObserver.newRequestArrived(request) + } + } + + override func stopLoading() { + sessionTask?.cancel() + currentRequest?.httpBody = body(from: request) + if let startDate = currentRequest?.date{ + currentRequest?.duration = fabs(startDate.timeIntervalSinceNow) * 1000 //Find elapsed time and convert to milliseconds + } + currentRequest?.isFinished = true + + if let request = currentRequest { + requestObserver.newRequestArrived(request) + } + session?.invalidateAndCancel() + } + + private func body(from request: URLRequest) -> Data? { + /// The receiver will have either an HTTP body or an HTTP body stream only one may be set for a request. + /// A HTTP body stream is preserved when copying an NSURLRequest object, + /// but is lost when a request is archived using the NSCoding protocol. + // return request.httpBody ?? request.httpBodyStream?.readfully() + return nil + } + + deinit { + session = nil + sessionTask = nil + currentRequest = nil + } +} + +extension NetwrokListenerUrlProtocol: URLSessionDataDelegate { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + client?.urlProtocol(self, didLoad: data) + if currentRequest?.dataResponse == nil{ + currentRequest?.dataResponse = data + } + else{ + currentRequest?.dataResponse?.append(data) + } + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) + currentRequest?.initResponse(response: response) + completionHandler(.allow) + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error = error { + currentRequest?.errorClientDescription = error.localizedDescription + client?.urlProtocol(self, didFailWithError: error) + } else { + client?.urlProtocolDidFinishLoading(self) + } + } + + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) + completionHandler(request) + } + + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + guard let error = error else { return } + currentRequest?.errorClientDescription = error.localizedDescription + client?.urlProtocol(self, didFailWithError: error) + } + + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + let protectionSpace = challenge.protectionSpace + let sender = challenge.sender + + if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + if let serverTrust = protectionSpace.serverTrust { + let credential = URLCredential(trust: serverTrust) + sender?.use(credential, for: challenge) + completionHandler(.useCredential, credential) + return + } + } + } + + func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + client?.urlProtocolDidFinishLoading(self) + } +} + +