123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- //
- // 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 <QNUploadSingleRequestMetrics *> *requestMetricsList;
- @property(nonatomic, strong)id <QNRequestClient> 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 <QNUploadServer>)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 <QNUploadServer>)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<QNUploadServer>)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 <QNUploadServer>)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 <QNUploadServer>)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 <QNUploadServer>)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
|