QNHttpSingleRequest.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. //
  2. // QNHttpRequest+SingleRequestRetry.m
  3. // QiniuSDK
  4. //
  5. // Created by yangsen on 2020/4/29.
  6. // Copyright © 2020 Qiniu. All rights reserved.
  7. //
  8. #import "QNDefine.h"
  9. #import "QNAsyncRun.h"
  10. #import "QNVersion.h"
  11. #import "QNUtils.h"
  12. #import "QNLogUtil.h"
  13. #import "QNHttpSingleRequest.h"
  14. #import "QNConfiguration.h"
  15. #import "QNUploadOption.h"
  16. #import "QNUpToken.h"
  17. #import "QNResponseInfo.h"
  18. #import "QNNetworkStatusManager.h"
  19. #import "QNRequestClient.h"
  20. #import "QNUploadRequestState.h"
  21. #import "QNConnectChecker.h"
  22. #import "QNDnsPrefetch.h"
  23. #import "QNReportItem.h"
  24. #import "QNCFHttpClient.h"
  25. #import "QNUploadSystemClient.h"
  26. #import "NSURLRequest+QNRequest.h"
  27. @interface QNHttpSingleRequest()
  28. @property(nonatomic, assign)int currentRetryTime;
  29. @property(nonatomic, strong)QNConfiguration *config;
  30. @property(nonatomic, strong)QNUploadOption *uploadOption;
  31. @property(nonatomic, strong)QNUpToken *token;
  32. @property(nonatomic, strong)QNUploadRequestInfo *requestInfo;
  33. @property(nonatomic, strong)QNUploadRequestState *requestState;
  34. @property(nonatomic, strong)NSMutableArray <QNUploadSingleRequestMetrics *> *requestMetricsList;
  35. @property(nonatomic, strong)id <QNRequestClient> client;
  36. @end
  37. @implementation QNHttpSingleRequest
  38. - (instancetype)initWithConfig:(QNConfiguration *)config
  39. uploadOption:(QNUploadOption *)uploadOption
  40. token:(QNUpToken *)token
  41. requestInfo:(QNUploadRequestInfo *)requestInfo
  42. requestState:(QNUploadRequestState *)requestState{
  43. if (self = [super init]) {
  44. _config = config;
  45. _uploadOption = uploadOption;
  46. _token = token;
  47. _requestInfo = requestInfo;
  48. _requestState = requestState;
  49. _currentRetryTime = 0;
  50. }
  51. return self;
  52. }
  53. - (void)request:(NSURLRequest *)request
  54. server:(id <QNUploadServer>)server
  55. shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
  56. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  57. complete:(QNSingleRequestCompleteHandler)complete{
  58. _currentRetryTime = 0;
  59. _requestMetricsList = [NSMutableArray array];
  60. [self retryRequest:request server:server shouldRetry:shouldRetry progress:progress complete:complete];
  61. }
  62. - (void)retryRequest:(NSURLRequest *)request
  63. server:(id <QNUploadServer>)server
  64. shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
  65. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  66. complete:(QNSingleRequestCompleteHandler)complete{
  67. if (kQNIsHttp3(server.httpVersion)) {
  68. self.client = [[QNUploadSystemClient alloc] init];
  69. } else {
  70. if ([self shouldUseCFClient:request server:server]) {
  71. self.client = [[QNCFHttpClient alloc] init];
  72. } else {
  73. self.client = [[QNUploadSystemClient alloc] init];
  74. }
  75. }
  76. kQNWeakSelf;
  77. BOOL (^checkCancelHandler)(void) = ^{
  78. kQNStrongSelf;
  79. BOOL isCancelled = self.requestState.isUserCancel;
  80. if (!isCancelled && self.uploadOption.cancellationSignal) {
  81. isCancelled = self.uploadOption.cancellationSignal();
  82. }
  83. return isCancelled;
  84. };
  85. QNLogInfo(@"key:%@ retry:%d url:%@", self.requestInfo.key, self.currentRetryTime, request.URL);
  86. [self.client request:request server:server connectionProxy:self.config.proxy progress:^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
  87. kQNStrongSelf;
  88. if (progress) {
  89. progress(totalBytesWritten, totalBytesExpectedToWrite);
  90. }
  91. if (checkCancelHandler()) {
  92. self.requestState.isUserCancel = YES;
  93. [self.client cancel];
  94. }
  95. } complete:^(NSURLResponse *response, QNUploadSingleRequestMetrics *metrics, NSData * responseData, NSError * error) {
  96. kQNStrongSelf;
  97. if (metrics) {
  98. [self.requestMetricsList addObject:metrics];
  99. }
  100. QNResponseInfo *responseInfo = nil;
  101. if (checkCancelHandler()) {
  102. responseInfo = [QNResponseInfo cancelResponse];
  103. [self reportRequest:responseInfo server:server requestMetrics:metrics];
  104. [self complete:responseInfo server:server response:nil requestMetrics:metrics complete:complete];
  105. return;
  106. }
  107. NSDictionary *responseDic = nil;
  108. if (responseData) {
  109. responseDic = [NSJSONSerialization JSONObjectWithData:responseData
  110. options:NSJSONReadingMutableLeaves
  111. error:nil];
  112. }
  113. responseInfo = [[QNResponseInfo alloc] initWithResponseInfoHost:request.qn_domain
  114. response:(NSHTTPURLResponse *)response
  115. body:responseData
  116. error:error];
  117. BOOL isSafeDnsSource = kQNIsDnsSourceCustom(server.source) || kQNIsDnsSourceDoh(server.source) || kQNIsDnsSourceDnsPod(server.source);
  118. BOOL hijacked = responseInfo.isNotQiniu && !isSafeDnsSource;
  119. if (hijacked) {
  120. metrics.hijacked = kQNMetricsRequestHijacked;
  121. NSError *err = nil;
  122. metrics.syncDnsSource = [kQNDnsPrefetch prefetchHostBySafeDns:server.host error:&err];
  123. metrics.syncDnsError = err;
  124. }
  125. if (!hijacked && [self shouldCheckConnect:responseInfo]) {
  126. // 网络状态检测
  127. QNUploadSingleRequestMetrics *connectCheckMetrics = [QNConnectChecker check];
  128. metrics.connectCheckMetrics = connectCheckMetrics;
  129. if (![QNConnectChecker isConnected:connectCheckMetrics]) {
  130. NSString *message = [NSString stringWithFormat:@"check origin statusCode:%d error:%@", responseInfo.statusCode, responseInfo.error];
  131. responseInfo = [QNResponseInfo errorResponseInfo:NSURLErrorNotConnectedToInternet errorDesc:message];
  132. } else if (!isSafeDnsSource) {
  133. metrics.hijacked = kQNMetricsRequestMaybeHijacked;
  134. NSError *err = nil;
  135. [kQNDnsPrefetch prefetchHostBySafeDns:server.host error:&err];
  136. metrics.syncDnsError = err;
  137. }
  138. }
  139. [self reportRequest:responseInfo server:server requestMetrics:metrics];
  140. QNLogInfo(@"key:%@ response:%@", self.requestInfo.key, responseInfo);
  141. if (shouldRetry(responseInfo, responseDic)
  142. && self.currentRetryTime < self.config.retryMax
  143. && responseInfo.couldHostRetry) {
  144. self.currentRetryTime += 1;
  145. QNAsyncRunAfter(self.config.retryInterval, kQNBackgroundQueue, ^{
  146. [self retryRequest:request server:server shouldRetry:shouldRetry progress:progress complete:complete];
  147. });
  148. } else {
  149. [self complete:responseInfo server:server response:responseDic requestMetrics:metrics complete:complete];
  150. }
  151. }];
  152. }
  153. - (BOOL)shouldCheckConnect:(QNResponseInfo *)responseInfo {
  154. if (!kQNGlobalConfiguration.connectCheckEnable || [kQNGlobalConfiguration.connectCheckURLStrings count] == 0) {
  155. return NO;
  156. }
  157. return responseInfo.statusCode == kQNNetworkError ||
  158. responseInfo.statusCode == kQNUnexpectedSysCallError || // CF 内部部分错误码 归结到了调用错误
  159. responseInfo.statusCode == NSURLErrorTimedOut /* NSURLErrorTimedOut */ ||
  160. responseInfo.statusCode == -1003 /* NSURLErrorCannotFindHost */ ||
  161. responseInfo.statusCode == -1004 /* NSURLErrorCannotConnectToHost */ ||
  162. responseInfo.statusCode == -1005 /* NSURLErrorNetworkConnectionLost */ ||
  163. responseInfo.statusCode == -1006 /* NSURLErrorDNSLookupFailed */ ||
  164. responseInfo.statusCode == -1009 /* NSURLErrorNotConnectedToInternet */ ||
  165. responseInfo.statusCode == -1200 /* NSURLErrorSecureConnectionFailed */ ||
  166. responseInfo.statusCode == -1204 /* NSURLErrorServerCertificateNotYetValid */ ||
  167. responseInfo.statusCode == -1205 /* NSURLErrorClientCertificateRejected */;
  168. }
  169. - (void)complete:(QNResponseInfo *)responseInfo
  170. server:(id<QNUploadServer>)server
  171. response:(NSDictionary *)response
  172. requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics
  173. complete:(QNSingleRequestCompleteHandler)complete {
  174. [self updateHostNetworkStatus:responseInfo server:server requestMetrics:requestMetrics];
  175. if (complete) {
  176. complete(responseInfo, [self.requestMetricsList copy], response);
  177. }
  178. }
  179. - (BOOL)shouldUseCFClient:(NSURLRequest *)request server:(id <QNUploadServer>)server {
  180. if (request.qn_isHttps && server.host.length > 0 && server.ip.length > 0) {
  181. return YES;
  182. } else {
  183. return NO;
  184. }
  185. }
  186. //MARK:-- 统计网络状态
  187. - (void)updateHostNetworkStatus:(QNResponseInfo *)responseInfo
  188. server:(id <QNUploadServer>)server
  189. requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics{
  190. long long bytes = requestMetrics.bytesSend.longLongValue;
  191. if (requestMetrics.startDate && requestMetrics.endDate && bytes >= 1024 * 1024) {
  192. double duration = [requestMetrics.endDate timeIntervalSinceDate:requestMetrics.startDate] * 1000;
  193. NSNumber *speed = [QNUtils calculateSpeed:bytes totalTime:duration];
  194. if (speed) {
  195. NSString *type = [QNNetworkStatusManager getNetworkStatusType:server.host ip:server.ip];
  196. [kQNNetworkStatusManager updateNetworkStatus:type speed:(int)(speed.longValue / 1000)];
  197. }
  198. }
  199. }
  200. //MARK:-- 统计quality日志
  201. - (void)reportRequest:(QNResponseInfo *)info
  202. server:(id <QNUploadServer>)server
  203. requestMetrics:(QNUploadSingleRequestMetrics *)requestMetrics {
  204. if (! [self.requestInfo shouldReportRequestLog]) {
  205. return;
  206. }
  207. QNUploadSingleRequestMetrics *requestMetricsP = requestMetrics ?: [QNUploadSingleRequestMetrics emptyMetrics];
  208. NSInteger currentTimestamp = [QNUtils currentTimestamp];
  209. QNReportItem *item = [QNReportItem item];
  210. [item setReportValue:QNReportLogTypeRequest forKey:QNReportRequestKeyLogType];
  211. [item setReportValue:@(currentTimestamp/1000) forKey:QNReportRequestKeyUpTime];
  212. [item setReportValue:info.requestReportStatusCode forKey:QNReportRequestKeyStatusCode];
  213. [item setReportValue:info.reqId forKey:QNReportRequestKeyRequestId];
  214. [item setReportValue:requestMetricsP.request.qn_domain forKey:QNReportRequestKeyHost];
  215. [item setReportValue:requestMetricsP.remoteAddress forKey:QNReportRequestKeyRemoteIp];
  216. [item setReportValue:requestMetricsP.remotePort forKey:QNReportRequestKeyPort];
  217. [item setReportValue:self.requestInfo.bucket forKey:QNReportRequestKeyTargetBucket];
  218. [item setReportValue:self.requestInfo.key forKey:QNReportRequestKeyTargetKey];
  219. [item setReportValue:requestMetricsP.totalElapsedTime forKey:QNReportRequestKeyTotalElapsedTime];
  220. [item setReportValue:requestMetricsP.totalDnsTime forKey:QNReportRequestKeyDnsElapsedTime];
  221. [item setReportValue:requestMetricsP.totalConnectTime forKey:QNReportRequestKeyConnectElapsedTime];
  222. [item setReportValue:requestMetricsP.totalSecureConnectTime forKey:QNReportRequestKeyTLSConnectElapsedTime];
  223. [item setReportValue:requestMetricsP.totalRequestTime forKey:QNReportRequestKeyRequestElapsedTime];
  224. [item setReportValue:requestMetricsP.totalWaitTime forKey:QNReportRequestKeyWaitElapsedTime];
  225. [item setReportValue:requestMetricsP.totalWaitTime forKey:QNReportRequestKeyResponseElapsedTime];
  226. [item setReportValue:requestMetricsP.totalResponseTime forKey:QNReportRequestKeyResponseElapsedTime];
  227. [item setReportValue:self.requestInfo.fileOffset forKey:QNReportRequestKeyFileOffset];
  228. [item setReportValue:requestMetricsP.bytesSend forKey:QNReportRequestKeyBytesSent];
  229. [item setReportValue:requestMetricsP.totalBytes forKey:QNReportRequestKeyBytesTotal];
  230. [item setReportValue:@([QNUtils getCurrentProcessID]) forKey:QNReportRequestKeyPid];
  231. [item setReportValue:@([QNUtils getCurrentThreadID]) forKey:QNReportRequestKeyTid];
  232. [item setReportValue:self.requestInfo.targetRegionId forKey:QNReportRequestKeyTargetRegionId];
  233. [item setReportValue:self.requestInfo.currentRegionId forKey:QNReportRequestKeyCurrentRegionId];
  234. [item setReportValue:info.requestReportErrorType forKey:QNReportRequestKeyErrorType];
  235. NSString *errorDesc = info.requestReportErrorType ? info.message : nil;
  236. [item setReportValue:errorDesc forKey:QNReportRequestKeyErrorDescription];
  237. [item setReportValue:self.requestInfo.requestType forKey:QNReportRequestKeyUpType];
  238. [item setReportValue:[QNUtils systemName] forKey:QNReportRequestKeyOsName];
  239. [item setReportValue:[QNUtils systemVersion] forKey:QNReportRequestKeyOsVersion];
  240. [item setReportValue:[QNUtils sdkLanguage] forKey:QNReportRequestKeySDKName];
  241. [item setReportValue:[QNUtils sdkVersion] forKey:QNReportRequestKeySDKVersion];
  242. [item setReportValue:@([QNUtils currentTimestamp]) forKey:QNReportRequestKeyClientTime];
  243. [item setReportValue:[QNUtils getCurrentNetworkType] forKey:QNReportRequestKeyNetworkType];
  244. [item setReportValue:[QNUtils getCurrentSignalStrength] forKey:QNReportRequestKeySignalStrength];
  245. [item setReportValue:server.source forKey:QNReportRequestKeyPrefetchedDnsSource];
  246. if (server.ipPrefetchedTime) {
  247. NSInteger prefetchTime = currentTimestamp/1000 - [server.ipPrefetchedTime integerValue];
  248. [item setReportValue:@(prefetchTime) forKey:QNReportRequestKeyPrefetchedBefore];
  249. }
  250. [item setReportValue:kQNDnsPrefetch.lastPrefetchedErrorMessage forKey:QNReportRequestKeyPrefetchedErrorMessage];
  251. [item setReportValue:requestMetricsP.httpVersion forKey:QNReportRequestKeyHttpVersion];
  252. if (!kQNGlobalConfiguration.connectCheckEnable) {
  253. [item setReportValue:@"disable" forKey:QNReportRequestKeyNetworkMeasuring];
  254. } else if (requestMetricsP.connectCheckMetrics) {
  255. QNUploadSingleRequestMetrics *metrics = requestMetricsP.connectCheckMetrics;
  256. NSString *connectCheckDuration = [NSString stringWithFormat:@"%.2lf", [metrics.totalElapsedTime doubleValue]];
  257. NSString *connectCheckStatusCode = @"";
  258. if (metrics.response) {
  259. connectCheckStatusCode = [NSString stringWithFormat:@"%ld", (long)((NSHTTPURLResponse *)metrics.response).statusCode];
  260. } else if (metrics.error) {
  261. connectCheckStatusCode = [NSString stringWithFormat:@"%ld", (long)metrics.error.code];
  262. }
  263. NSString *networkMeasuring = [NSString stringWithFormat:@"duration:%@ status_code:%@",connectCheckDuration, connectCheckStatusCode];
  264. [item setReportValue:networkMeasuring forKey:QNReportRequestKeyNetworkMeasuring];
  265. }
  266. // 劫持标记
  267. [item setReportValue:requestMetricsP.hijacked forKey:QNReportRequestKeyHijacking];
  268. [item setReportValue:requestMetricsP.syncDnsSource forKey:QNReportRequestKeyDnsSource];
  269. [item setReportValue:[requestMetricsP.syncDnsError description] forKey:QNReportRequestKeyDnsErrorMessage];
  270. // 成功统计速度
  271. if (info.isOK) {
  272. [item setReportValue:requestMetricsP.perceptiveSpeed forKey:QNReportRequestKeyPerceptiveSpeed];
  273. }
  274. [item setReportValue:self.client.clientId forKey:QNReportRequestKeyHttpClient];
  275. [kQNReporter reportItem:item token:self.token.token];
  276. }
  277. @end