QNRequestTransaction.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. //
  2. // QNRequestTransaction.m
  3. // QiniuSDK
  4. //
  5. // Created by yangsen on 2020/4/30.
  6. // Copyright © 2020 Qiniu. All rights reserved.
  7. //
  8. #import "QNRequestTransaction.h"
  9. #import "QNDefine.h"
  10. #import "QNUtils.h"
  11. #import "QNCrc32.h"
  12. #import "NSData+QNMD5.h"
  13. #import "QNUrlSafeBase64.h"
  14. #import "QNUpToken.h"
  15. #import "QNConfiguration.h"
  16. #import "QNUploadOption.h"
  17. #import "QNZoneInfo.h"
  18. #import "QNUserAgent.h"
  19. #import "QNResponseInfo.h"
  20. #import "QNUploadDomainRegion.h"
  21. #import "QNHttpRegionRequest.h"
  22. @interface QNRequestTransaction()
  23. @property(nonatomic, strong)QNConfiguration *config;
  24. @property(nonatomic, strong)QNUploadOption *uploadOption;
  25. @property(nonatomic, copy)NSString *key;
  26. @property(nonatomic, strong)QNUpToken *token;
  27. @property(nonatomic, strong)QNUploadRequestInfo *requestInfo;
  28. @property(nonatomic, strong)QNUploadRequestState *requestState;
  29. @property(nonatomic, strong)QNHttpRegionRequest *regionRequest;
  30. @end
  31. @implementation QNRequestTransaction
  32. - (instancetype)initWithHosts:(NSArray <NSString *> *)hosts
  33. regionId:(NSString * _Nullable)regionId
  34. token:(QNUpToken *)token{
  35. return [self initWithConfig:[QNConfiguration defaultConfiguration]
  36. uploadOption:[QNUploadOption defaultOptions]
  37. hosts:hosts
  38. regionId:regionId
  39. key:nil
  40. token:token];
  41. }
  42. - (instancetype)initWithConfig:(QNConfiguration *)config
  43. uploadOption:(QNUploadOption *)uploadOption
  44. hosts:(NSArray <NSString *> *)hosts
  45. regionId:(NSString * _Nullable)regionId
  46. key:(NSString * _Nullable)key
  47. token:(nonnull QNUpToken *)token{
  48. QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] init];
  49. [region setupRegionData:[QNZoneInfo zoneInfoWithMainHosts:hosts regionId:regionId]];
  50. return [self initWithConfig:config
  51. uploadOption:uploadOption
  52. targetRegion:region
  53. currentRegion:region
  54. key:key
  55. token:token];
  56. }
  57. - (instancetype)initWithConfig:(QNConfiguration *)config
  58. uploadOption:(QNUploadOption *)uploadOption
  59. targetRegion:(id <QNUploadRegion>)targetRegion
  60. currentRegion:(id <QNUploadRegion>)currentRegion
  61. key:(NSString *)key
  62. token:(QNUpToken *)token{
  63. if (self = [super init]) {
  64. _config = config;
  65. _uploadOption = uploadOption;
  66. _requestState = [[QNUploadRequestState alloc] init];
  67. _key = key;
  68. _token = token;
  69. _requestInfo = [[QNUploadRequestInfo alloc] init];
  70. _requestInfo.targetRegionId = targetRegion.zoneInfo.regionId;
  71. _requestInfo.currentRegionId = currentRegion.zoneInfo.regionId;
  72. _requestInfo.bucket = token.bucket;
  73. _requestInfo.key = key;
  74. _regionRequest = [[QNHttpRegionRequest alloc] initWithConfig:config
  75. uploadOption:uploadOption
  76. token:token
  77. region:currentRegion
  78. requestInfo:_requestInfo
  79. requestState:_requestState];
  80. }
  81. return self;
  82. }
  83. //MARK: -- uc query
  84. - (void)queryUploadHosts:(QNRequestTransactionCompleteHandler)complete{
  85. self.requestInfo.requestType = QNUploadRequestTypeUCQuery;
  86. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  87. return (BOOL)!responseInfo.isOK;
  88. };
  89. NSDictionary *header = @{@"User-Agent" : [kQNUserAgent getUserAgent:self.token.token]};
  90. NSString *action = [NSString stringWithFormat:@"/v4/query?ak=%@&bucket=%@", self.token.access, self.token.bucket];
  91. [self.regionRequest get:action
  92. headers:header
  93. shouldRetry:shouldRetry
  94. complete:complete];
  95. }
  96. //MARK: -- upload form
  97. - (void)uploadFormData:(NSData *)data
  98. fileName:(NSString *)fileName
  99. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  100. complete:(QNRequestTransactionCompleteHandler)complete{
  101. self.requestInfo.requestType = QNUploadRequestTypeForm;
  102. NSMutableDictionary *param = [NSMutableDictionary dictionary];
  103. if (self.uploadOption.params) {
  104. [param addEntriesFromDictionary:self.uploadOption.params];
  105. }
  106. if (self.uploadOption.metaDataParam) {
  107. [param addEntriesFromDictionary:self.uploadOption.metaDataParam];
  108. }
  109. if (self.key && self.key.length > 0) {
  110. param[@"key"] = self.key;
  111. }
  112. param[@"token"] = self.token.token ?: @"";
  113. if (self.uploadOption.checkCrc) {
  114. param[@"crc32"] = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:data]];
  115. }
  116. NSMutableData *body = [NSMutableData data];
  117. NSString *boundary = @"werghnvt54wef654rjuhgb56trtg34tweuyrgf";
  118. NSString *disposition = @"Content-Disposition: form-data";
  119. for (NSString *paramsKey in param) {
  120. NSString *pair = [NSString stringWithFormat:@"--%@\r\n%@; name=\"%@\"\r\n\r\n", boundary, disposition, paramsKey];
  121. [body appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
  122. id value = [param objectForKey:paramsKey];
  123. if ([value isKindOfClass:[NSString class]]) {
  124. [body appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
  125. } else if ([value isKindOfClass:[NSData class]]) {
  126. [body appendData:value];
  127. }
  128. [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
  129. }
  130. fileName = [QNUtils formEscape:fileName];
  131. NSString *filePair = [NSString stringWithFormat:@"--%@\r\n%@; name=\"%@\"; filename=\"%@\"\nContent-Type:%@\r\n\r\n", boundary, disposition, @"file", fileName, self.uploadOption.mimeType];
  132. [body appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
  133. [body appendData:data];
  134. [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
  135. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  136. header[@"Content-Type"] = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  137. header[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)body.length];
  138. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  139. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  140. return (BOOL)!responseInfo.isOK;
  141. };
  142. [self.regionRequest post:nil
  143. headers:header
  144. body:body
  145. shouldRetry:shouldRetry
  146. progress:progress
  147. complete:complete];
  148. }
  149. //MARK: -- 分块上传
  150. - (void)makeBlock:(long long)blockOffset
  151. blockSize:(long long)blockSize
  152. firstChunkData:(NSData *)firstChunkData
  153. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  154. complete:(QNRequestTransactionCompleteHandler)complete{
  155. self.requestInfo.requestType = QNUploadRequestTypeMkblk;
  156. self.requestInfo.fileOffset = @(blockOffset);
  157. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  158. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  159. header[@"Authorization"] = token;
  160. header[@"Content-Type"] = @"application/octet-stream";
  161. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  162. NSString *action = [NSString stringWithFormat:@"/mkblk/%u", (unsigned int)blockSize];
  163. NSString *chunkCrc = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:firstChunkData]];
  164. kQNWeakSelf;
  165. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  166. kQNStrongSelf;
  167. NSString *ctx = response[@"ctx"];
  168. NSString *crcServer = [NSString stringWithFormat:@"%@", response[@"crc32"]];
  169. return (BOOL)(responseInfo.isOK == false || (responseInfo.isOK && (!ctx || (self.uploadOption.checkCrc && ![chunkCrc isEqualToString:crcServer]))));
  170. };
  171. [self.regionRequest post:action
  172. headers:header
  173. body:firstChunkData
  174. shouldRetry:shouldRetry
  175. progress:progress
  176. complete:complete];
  177. }
  178. - (void)uploadChunk:(NSString *)blockContext
  179. blockOffset:(long long)blockOffset
  180. chunkData:(NSData *)chunkData
  181. chunkOffset:(long long)chunkOffset
  182. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  183. complete:(QNRequestTransactionCompleteHandler)complete{
  184. self.requestInfo.requestType = QNUploadRequestTypeBput;
  185. self.requestInfo.fileOffset = @(blockOffset + chunkOffset);
  186. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  187. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  188. header[@"Authorization"] = token;
  189. header[@"Content-Type"] = @"application/octet-stream";
  190. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  191. NSString *action = [NSString stringWithFormat:@"/bput/%@/%lld", blockContext, chunkOffset];
  192. NSString *chunkCrc = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:chunkData]];
  193. kQNWeakSelf;
  194. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  195. kQNStrongSelf;
  196. NSString *ctx = response[@"ctx"];
  197. NSString *crcServer = [NSString stringWithFormat:@"%@", response[@"crc32"]];
  198. return (BOOL)(responseInfo.isOK == false || (responseInfo.isOK && (!ctx || (self.uploadOption.checkCrc && ![chunkCrc isEqualToString:crcServer]))));
  199. };
  200. [self.regionRequest post:action
  201. headers:header
  202. body:chunkData
  203. shouldRetry:shouldRetry
  204. progress:progress
  205. complete:complete];
  206. }
  207. - (void)makeFile:(long long)fileSize
  208. fileName:(NSString *)fileName
  209. blockContexts:(NSArray <NSString *> *)blockContexts
  210. complete:(QNRequestTransactionCompleteHandler)complete{
  211. self.requestInfo.requestType = QNUploadRequestTypeMkfile;
  212. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  213. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  214. header[@"Authorization"] = token;
  215. header[@"Content-Type"] = @"application/octet-stream";
  216. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  217. NSString *mimeType = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.uploadOption.mimeType]];
  218. __block NSString *action = [[NSString alloc] initWithFormat:@"/mkfile/%lld%@", fileSize, mimeType];
  219. if (self.key != nil) {
  220. NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
  221. action = [NSString stringWithFormat:@"%@%@", action, keyStr];
  222. }
  223. [self.uploadOption.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  224. action = [NSString stringWithFormat:@"%@/%@/%@", action, key, [QNUrlSafeBase64 encodeString:obj]];
  225. }];
  226. [self.uploadOption.metaDataParam enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  227. action = [NSString stringWithFormat:@"%@/%@/%@", action, key, [QNUrlSafeBase64 encodeString:obj]];
  228. }];
  229. //添加路径
  230. NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:fileName]];
  231. action = [NSString stringWithFormat:@"%@%@", action, fname];
  232. NSMutableData *body = [NSMutableData data];
  233. NSString *bodyString = [blockContexts componentsJoinedByString:@","];
  234. [body appendData:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
  235. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  236. return (BOOL)(!responseInfo.isOK);
  237. };
  238. [self.regionRequest post:action
  239. headers:header
  240. body:body
  241. shouldRetry:shouldRetry
  242. progress:nil
  243. complete:complete];
  244. }
  245. - (void)initPart:(QNRequestTransactionCompleteHandler)complete{
  246. self.requestInfo.requestType = QNUploadRequestTypeInitParts;
  247. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  248. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  249. header[@"Authorization"] = token;
  250. header[@"Content-Type"] = @"application/octet-stream";
  251. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  252. NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
  253. NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];;
  254. NSString *action = [[NSString alloc] initWithFormat:@"%@%@/uploads", buckets, objects];
  255. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  256. return (BOOL)(!responseInfo.isOK);
  257. };
  258. [self.regionRequest post:action
  259. headers:header
  260. body:nil
  261. shouldRetry:shouldRetry
  262. progress:nil
  263. complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
  264. complete(responseInfo, metrics, response);
  265. }];
  266. }
  267. - (void)uploadPart:(NSString *)uploadId
  268. partIndex:(NSInteger)partIndex
  269. partData:(NSData *)partData
  270. progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
  271. complete:(QNRequestTransactionCompleteHandler)complete{
  272. self.requestInfo.requestType = QNUploadRequestTypeUploadPart;
  273. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  274. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  275. header[@"Authorization"] = token;
  276. header[@"Content-Type"] = @"application/octet-stream";
  277. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  278. if (self.uploadOption.checkCrc) {
  279. NSString *md5 = [[partData qn_md5] lowercaseString];
  280. if (md5) {
  281. header[@"Content-MD5"] = md5;
  282. }
  283. }
  284. NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
  285. NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];;
  286. NSString *uploads = [[NSString alloc] initWithFormat:@"/uploads/%@", uploadId];
  287. NSString *partNumber = [[NSString alloc] initWithFormat:@"/%ld", (long)partIndex];
  288. NSString *action = [[NSString alloc] initWithFormat:@"%@%@%@%@", buckets, objects, uploads, partNumber];
  289. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  290. NSString *etag = [NSString stringWithFormat:@"%@", response[@"etag"]];
  291. NSString *serverMD5 = [NSString stringWithFormat:@"%@", response[@"md5"]];
  292. return (BOOL)(!responseInfo.isOK || !etag || !serverMD5);
  293. };
  294. [self.regionRequest put:action
  295. headers:header
  296. body:partData
  297. shouldRetry:shouldRetry
  298. progress:progress
  299. complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
  300. complete(responseInfo, metrics, response);
  301. }];
  302. }
  303. - (void)completeParts:(NSString *)fileName
  304. uploadId:(NSString *)uploadId
  305. partInfoArray:(NSArray <NSDictionary *> *)partInfoArray
  306. complete:(QNRequestTransactionCompleteHandler)complete{
  307. self.requestInfo.requestType = QNUploadRequestTypeCompletePart;
  308. if (!partInfoArray || partInfoArray.count == 0) {
  309. QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithInvalidArgument:@"partInfoArray"];
  310. if (complete) {
  311. complete(responseInfo, nil, responseInfo.responseDictionary);
  312. }
  313. return;
  314. }
  315. NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
  316. NSMutableDictionary *header = [NSMutableDictionary dictionary];
  317. header[@"Authorization"] = token;
  318. header[@"Content-Type"] = @"application/json";
  319. header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
  320. NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
  321. NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];
  322. NSString *uploads = [[NSString alloc] initWithFormat:@"/uploads/%@", uploadId];
  323. NSString *action = [[NSString alloc] initWithFormat:@"%@%@%@", buckets, objects, uploads];
  324. NSMutableDictionary *bodyDictionary = [NSMutableDictionary dictionary];
  325. if (partInfoArray) {
  326. bodyDictionary[@"parts"] = partInfoArray;
  327. }
  328. if (fileName) {
  329. bodyDictionary[@"fname"] = fileName;
  330. }
  331. if (self.uploadOption.mimeType) {
  332. bodyDictionary[@"mimeType"] = self.uploadOption.mimeType;
  333. }
  334. if (self.uploadOption.params) {
  335. bodyDictionary[@"customVars"] = self.uploadOption.params;
  336. }
  337. if (self.uploadOption.metaDataParam) {
  338. bodyDictionary[@"metaData"] = self.uploadOption.metaDataParam;
  339. }
  340. NSError *error = nil;
  341. NSData *body = [NSJSONSerialization dataWithJSONObject:bodyDictionary
  342. options:NSJSONWritingPrettyPrinted
  343. error:&error];
  344. if (error) {
  345. QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:error.description];
  346. if (complete) {
  347. complete(responseInfo, nil, responseInfo.responseDictionary);
  348. }
  349. return;
  350. }
  351. BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
  352. return (BOOL)(!responseInfo.isOK);
  353. };
  354. [self.regionRequest post:action
  355. headers:header
  356. body:body
  357. shouldRetry:shouldRetry
  358. progress:nil
  359. complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
  360. complete(responseInfo, metrics, response);
  361. }];
  362. }
  363. - (NSString *)resumeV2EncodeKey:(NSString *)key{
  364. NSString *encodeKey = nil;
  365. if (!self.key) {
  366. encodeKey = @"~";
  367. } else if (self.key.length == 0) {
  368. encodeKey = @"";
  369. } else {
  370. encodeKey = [QNUrlSafeBase64 encodeString:self.key];
  371. }
  372. return encodeKey;
  373. }
  374. @end