QNSessionManager.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. //
  2. // QNHttpManager.m
  3. // QiniuSDK
  4. //
  5. // Created by bailong on 14/10/1.
  6. // Copyright (c) 2014年 Qiniu. All rights reserved.
  7. //
  8. #import "QNAsyncRun.h"
  9. #import "QNConfiguration.h"
  10. #import "QNHttpResponseInfo.h"
  11. #import "QNSessionManager.h"
  12. #import "QNUserAgent.h"
  13. #import "QNSystemTool.h"
  14. #import "QNUploadInfoCollector.h"
  15. #import "NSURLRequest+QNRequest.h"
  16. #import "QNURLProtocol.h"
  17. #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
  18. @implementation QNSessionStatistics
  19. - (instancetype)init
  20. {
  21. self = [super init];
  22. if (self) {
  23. _port = QN_IntNotSet;
  24. _totalElapsedTime = QN_IntNotSet;
  25. _dnsElapsedTime = QN_IntNotSet;
  26. _connectElapsedTime = QN_IntNotSet;
  27. _connectElapsedTime = QN_IntNotSet;
  28. _tlsConnectElapsedTime = QN_IntNotSet;
  29. _requestElapsedTime = QN_IntNotSet;
  30. _waitElapsedTime = QN_IntNotSet;
  31. _responseElapsedTime = QN_IntNotSet;
  32. _bytesSent = QN_IntNotSet;
  33. _bytesTotal = QN_IntNotSet;
  34. }
  35. return self;
  36. }
  37. @end
  38. typedef void (^QNSessionComplete)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error, QNSessionStatistics *sessionStatistics);
  39. @interface QNSessionDelegateHandler : NSObject <NSURLSessionDataDelegate>
  40. @property (nonatomic, copy) QNInternalProgressBlock progressBlock;
  41. @property (nonatomic, copy) QNCancelBlock cancelBlock;
  42. @property (nonatomic, copy) QNSessionComplete completeBlock;
  43. @property (nonatomic, strong) NSData *responseData;
  44. @property (nonatomic, strong) QNSessionStatistics *sessionStatistics;
  45. @end
  46. @implementation QNSessionDelegateHandler
  47. - (instancetype)init
  48. {
  49. self = [super init];
  50. if (self) {
  51. _sessionStatistics = [[QNSessionStatistics alloc] init];
  52. }
  53. return self;
  54. }
  55. #pragma mark - NSURLSessionDataDelegate
  56. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
  57. completionHandler(NSURLSessionResponseAllow);
  58. }
  59. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  60. _responseData = data;
  61. }
  62. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
  63. didCompleteWithError:(nullable NSError *)error {
  64. // bytes_sent & bytes_total
  65. _sessionStatistics.bytesSent = task.countOfBytesSent;
  66. _sessionStatistics.bytesTotal = task.countOfBytesExpectedToSend;
  67. self.completeBlock(_responseData, task.response, error, _sessionStatistics);
  68. [session finishTasksAndInvalidate];
  69. }
  70. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(ios(10.0)) {
  71. if (metrics) {
  72. if (metrics.transactionMetrics.count > 0) {
  73. NSURLSessionTaskTransactionMetrics *transactionMetrics = metrics.transactionMetrics[0];
  74. _sessionStatistics = [[QNSessionStatistics alloc] init];
  75. // remote_ip & port
  76. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  77. if (@available(iOS 13.0, *)) {
  78. _sessionStatistics.remoteIp = transactionMetrics.remoteAddress;
  79. _sessionStatistics.port = [transactionMetrics.remotePort unsignedShortValue];
  80. }
  81. #endif
  82. // time
  83. _sessionStatistics.totalElapsedTime = metrics.taskInterval.duration * 1000;
  84. _sessionStatistics.dnsElapsedTime = [self getTimeintervalWithStartDate:transactionMetrics.domainLookupStartDate endDate:transactionMetrics.domainLookupEndDate];
  85. _sessionStatistics.connectElapsedTime =
  86. [self getTimeintervalWithStartDate:transactionMetrics.connectStartDate endDate:transactionMetrics.connectEndDate];
  87. _sessionStatistics.tlsConnectElapsedTime = [self getTimeintervalWithStartDate:transactionMetrics.secureConnectionStartDate endDate:transactionMetrics.secureConnectionEndDate];
  88. _sessionStatistics.requestElapsedTime = [self getTimeintervalWithStartDate:transactionMetrics.requestStartDate endDate:transactionMetrics.requestEndDate];
  89. _sessionStatistics.waitElapsedTime = [self getTimeintervalWithStartDate:transactionMetrics.requestEndDate endDate:transactionMetrics.responseStartDate];
  90. _sessionStatistics.responseElapsedTime = [self getTimeintervalWithStartDate:transactionMetrics.responseStartDate endDate:transactionMetrics.responseEndDate];
  91. // proxy
  92. _sessionStatistics.proxyConnection = transactionMetrics.isProxyConnection;
  93. }
  94. }
  95. }
  96. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
  97. didSendBodyData:(int64_t)bytesSent
  98. totalBytesSent:(int64_t)totalBytesSent
  99. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  100. if (_progressBlock) {
  101. _progressBlock(totalBytesSent, totalBytesExpectedToSend);
  102. }
  103. if (_cancelBlock && _cancelBlock()) {
  104. [task cancel];
  105. }
  106. }
  107. - (uint64_t)getTimeintervalWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate {
  108. if (!startDate || !endDate) return 0;
  109. NSTimeInterval interval = [endDate timeIntervalSinceDate:startDate];
  110. return interval * 1000;
  111. }
  112. @end
  113. @interface QNSessionManager ()
  114. @property UInt32 timeout;
  115. @property (nonatomic, strong) QNUrlConvert converter;
  116. @property (nonatomic, strong) NSDictionary *proxyDict;
  117. @property (nonatomic, strong) NSOperationQueue *delegateQueue;
  118. @property (nonatomic, strong) NSMutableArray *sessionArray;
  119. @property (nonatomic, strong) NSLock *lock;
  120. @end
  121. @implementation QNSessionManager
  122. - (instancetype)initWithProxy:(NSDictionary *)proxyDict
  123. timeout:(UInt32)timeout
  124. urlConverter:(QNUrlConvert)converter {
  125. if (self = [super init]) {
  126. _delegateQueue = [[NSOperationQueue alloc] init];
  127. _timeout = timeout;
  128. _converter = converter;
  129. _sessionArray = [NSMutableArray array];
  130. [QNURLProtocol registerProtocol];
  131. _lock = [[NSLock alloc] init];
  132. }
  133. return self;
  134. }
  135. - (instancetype)init {
  136. return [self initWithProxy:nil timeout:60 urlConverter:nil];
  137. }
  138. - (void)sendRequest:(NSMutableURLRequest *)request
  139. withIdentifier:(NSString *)identifier
  140. withCompleteBlock:(QNCompleteBlock)completeBlock
  141. withProgressBlock:(QNInternalProgressBlock)progressBlock
  142. withCancelBlock:(QNCancelBlock)cancelBlock
  143. withAccess:(NSString *)access {
  144. NSString *domain = request.URL.host;
  145. NSString *u = request.URL.absoluteString;
  146. NSURL *url = request.URL;
  147. if (_converter != nil) {
  148. url = [[NSURL alloc] initWithString:_converter(u)];
  149. request.URL = url;
  150. domain = url.host;
  151. }
  152. request.qn_domain = request.URL.host;
  153. [request setTimeoutInterval:_timeout];
  154. [request setValue:[[QNUserAgent sharedInstance] getUserAgent:access] forHTTPHeaderField:@"User-Agent"];
  155. [request setValue:nil forHTTPHeaderField:@"Accept-Language"];
  156. QNSessionDelegateHandler *delegate = [[QNSessionDelegateHandler alloc] init];
  157. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration qn_sessionConfiguration];
  158. configuration.connectionProxyDictionary = _proxyDict ? _proxyDict : nil;
  159. __block NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:_delegateQueue];
  160. [_sessionArray addObject:@{@"identifier":identifier,@"session":session}];
  161. delegate.cancelBlock = cancelBlock;
  162. delegate.progressBlock = progressBlock ? progressBlock : ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
  163. };
  164. delegate.completeBlock = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error, QNSessionStatistics *sessionStatistics) {
  165. [self finishSession:session];
  166. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  167. QNHttpResponseInfo *info = [QNHttpResponseInfo buildResponseInfoHost:domain response:httpResponse body:data error:error sessionStatistics:sessionStatistics];
  168. completeBlock(info, [info getResponseBody]);
  169. };
  170. NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request];
  171. [uploadTask resume];
  172. }
  173. - (void)multipartPost:(NSString *)url
  174. withData:(NSData *)data
  175. withParams:(NSDictionary *)params
  176. withFileName:(NSString *)key
  177. withMimeType:(NSString *)mime
  178. withIdentifier:(NSString *)identifier
  179. withCompleteBlock:(QNCompleteBlock)completeBlock
  180. withProgressBlock:(QNInternalProgressBlock)progressBlock
  181. withCancelBlock:(QNCancelBlock)cancelBlock
  182. withAccess:(NSString *)access {
  183. NSURL *URL = [[NSURL alloc] initWithString:url];
  184. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
  185. request.HTTPMethod = @"POST";
  186. NSString *boundary = @"werghnvt54wef654rjuhgb56trtg34tweuyrgf";
  187. request.allHTTPHeaderFields = @{
  188. @"Content-Type" : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
  189. };
  190. NSMutableData *postData = [[NSMutableData alloc] init];
  191. for (NSString *paramsKey in params) {
  192. NSString *pair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n", boundary, paramsKey];
  193. [postData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
  194. id value = [params objectForKey:paramsKey];
  195. if ([value isKindOfClass:[NSString class]]) {
  196. [postData appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
  197. } else if ([value isKindOfClass:[NSData class]]) {
  198. [postData appendData:value];
  199. }
  200. [postData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  201. }
  202. NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\nContent-Type:%@\r\n\r\n", boundary, @"file", key, mime];
  203. [postData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
  204. [postData appendData:data];
  205. [postData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  206. request.HTTPBody = postData;
  207. [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)postData.length] forHTTPHeaderField:@"Content-Length"];
  208. [self sendRequest:request withIdentifier:identifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:cancelBlock
  209. withAccess:access];
  210. }
  211. - (void)post:(NSString *)url
  212. withData:(NSData *)data
  213. withParams:(NSDictionary *)params
  214. withHeaders:(NSDictionary *)headers
  215. withIdentifier:(NSString *)identifier
  216. withCompleteBlock:(QNCompleteBlock)completeBlock
  217. withProgressBlock:(QNInternalProgressBlock)progressBlock
  218. withCancelBlock:(QNCancelBlock)cancelBlock
  219. withAccess:(NSString *)access {
  220. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:url]];
  221. if (headers) {
  222. [request setAllHTTPHeaderFields:headers];
  223. }
  224. [request setHTTPMethod:@"POST"];
  225. if (params) {
  226. [request setValuesForKeysWithDictionary:params];
  227. }
  228. [request setHTTPBody:data];
  229. identifier = !identifier ? [[NSUUID UUID] UUIDString] : identifier;
  230. QNAsyncRun(^{
  231. [self sendRequest:request
  232. withIdentifier:identifier
  233. withCompleteBlock:completeBlock
  234. withProgressBlock:progressBlock
  235. withCancelBlock:cancelBlock
  236. withAccess:access];
  237. });
  238. }
  239. - (void)get:(NSString *)url
  240. withHeaders:(NSDictionary *)headers
  241. withCompleteBlock:(QNCompleteBlock)completeBlock {
  242. QNAsyncRun(^{
  243. NSURL *URL = [NSURL URLWithString:url];
  244. NSString *domain = URL.host;
  245. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  246. request.qn_domain = URL.host;
  247. QNSessionDelegateHandler *delegate = [[QNSessionDelegateHandler alloc] init];
  248. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration qn_sessionConfiguration];
  249. __block NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:self.delegateQueue];
  250. NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
  251. delegate.cancelBlock = nil;
  252. delegate.progressBlock = nil;
  253. delegate.completeBlock = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error, QNSessionStatistics *sessionStatistics) {
  254. [self finishSession:session];
  255. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  256. QNHttpResponseInfo *info = [QNHttpResponseInfo buildResponseInfoHost:domain response:httpResponse body:data error:error sessionStatistics:sessionStatistics];
  257. completeBlock(info, [info getResponseBody]);
  258. };
  259. [dataTask resume];
  260. });
  261. }
  262. - (void)finishSession:(NSURLSession *)session {
  263. [_lock lock];
  264. for (int i = 0; i < _sessionArray.count; i++) {
  265. NSDictionary *sessionInfo = _sessionArray[i];
  266. if (sessionInfo[@"session"] == session) {
  267. [session finishTasksAndInvalidate];
  268. [_sessionArray removeObject:sessionInfo];
  269. break;
  270. }
  271. }
  272. [_lock unlock];
  273. }
  274. - (void)invalidateSessionWithIdentifier:(NSString *)identifier {
  275. [_lock lock];
  276. for (int i = 0; i < _sessionArray.count; i++) {
  277. NSDictionary *sessionInfo = _sessionArray[i];
  278. if ([sessionInfo[@"identifier"] isEqualToString:identifier]) {
  279. NSURLSession *session = sessionInfo[@"session"];
  280. [session invalidateAndCancel];
  281. [_sessionArray removeObject:sessionInfo];
  282. break;
  283. }
  284. }
  285. [_lock unlock];
  286. }
  287. @end
  288. #endif