QNRequestTransaction.m 19 KB

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