美文网首页网络
iOS-利用NSURLProtocol进行网络监控

iOS-利用NSURLProtocol进行网络监控

作者: 海浪萌物 | 来源:发表于2019-12-26 17:11 被阅读0次

    参考文章:http://www.cocoachina.com/articles/19683

    啥也不说,先上代码

    /// 处理POST请求相关POST  用HTTPBodyStream来处理HTTPBody
    /// @param request 处理后的request
    - (NSMutableURLRequest *)handlePostRequestBodyWithRequest:(NSURLRequest *)request {
        NSMutableURLRequest * mutableRrequest = [request mutableCopy];
        if ([request.HTTPMethod isEqualToString:@"POST"]) {
            if (!request.HTTPBody) {
                uint8_t d[1024] = {0};
                NSInputStream *stream = request.HTTPBodyStream;
                NSMutableData *data = [[NSMutableData alloc] init];
                [stream open];
                while ([stream hasBytesAvailable]) {
                    NSInteger len = [stream read:d maxLength:1024];
                    if (len > 0 && stream.streamError == nil) {
                        [data appendBytes:(void *)d length:len];
                    }
                }
                mutableRrequest.HTTPBody = [data copy];
                [stream close];
            }
        }
        return mutableRrequest;
    }
    
    /// 重定向url,重新生成一个request
    /// @param request 重定向后的request
    +(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
    {
        if ([request.URL host].length == 0) {
            return request;
        }
        NSString *originUrlString = [request.URL absoluteString];
        NSString *originHostString = [request.URL host];
        NSRange hostRange = [originUrlString rangeOfString:originHostString];
        if (hostRange.location == NSNotFound) {
            return request;
        }
        // 替换host
        if ([originHostString containsString:@"baoyinxiaofei"]) {
            NSString *ip = @"app.baoyinxiaofei.com";
            NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
            NSURL *url = [NSURL URLWithString:urlString];
            request.URL = url;
        }
    
        NSMutableURLRequest *mutableRequest = [[self new] handlePostRequestBodyWithRequest:request];
        return mutableRequest;
    }
    
    //所有注册此Protocol的请求都会经过这个方法的判断,询问是否对该请求进行处理
    + (BOOL)canInitWithRequest:(NSURLRequest *)request{
    
        if (![request.URL.scheme isEqualToString:@"http"] &&
            ![request.URL.scheme isEqualToString:@"https"]) {
            return NO;
        }
    
        //看看是否已经处理过了,防止无限循环 根据业务来截取
        if ([NSURLProtocol propertyForKey: URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        return YES;
    }
    //可选方法,对需要拦截的请求进行自定的处理
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
    
        NSMutableURLRequest *mutableReqeust = [request mutableCopy];
        [NSURLProtocol setProperty:@YES
                            forKey:URLProtocolHandledKey
                         inRequest:mutableReqeust];
        mutableReqeust = [self redirectHostInRequset:mutableReqeust];
    
        return [mutableReqeust copy];
    
    }
    /**
        开始请求在这里需要我们手动的把请求发出去,可以使用原生的NSURLSessionDataTask,也可以使用的第三方网络库
        同时设置"NSURLSessionDataDelegate"协议,接收Server端的响应
    */
    - (void)startLoading {
    
        NSMutableURLRequest *mutableRequest = [self handlePostRequestBodyWithRequest:self.request];
        NSDictionary *dic = nil;
        NSError *error = nil;
        if (mutableRequest.HTTPBody) {
            dic = [NSJSONSerialization JSONObjectWithData:mutableRequest.HTTPBody options:NSJSONReadingFragmentsAllowed error:&error];
        }
    
        self.startDate                                        = [NSDate date];
        self.data                                             = [NSMutableData data];
        NSURLSessionConfiguration *configuration              = [NSURLSessionConfiguration defaultSessionConfiguration];
        //此处不能用主线程,不然会导致卡顿,并且如果多个请求会导致请求延迟
        self.sessionDelegateQueue                             = [[NSOperationQueue alloc] init];
        self.sessionDelegateQueue.maxConcurrentOperationCount = 1;
        self.sessionDelegateQueue.name                        = @"com.hujiang.wedjat.session.queue";
        NSURLSession *session                                 = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:self.sessionDelegateQueue];
    //此处request的HTTPBody为空,但是HTTPBodyStream有值,可以猜测当HTTPBody为空时候,会去找HTTPBodyStream里面的值,并且HTTPBodyStream的流本身也是HTTPBody转换过来的
        self.dataTask                                         = [session dataTaskWithRequest:self.request];
        [self.dataTask resume];
    
        self.startDateString                             = [[NSDate date] timeIntervalSince1970];
    
        NSLog(@"%s ---  %@ --- %f -- %@-- %@ -- %@ -- %f",__func__,mutableRequest.URL.absoluteString,mutableRequest.timeoutInterval,mutableRequest.HTTPBody,dic,error,self.startDateString);
    
    }
    
    - (void)stopLoading {
    
        [self.dataTask cancel];
        self.dataTask                    = nil;
        NSString *endDateString          = [BYDateTool stringToDate:[NSDate date] andFormatStr:@"yyyy-MM-dd HH:mm:ss"];
        NSString *mimeType               = self.response.MIMEType;
    }
    
    #pragma mark - NSURLSessionTaskDelegate
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        if (!error) {
            [self.client URLProtocolDidFinishLoading:self];
        } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
        } else {
            [self.client URLProtocol:self didFailWithError:error];
        }
        self.dataTask = nil;
    }
    
    #pragma mark - NSURLSessionDataDelegate
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
        didReceiveData:(NSData *)data {
        [self.client URLProtocol:self didLoadData:data];
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
        completionHandler(NSURLSessionResponseAllow);
        self.response = response;
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
        if (response != nil){
            self.response = response;
            [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
        }
    }
    
    /*
    如果实现这个代理方法,就可以通过该回调的 NSURLSessionTaskMetrics 类型参数获取到采集的网络指标,实现对网络请求中 DNS 查询/TCP 建立连接/TLS 握手/请求响应等各环节时间的统计。
    
     */
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)){
    
        NSTimeInterval startTime = [metrics.taskInterval.startDate timeIntervalSince1970];
        NSTimeInterval endTime = [metrics.taskInterval.endDate timeIntervalSince1970];
        NSTimeInterval duration = metrics.taskInterval.duration;;
    
        NSLog(@"%s :  %f  ---   %f   ----   %f   %f",__func__,startTime,endTime,duration,startTime - endTime);
    
        NSArray *arr = metrics.transactionMetrics;
        for (int i = 0; i < arr.count; i++) {
    
            NSURLSessionTaskTransactionMetrics *metrics = arr[i];
            NSLog(@"\n %@---\n网络协议  %@   \n---是否使用代理进行网络连接:  %d \n---网络加载类型: %ld --- %f ",
                  metrics.request.URL.absoluteURL,
                  metrics.networkProtocolName,
                  metrics.isProxyConnection,
                  (long)metrics.resourceFetchType,
                  [metrics.fetchStartDate timeIntervalSince1970]);
        }
    }
    

    这幅图示意了一次 HTTP 请求在各环节分别做了哪些工作


    image.png
    NSURLSessionTaskMetrics对象立马封装了 session task 的指标,每个 NSURLSessionTaskMetrics 对象有 taskInterval 和 redirectCount 属性,还有在执行任务时产生的每个请求/响应事务中收集的指标。
     然后我们看下立马的属性:
     transactionMetrics:数组里面对象是NSURLSessionTaskTransactionMetrics,包含了在执行任务时产生的每个请求/响应事务中收集的指标。
     taskInterval:任务从创建到完成花费的总时间,任务的创建时间是任务被实例化时的时间;任务完成时间是任务的内部状态将要变为完成的时间
     redirectCount:记录了被重定向的次数
     ###############################
     NSURLSessionTaskTransactionMetrics对象封装了任务执行时收集的性能指标,包括了 request 和 response 属性,对应 HTTP 的请求和响应,还包括了从 fetchStartDate 开始,到 responseEndDate 结束之间的指标,当然还有 networkProtocolName 和 resourceFetchType 属
     再看一下里面的属性:
     request:表示了网络请求对象
     response:表示了网络响应对象,如果网络出错或没有响应时,response 为 nil
     networkProtocolName:获取资源时使用的网络协议,由 ALPN 协商后标识的协议,比如 h2, http/1.1, spdy/3.1
     isProxyConnection:是否使用代理进行网络连接
     isReusedConnection:是否复用已有连接
     resourceFetchType:NSURLSessionTaskMetricsResourceFetchType 枚举类型,标识资源是通过网络加载,服务器推送还是本地缓存获取的
     对于下面所有 NSDate 类型指标,如果任务没有完成,所有相应的 EndDate 指标都将为 nil。例如,如果 DNS 解析超时、失败或者客户端在解析成功之前取消,domainLookupStartDate 会有对应的数据,然而 domainLookupEndDate 以及在它之后的所有指标都为 nil
     如果是复用已有的连接或者从本地缓存中获取资源,下面的指标都会被赋值为 nil:
     domainLookupStartDate
     domainLookupEndDate
     connectStartDate
     connectEndDate
     secureConnectionStartDate
     secureConnectionEndDate
    
     fetchStartDate:客户端开始请求的时间,无论资源是从服务器还是本地缓存中获取
     domainLookupStartDate:DNS 解析开始时间,Domain -> IP 地址
     domainLookupEndDate:DNS 解析完成时间,客户端已经获取到域名对应的 IP 地址
     connectStartDate:客户端与服务器开始建立 TCP 连接的时间
     secureConnectionStartDate:HTTPS 的 TLS 握手开始时间
     secureConnectionEndDate:HTTPS 的 TLS 握手结束时间
     connectEndDate:客户端与服务器建立 TCP 连接完成时间,包括 TLS 握手时间
     requestStartDate:开始传输 HTTP 请求的 header 第一个字节的时间
     requestEndDate:HTTP 请求最后一个字节传输完成的时间
     responseStartDate:客户端从服务器接收到响应的第一个字节的时间
     responseEndDate:客户端从服务器接收到最后一个字节的时间
    

    相关文章

      网友评论

        本文标题:iOS-利用NSURLProtocol进行网络监控

        本文链接:https://www.haomeiwen.com/subject/cukloctx.html