// // QNHttpRequest+SingleRequestRetry.m // QiniuSDK // // Created by yangsen on 2020/4/29. // Copyright © 2020 Qiniu. All rights reserved. // #import "QNDefine.h" #import "QNAsyncRun.h" #import "QNVersion.h" #import "QNUtils.h" #import "QNLogUtil.h" #import "QNHttpSingleRequest.h" #import "QNConfiguration.h" #import "QNUploadOption.h" #import "QNUpToken.h" #import "QNResponseInfo.h" #import "QNNetworkStatusManager.h" #import "QNRequestClient.h" #import "QNUploadRequestState.h" #import "QNConnectChecker.h" #import "QNDnsPrefetch.h" #import "QNReportItem.h" #import "QNCFHttpClient.h" #import "QNUploadSystemClient.h" #import "NSURLRequest+QNRequest.h" @interface QNHttpSingleRequest() @property(nonatomic, assign)int currentRetryTime; @property(nonatomic, strong)QNConfiguration *config; @property(nonatomic, strong)QNUploadOption *uploadOption; @property(nonatomic, strong)QNUpToken *token; @property(nonatomic, strong)QNUploadRequestInfo *requestInfo; @property(nonatomic, strong)QNUploadRequestState *requestState; @property(nonatomic, strong)NSMutableArray *requestMetricsList; @property(nonatomic, strong)id client; @end @implementation QNHttpSingleRequest - (instancetype)initWithConfig:(QNConfiguration *)config uploadOption:(QNUploadOption *)uploadOption token:(QNUpToken *)token requestInfo:(QNUploadRequestInfo *)requestInfo requestState:(QNUploadRequestState *)requestState{ if (self = [super init]) { _config = config; _uploadOption = uploadOption; _token = token; _requestInfo = requestInfo; _requestState = requestState; _currentRetryTime = 0; } return self; } - (void)request:(NSURLRequest *)request server:(id )server shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress complete:(QNSingleRequestCompleteHandler)complete{ _currentRetryTime = 0; _requestMetricsList = [NSMutableArray array]; [self retryRequest:request server:server shouldRetry:shouldRetry progress:progress complete:complete]; } - (void)retryRequest:(NSURLRequest *)request server:(id )server shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress complete:(QNSingleRequestCompleteHandler)complete{ if (kQNIsHttp3(server.httpVersion)) { self.client = [[QNUploadSystemClient alloc] init]; } else { if ([self shouldUseCFClient:request server:server]) { self.client = [[QNCFHttpClient alloc] init]; } else { self.client = [[QNUploadSystemClient alloc] init]; } } kQNWeakSelf; BOOL (^checkCancelHandler)(void) = ^{ kQNStrongSelf; BOOL isCancelled = self.requestState.isUserCancel; if (!isCancelled && self.uploadOption.cancellationSignal) { isCancelled = self.uploadOption.cancellationSignal(); } return isCancelled; }; QNLogInfo(@"key:%@ retry:%d url:%@", self.requestInfo.key, self.currentRetryTime, request.URL); [self.client request:request server:server connectionProxy:self.config.proxy progress:^(long long totalBytesWritten, long long totalBytesExpectedToWrite) { kQNStrongSelf; if (progress) { progress(totalBytesWritten, totalBytesExpectedToWrite); } if (checkCancelHandler()) { self.requestState.isUserCancel = YES; [self.client cancel]; } } complete:^(NSURLResponse *response, QNUploadSingleRequestMetrics *metrics, NSData * responseData, NSError * error) { kQNStrongSelf; if (metrics) { [self.requestMetricsList addObject:metrics]; } QNResponseInfo *responseInfo = nil; if (checkCancelHandler()) { responseInfo = [QNResponseInfo cancelResponse]; [self reportRequest:responseInfo server:server requestMetrics:metrics]; [self complete:responseInfo server:server response:nil requestMetrics:metrics complete:complete]; return; } NSDictionary *responseDic = nil; if (responseData) { responseDic = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:nil]; } responseInfo = [[QNResponseInfo alloc] initWithResponseInfoHost:request.qn_domain response:(NSHTTPURLResponse *)response body:responseData error:error]; BOOL isSafeDnsSource = kQNIsDnsSourceCustom(server.source) || kQNIsDnsSourceDoh(server.source) || kQNIsDnsSourceDnsPod(server.source); BOOL hijacked = responseInfo.isNotQiniu && !isSafeDnsSource; if (hijacked) { metrics.hijacked = kQNMetricsRequestHijacked; NSError *err = nil; metrics.syncDnsSource = [kQNDnsPrefetch prefetchHostBySafeDns:server.host error:&err]; metrics.syncDnsError = err; } if (!hijacked && [self shouldCheckConnect:responseInfo]) { // 网络状态检测 QNUploadSingleRequestMetrics *connectCheckMetrics = [QNConnectChecker check]; metrics.connectCheckMetrics = connectCheckMetrics; if (![QNConnectChecker isConnected:connectCheckMetrics]) { NSString *message = [NSString stringWithFormat:@"check origin statusCode:%d error:%@", responseInfo.statusCode, responseInfo.error]; responseInfo = [QNResponseInfo errorResponseInfo:NSURLErrorNotConnectedToInternet errorDesc:message]; } else if (!isSafeDnsSource) { metrics.hijacked = kQNMetricsRequestMaybeHijacked; NSError *err = nil; [kQNDnsPrefetch prefetchHostBySafeDns:server.host error:&err]; metrics.syncDnsError = err; } } [self reportRequest:responseInfo server:server requestMetrics:metrics]; QNLogInfo(@"key:%@ response:%@", self.requestInfo.key, responseInfo); if (shouldRetry(responseInfo, responseDic) && self.currentRetryTime < self.config.retryMax && responseInfo.couldHostRetry) { self.currentRetryTime += 1; QNAsyncRunAfter(self.config.retryInterval, kQNBackgroundQueue, ^{ [self retryRequest:request server:server shouldRetry:shouldRetry progress:progress complete:complete]; }); } else { [self complete:responseInfo server:server response:responseDic requestMetrics:metrics complete:complete]; } }]; } - (BOOL)shouldCheckConnect:(QNResponseInfo *)responseInfo { if (!kQNGlobalConfiguration.connectCheckEnable || [kQNGlobalConfiguration.connectCheckURLStrings count] == 0) { return NO; } return responseInfo.statusCode == kQNNetworkError || responseInfo.statusCode == kQNUnexpectedSysCallError || // CF 内部部分错误码 归结到了调用错误 responseInfo.statusCode == NSURLErrorTimedOut /* NSURLErrorTimedOut */ || responseInfo.statusCode == -1003 /* NSURLErrorCannotFindHost */ || responseInfo.statusCode == -1004 /* NSURLErrorCannotConnectToHost */ || responseInfo.statusCode == -1005 /* NSURLErrorNetworkConnectionLost */ || responseInfo.statusCode == -1006 /* NSURLErrorDNSLookupFailed */ || responseInfo.statusCode == -1009 /* NSURLErrorNotConnectedToInternet */ || responseInfo.statusCode == -1200 /* NSURLErrorSecureConnectionFailed */ || responseInfo.statusCode == -1204 /* NSURLErrorServerCertificateNotYetValid */ || responseInfo.statusCode == -1205 /* NSURLErrorClientCertificateRejected */; } - (void)complete:(QNResponseInfo *)responseInfo server:(id)server response:(NSDictionary *)response requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics complete:(QNSingleRequestCompleteHandler)complete { [self updateHostNetworkStatus:responseInfo server:server requestMetrics:requestMetrics]; if (complete) { complete(responseInfo, [self.requestMetricsList copy], response); } } - (BOOL)shouldUseCFClient:(NSURLRequest *)request server:(id )server { if (request.qn_isHttps && server.host.length > 0 && server.ip.length > 0) { return YES; } else { return NO; } } //MARK:-- 统计网络状态 - (void)updateHostNetworkStatus:(QNResponseInfo *)responseInfo server:(id )server requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics{ long long bytes = requestMetrics.bytesSend.longLongValue; if (requestMetrics.startDate && requestMetrics.endDate && bytes >= 1024 * 1024) { double duration = [requestMetrics.endDate timeIntervalSinceDate:requestMetrics.startDate] * 1000; NSNumber *speed = [QNUtils calculateSpeed:bytes totalTime:duration]; if (speed) { NSString *type = [QNNetworkStatusManager getNetworkStatusType:server.host ip:server.ip]; [kQNNetworkStatusManager updateNetworkStatus:type speed:(int)(speed.longValue / 1000)]; } } } //MARK:-- 统计quality日志 - (void)reportRequest:(QNResponseInfo *)info server:(id )server requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics { if (! [self.requestInfo shouldReportRequestLog]) { return; } QNUploadSingleRequestMetrics *requestMetricsP = requestMetrics ?: [QNUploadSingleRequestMetrics emptyMetrics]; NSInteger currentTimestamp = [QNUtils currentTimestamp]; QNReportItem *item = [QNReportItem item]; [item setReportValue:QNReportLogTypeRequest forKey:QNReportRequestKeyLogType]; [item setReportValue:@(currentTimestamp/1000) forKey:QNReportRequestKeyUpTime]; [item setReportValue:info.requestReportStatusCode forKey:QNReportRequestKeyStatusCode]; [item setReportValue:info.reqId forKey:QNReportRequestKeyRequestId]; [item setReportValue:requestMetricsP.request.qn_domain forKey:QNReportRequestKeyHost]; [item setReportValue:requestMetricsP.remoteAddress forKey:QNReportRequestKeyRemoteIp]; [item setReportValue:requestMetricsP.remotePort forKey:QNReportRequestKeyPort]; [item setReportValue:self.requestInfo.bucket forKey:QNReportRequestKeyTargetBucket]; [item setReportValue:self.requestInfo.key forKey:QNReportRequestKeyTargetKey]; [item setReportValue:requestMetricsP.totalElapsedTime forKey:QNReportRequestKeyTotalElapsedTime]; [item setReportValue:requestMetricsP.totalDnsTime forKey:QNReportRequestKeyDnsElapsedTime]; [item setReportValue:requestMetricsP.totalConnectTime forKey:QNReportRequestKeyConnectElapsedTime]; [item setReportValue:requestMetricsP.totalSecureConnectTime forKey:QNReportRequestKeyTLSConnectElapsedTime]; [item setReportValue:requestMetricsP.totalRequestTime forKey:QNReportRequestKeyRequestElapsedTime]; [item setReportValue:requestMetricsP.totalWaitTime forKey:QNReportRequestKeyWaitElapsedTime]; [item setReportValue:requestMetricsP.totalWaitTime forKey:QNReportRequestKeyResponseElapsedTime]; [item setReportValue:requestMetricsP.totalResponseTime forKey:QNReportRequestKeyResponseElapsedTime]; [item setReportValue:self.requestInfo.fileOffset forKey:QNReportRequestKeyFileOffset]; [item setReportValue:requestMetricsP.bytesSend forKey:QNReportRequestKeyBytesSent]; [item setReportValue:requestMetricsP.totalBytes forKey:QNReportRequestKeyBytesTotal]; [item setReportValue:@([QNUtils getCurrentProcessID]) forKey:QNReportRequestKeyPid]; [item setReportValue:@([QNUtils getCurrentThreadID]) forKey:QNReportRequestKeyTid]; [item setReportValue:self.requestInfo.targetRegionId forKey:QNReportRequestKeyTargetRegionId]; [item setReportValue:self.requestInfo.currentRegionId forKey:QNReportRequestKeyCurrentRegionId]; [item setReportValue:info.requestReportErrorType forKey:QNReportRequestKeyErrorType]; NSString *errorDesc = info.requestReportErrorType ? info.message : nil; [item setReportValue:errorDesc forKey:QNReportRequestKeyErrorDescription]; [item setReportValue:self.requestInfo.requestType forKey:QNReportRequestKeyUpType]; [item setReportValue:[QNUtils systemName] forKey:QNReportRequestKeyOsName]; [item setReportValue:[QNUtils systemVersion] forKey:QNReportRequestKeyOsVersion]; [item setReportValue:[QNUtils sdkLanguage] forKey:QNReportRequestKeySDKName]; [item setReportValue:[QNUtils sdkVersion] forKey:QNReportRequestKeySDKVersion]; [item setReportValue:@([QNUtils currentTimestamp]) forKey:QNReportRequestKeyClientTime]; [item setReportValue:[QNUtils getCurrentNetworkType] forKey:QNReportRequestKeyNetworkType]; [item setReportValue:[QNUtils getCurrentSignalStrength] forKey:QNReportRequestKeySignalStrength]; [item setReportValue:server.source forKey:QNReportRequestKeyPrefetchedDnsSource]; if (server.ipPrefetchedTime) { NSInteger prefetchTime = currentTimestamp/1000 - [server.ipPrefetchedTime integerValue]; [item setReportValue:@(prefetchTime) forKey:QNReportRequestKeyPrefetchedBefore]; } [item setReportValue:kQNDnsPrefetch.lastPrefetchedErrorMessage forKey:QNReportRequestKeyPrefetchedErrorMessage]; [item setReportValue:requestMetricsP.httpVersion forKey:QNReportRequestKeyHttpVersion]; if (!kQNGlobalConfiguration.connectCheckEnable) { [item setReportValue:@"disable" forKey:QNReportRequestKeyNetworkMeasuring]; } else if (requestMetricsP.connectCheckMetrics) { QNUploadSingleRequestMetrics *metrics = requestMetricsP.connectCheckMetrics; NSString *connectCheckDuration = [NSString stringWithFormat:@"%.2lf", [metrics.totalElapsedTime doubleValue]]; NSString *connectCheckStatusCode = @""; if (metrics.response) { connectCheckStatusCode = [NSString stringWithFormat:@"%ld", (long)((NSHTTPURLResponse *)metrics.response).statusCode]; } else if (metrics.error) { connectCheckStatusCode = [NSString stringWithFormat:@"%ld", (long)metrics.error.code]; } NSString *networkMeasuring = [NSString stringWithFormat:@"duration:%@ status_code:%@",connectCheckDuration, connectCheckStatusCode]; [item setReportValue:networkMeasuring forKey:QNReportRequestKeyNetworkMeasuring]; } // 劫持标记 [item setReportValue:requestMetricsP.hijacked forKey:QNReportRequestKeyHijacking]; [item setReportValue:requestMetricsP.syncDnsSource forKey:QNReportRequestKeyDnsSource]; [item setReportValue:[requestMetricsP.syncDnsError description] forKey:QNReportRequestKeyDnsErrorMessage]; // 成功统计速度 if (info.isOK) { [item setReportValue:requestMetricsP.perceptiveSpeed forKey:QNReportRequestKeyPerceptiveSpeed]; } [item setReportValue:self.client.clientId forKey:QNReportRequestKeyHttpClient]; [kQNReporter reportItem:item token:self.token.token]; } @end