Skip to content

Commit

Permalink
Split recording to logger, interceptor and listener (#9)
Browse files Browse the repository at this point in the history
* Split recording to logger, interceptor and listener

* bug fix in swizzling and fake protocol

* fix grpc observer
  • Loading branch information
mehdiimrz authored Jan 9, 2022
1 parent c6b6d8d commit 0de3746
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
57 changes: 43 additions & 14 deletions Sources/NetShears/NetShears.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,51 @@ 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 = {
var savedModifiers = [RequestEvaluatorModifier]().retrieveFromDisk()
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) {
Expand Down Expand Up @@ -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)
}
}
}
10 changes: 1 addition & 9 deletions Sources/NetShears/NetworkInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
32 changes: 25 additions & 7 deletions Sources/NetShears/NetworkRequestInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class NetworkLoggerUrlProtocol: URLProtocol {
lazy var requestObserver: RequestObserverProtocol = {
RequestObserver(options: [
RequestStorage.shared,
RequestBroadcast.shared
])
}()

Expand Down
143 changes: 143 additions & 0 deletions Sources/NetShears/URLProtocol/NetwrokListenerUrlProtocol.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}


0 comments on commit 0de3746

Please sign in to comment.