解决了语音上传到问题,接下来要解决下载播放问题

This commit is contained in:
joywayer
2025-06-15 12:36:47 +08:00
parent bba3ed1cb4
commit c11fc62bf1
513 changed files with 31197 additions and 2969 deletions

55
Pods/Qiniu/QiniuSDK/BigData/QNPipeline.h generated Normal file
View File

@@ -0,0 +1,55 @@
//
// QNPipeline.h
// QiniuSDK
//
// Created by BaiLong on 2017/7/25.
// Copyright © 2017年 Qiniu. All rights reserved.
//
#ifndef QNPipeline_h
#define QNPipeline_h
@class QNResponseInfo;
@interface QNPipelineConfig : NSObject
/**
* 上报打点域名
*/
@property (copy, nonatomic, readonly) NSString *host;
/**
* 超时时间 单位 秒
*/
@property (assign) UInt32 timeoutInterval;
- (instancetype)initWithHost:(NSString *)host;
- (instancetype)init;
@end
/**
* 上传完成后的回调函数
*
* @param info 上下文信息,包括状态码,错误值
*/
typedef void (^QNPipelineCompletionHandler)(QNResponseInfo *info);
@interface QNPipeline : NSObject
- (instancetype)init:(QNPipelineConfig *)config;
- (void)pumpRepo:(NSString *)repo
event:(NSDictionary *)data
token:(NSString *)token
handler:(QNPipelineCompletionHandler)handler;
- (void)pumpRepo:(NSString *)repo
events:(NSArray<NSDictionary *> *)data
token:(NSString *)token
handler:(QNPipelineCompletionHandler)handler;
@end
#endif /* QNPipeline_h */

154
Pods/Qiniu/QiniuSDK/BigData/QNPipeline.m generated Normal file
View File

@@ -0,0 +1,154 @@
//
// QNPipeline.m
// QiniuSDK
//
// Created by BaiLong on 2017/7/25.
// Copyright © 2017 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNSessionManager.h"
#import "QNPipeline.h"
@implementation QNPipelineConfig
- (instancetype)init {
return [self initWithHost:@"https://pipeline.qiniu.com"];
}
- (instancetype)initWithHost:(NSString*)host {
if (self = [super init]) {
_host = host;
_timeoutInterval = 10;
}
return self;
}
@end
@interface QNPipeline ()
@property (nonatomic) QNSessionManager *httpManager;
@property (nonatomic) QNPipelineConfig* config;
+ (NSDateFormatter*)dateFormatter;
@end
static NSString* buildString(NSObject* obj) {
NSString* v;
if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber* num = (NSNumber*)obj;
if (num == (void*)kCFBooleanFalse) {
v = @"false";
} else if (num == (void*)kCFBooleanTrue) {
v = @"true";
} else if (!strcmp(num.objCType, @encode(BOOL))) {
if ([num intValue] == 0) {
v = @"false";
} else {
v = @"true";
}
} else {
v = num.stringValue;
}
} else if ([obj isKindOfClass:[NSString class]]) {
v = (NSString*)obj;
v = [v stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
v = [v stringByReplacingOccurrencesOfString:@"\t" withString:@"\\t"];
} else if ([obj isKindOfClass:[NSDictionary class]] || [obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSSet class]]) {
v = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:obj options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];
} else if ([obj isKindOfClass:[NSDate class]]) {
v = [[QNPipeline dateFormatter] stringFromDate:(NSDate*)obj];
} else {
v = [obj description];
}
return v;
}
static void formatPoint(NSDictionary* event, NSMutableString* buffer) {
[event enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSObject* obj, BOOL* stop) {
if (obj == nil || [obj isEqual:[NSNull null]]) {
return;
}
[buffer appendString:key];
[buffer appendString:@"="];
[buffer appendString:buildString(obj)];
[buffer appendString:@"\t"];
}];
NSRange range = NSMakeRange(buffer.length - 1, 1);
[buffer replaceCharactersInRange:range withString:@"\n"];
}
static NSMutableString* formatPoints(NSArray<NSDictionary*>* events) {
NSMutableString* str = [NSMutableString new];
[events enumerateObjectsUsingBlock:^(NSDictionary* _Nonnull obj, NSUInteger idx, BOOL* _Nonnull stop) {
formatPoint(obj, str);
}];
return str;
}
@implementation QNPipeline
- (instancetype)init:(QNPipelineConfig*)config {
if (self = [super init]) {
if (config == nil) {
config = [QNPipelineConfig new];
}
_config = config;
#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)
_httpManager = [[QNSessionManager alloc] initWithProxy:nil timeout:config.timeoutInterval urlConverter:nil];
#endif
}
return self;
}
- (void)pumpRepo:(NSString*)repo
event:(NSDictionary*)data
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSMutableString* str = [NSMutableString new];
formatPoint(data, str);
[self pumpRepo:repo string:str token:token handler:handler];
}
- (void)pumpRepo:(NSString*)repo
events:(NSArray<NSDictionary*>*)data
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSMutableString* str = formatPoints(data);
[self pumpRepo:repo string:str token:token handler:handler];
}
- (NSString*)url:(NSString*)repo {
return [NSString stringWithFormat:@"%@/v2/repos/%@/data", _config.host, repo];
}
- (void)pumpRepo:(NSString*)repo
string:(NSString*)str
token:(NSString*)token
handler:(QNPipelineCompletionHandler)handler {
NSDictionary* headers = @{ @"Authorization" : token,
@"Content-Type" : @"text/plain" };
[_httpManager post:[self url:repo] withData:[str dataUsingEncoding:NSUTF8StringEncoding] withParams:nil withHeaders:headers withIdentifier:nil withCompleteBlock:^(QNResponseInfo *httpResponseInfo, NSDictionary *respBody) {
handler(httpResponseInfo);
} withProgressBlock:nil withCancelBlock:nil withAccess:nil];
}
+ (NSDateFormatter*)dateFormatter {
static NSDateFormatter* formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"];
[formatter setTimeZone:[NSTimeZone defaultTimeZone]];
});
return formatter;
}
@end

View File

@@ -0,0 +1,50 @@
#import <Foundation/Foundation.h>
#import "QNConfiguration.h"
#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)
@class QNResponseInfo;
typedef void (^QNInternalProgressBlock)(long long totalBytesWritten, long long totalBytesExpectedToWrite);
typedef void (^QNCompleteBlock)(QNResponseInfo *httpResponseInfo, NSDictionary *respBody);
typedef BOOL (^QNCancelBlock)(void);
@interface QNSessionManager : NSObject
- (instancetype)initWithProxy:(NSDictionary *)proxyDict
timeout:(UInt32)timeout
urlConverter:(QNUrlConvert)converter;
- (void)multipartPost:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withFileName:(NSString *)key
withMimeType:(NSString *)mime
withIdentifier:(NSString *)identifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)post:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withHeaders:(NSDictionary *)headers
withIdentifier:(NSString *)identifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access;
- (void)get:(NSString *)url
withHeaders:(NSDictionary *)headers
withCompleteBlock:(QNCompleteBlock)completeBlock;
- (void)invalidateSessionWithIdentifier:(NSString *)identifier;
@end
#endif

View File

@@ -0,0 +1,265 @@
//
// QNHttpManager.m
// QiniuSDK
//
// Created by bailong on 14/10/1.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import "QNConfiguration.h"
#import "QNSessionManager.h"
#import "QNUserAgent.h"
#import "QNResponseInfo.h"
#import "NSURLRequest+QNRequest.h"
#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)
typedef void (^QNSessionComplete)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);
@interface QNSessionDelegateHandler : NSObject <NSURLSessionDataDelegate>
@property (nonatomic, copy) QNInternalProgressBlock progressBlock;
@property (nonatomic, copy) QNCancelBlock cancelBlock;
@property (nonatomic, copy) QNSessionComplete completeBlock;
@property (nonatomic, strong) NSData *responseData;
@end
@implementation QNSessionDelegateHandler
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
_responseData = data;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
// bytes_sent & bytes_total
self.completeBlock(_responseData, task.response, error);
[session finishTasksAndInvalidate];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
if (_progressBlock) {
_progressBlock(totalBytesSent, totalBytesExpectedToSend);
}
if (_cancelBlock && _cancelBlock()) {
[task cancel];
}
}
- (uint64_t)getTimeintervalWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate {
if (!startDate || !endDate) return 0;
NSTimeInterval interval = [endDate timeIntervalSinceDate:startDate];
return interval * 1000;
}
@end
@interface QNSessionManager ()
@property UInt32 timeout;
@property (nonatomic, strong) QNUrlConvert converter;
@property (nonatomic, strong) NSDictionary *proxyDict;
@property (nonatomic, strong) NSOperationQueue *delegateQueue;
@property (nonatomic, strong) NSMutableArray *sessionArray;
@property (nonatomic, strong) NSLock *lock;
@end
@implementation QNSessionManager
- (instancetype)initWithProxy:(NSDictionary *)proxyDict
timeout:(UInt32)timeout
urlConverter:(QNUrlConvert)converter {
if (self = [super init]) {
_delegateQueue = [[NSOperationQueue alloc] init];
_timeout = timeout;
_converter = converter;
_proxyDict = proxyDict;
_sessionArray = [NSMutableArray array];
_lock = [[NSLock alloc] init];
}
return self;
}
- (instancetype)init {
return [self initWithProxy:nil timeout:60 urlConverter:nil];
}
- (void)sendRequest:(NSMutableURLRequest *)request
withIdentifier:(NSString *)identifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
NSString *domain = request.URL.host;
NSString *u = request.URL.absoluteString;
NSURL *url = request.URL;
if (_converter != nil) {
url = [[NSURL alloc] initWithString:_converter(u)];
request.URL = url;
domain = url.host;
}
request.qn_domain = request.URL.host;
[request setTimeoutInterval:_timeout];
[request setValue:[[QNUserAgent sharedInstance] getUserAgent:access] forHTTPHeaderField:@"User-Agent"];
[request setValue:nil forHTTPHeaderField:@"Accept-Language"];
QNSessionDelegateHandler *delegate = [[QNSessionDelegateHandler alloc] init];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.connectionProxyDictionary = _proxyDict ? _proxyDict : nil;
__block NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:_delegateQueue];
[_sessionArray addObject:@{@"identifier":identifier,@"session":session}];
delegate.cancelBlock = cancelBlock;
delegate.progressBlock = progressBlock ? progressBlock : ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
};
delegate.completeBlock = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[self finishSession:session];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
QNResponseInfo *info = [[QNResponseInfo alloc] initWithResponseInfoHost:request.qn_domain response:httpResponse body:data error:error];
completeBlock(info, info.responseDictionary);
};
NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request];
[uploadTask resume];
}
- (void)multipartPost:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withFileName:(NSString *)key
withMimeType:(NSString *)mime
withIdentifier:(NSString *)identifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
NSURL *URL = [[NSURL alloc] initWithString:url];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
request.HTTPMethod = @"POST";
NSString *boundary = @"werghnvt54wef654rjuhgb56trtg34tweuyrgf";
request.allHTTPHeaderFields = @{
@"Content-Type" : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
};
NSMutableData *postData = [[NSMutableData alloc] init];
for (NSString *paramsKey in params) {
NSString *pair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n", boundary, paramsKey];
[postData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
id value = [params objectForKey:paramsKey];
if ([value isKindOfClass:[NSString class]]) {
[postData appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
} else if ([value isKindOfClass:[NSData class]]) {
[postData appendData:value];
}
[postData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"\nContent-Type:%@\r\n\r\n", boundary, @"file", key, mime];
[postData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
[postData appendData:data];
[postData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = postData;
[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)postData.length] forHTTPHeaderField:@"Content-Length"];
[self sendRequest:request withIdentifier:identifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:cancelBlock
withAccess:access];
}
- (void)post:(NSString *)url
withData:(NSData *)data
withParams:(NSDictionary *)params
withHeaders:(NSDictionary *)headers
withIdentifier:(NSString *)identifier
withCompleteBlock:(QNCompleteBlock)completeBlock
withProgressBlock:(QNInternalProgressBlock)progressBlock
withCancelBlock:(QNCancelBlock)cancelBlock
withAccess:(NSString *)access {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:url]];
if (headers) {
[request setAllHTTPHeaderFields:headers];
}
[request setHTTPMethod:@"POST"];
if (params) {
[request setValuesForKeysWithDictionary:params];
}
[request setHTTPBody:data];
identifier = !identifier ? [[NSUUID UUID] UUIDString] : identifier;
QNAsyncRun(^{
[self sendRequest:request
withIdentifier:identifier
withCompleteBlock:completeBlock
withProgressBlock:progressBlock
withCancelBlock:cancelBlock
withAccess:access];
});
}
- (void)get:(NSString *)url
withHeaders:(NSDictionary *)headers
withCompleteBlock:(QNCompleteBlock)completeBlock {
QNAsyncRun(^{
NSURL *URL = [NSURL URLWithString:url];
// NSString *domain = URL.host;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.qn_domain = URL.host;
QNSessionDelegateHandler *delegate = [[QNSessionDelegateHandler alloc] init];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
__block NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:self.delegateQueue];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
delegate.cancelBlock = nil;
delegate.progressBlock = nil;
delegate.completeBlock = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[self finishSession:session];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
QNResponseInfo *info = [[QNResponseInfo alloc] initWithResponseInfoHost:request.qn_domain response:httpResponse body:data error:error];
completeBlock(info, info.responseDictionary);
};
[dataTask resume];
});
}
- (void)finishSession:(NSURLSession *)session {
[_lock lock];
for (int i = 0; i < _sessionArray.count; i++) {
NSDictionary *sessionInfo = _sessionArray[i];
if (sessionInfo[@"session"] == session) {
[session finishTasksAndInvalidate];
[_sessionArray removeObject:sessionInfo];
break;
}
}
[_lock unlock];
}
- (void)invalidateSessionWithIdentifier:(NSString *)identifier {
[_lock lock];
for (int i = 0; i < _sessionArray.count; i++) {
NSDictionary *sessionInfo = _sessionArray[i];
if ([sessionInfo[@"identifier"] isEqualToString:identifier]) {
NSURLSession *session = sessionInfo[@"session"];
[session invalidateAndCancel];
[_sessionArray removeObject:sessionInfo];
break;
}
}
[_lock unlock];
}
@end
#endif

View File

@@ -0,0 +1,60 @@
//
// QNReportConfig.h
// QiniuSDK
//
// Created by 杨森 on 2020/7/14.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNReportConfig : NSObject
- (id)init __attribute__((unavailable("Use sharedInstance: instead.")));
+ (instancetype)sharedInstance;
/**
* 是否开启sdk上传信息搜集 默认为YES
*/
@property (nonatomic, assign, getter=isReportEnable) BOOL reportEnable;
/**
* 每次上传时间间隔 单位:分钟 默认为0.5分钟
*/
@property (nonatomic, assign) double interval;
/**
* 记录文件大于 uploadThreshold 会触发上传,单位:字节 默认为16 * 1024
*/
@property (nonatomic, assign) uint64_t uploadThreshold;
/**
* 记录文件最大值 要大于 uploadThreshold 单位:字节 默认为20 * 1024 * 1024
*/
@property (nonatomic, assign) uint64_t maxRecordFileSize;
/**
* 记录文件所在文件夹目录 默认为:.../沙盒/Library/Caches/com.qiniu.report
*/
@property (nonatomic, copy) NSString *recordDirectory;
/**
* 信息上报服务器地址
*/
@property (nonatomic, copy, readonly) NSString *serverURL;
/**
* 信息上报服务器地址 host
*/
@property (nonatomic, copy, readonly) NSString *serverHost;
/**
* 信息上报请求超时时间 单位:秒 默认为10秒
*/
@property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,42 @@
//
// QNReportConfig.m
// QiniuSDK
//
// Created by on 2020/7/14.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNReportConfig.h"
#import "QNConfig.h"
#import "QNUtils.h"
@implementation QNReportConfig
+ (instancetype)sharedInstance {
static QNReportConfig *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_reportEnable = YES;
_interval = 0.5;
_serverHost = kQNUpLogHost;
_recordDirectory = [NSString stringWithFormat:@"%@/report", [QNUtils sdkCacheDirectory]];
_maxRecordFileSize = 20 * 1024 * 1024;
_uploadThreshold = 16 * 1024;
_timeoutInterval = 10;
}
return self;
}
- (NSString *)serverURL {
return [NSString stringWithFormat:@"https://%@/log/4?compressed=gzip", _serverHost];
}
@end

143
Pods/Qiniu/QiniuSDK/Collect/QNReportItem.h generated Normal file
View File

@@ -0,0 +1,143 @@
//
// QNReportItem.h
// QiniuSDK
//
// Created by yangsen on 2020/5/12.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUploadInfoReporter.h"
#import "QNResponseInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNReportItem : NSObject
+ (instancetype)item;
/// 设置打点日志字段
/// @param value log value
/// @param key log key
- (void)setReportValue:(id _Nullable)value forKey:(NSString * _Nullable)key;
/// 移除打点日志字段
/// @param key log key
- (void)removeReportValueForKey:(NSString * _Nullable)key;
@end
@interface QNUploadInfoReporter(ReportItem)
- (void)reportItem:(QNReportItem *)item token:(NSString *)token;
@end
@interface QNResponseInfo(Report)
@property(nonatomic, assign, readonly)NSNumber *requestReportStatusCode;
@property(nonatomic, copy, readonly)NSString *requestReportErrorType;
@property(nonatomic, copy, readonly)NSString *qualityResult;
@end
//MARK:-- 日志类型
extern NSString *const QNReportLogTypeRequest;
extern NSString *const QNReportLogTypeBlock;
extern NSString *const QNReportLogTypeQuality;
//MARK:-- 请求信息打点⽇志
extern NSString *const QNReportRequestKeyLogType;
extern NSString *const QNReportRequestKeyUpTime;
extern NSString *const QNReportRequestKeyStatusCode;
extern NSString *const QNReportRequestKeyRequestId;
extern NSString *const QNReportRequestKeyHost;
extern NSString *const QNReportRequestKeyHttpVersion;
extern NSString *const QNReportRequestKeyRemoteIp;
extern NSString *const QNReportRequestKeyPort;
extern NSString *const QNReportRequestKeyTargetBucket;
extern NSString *const QNReportRequestKeyTargetKey;
extern NSString *const QNReportRequestKeyTotalElapsedTime;
extern NSString *const QNReportRequestKeyDnsElapsedTime;
extern NSString *const QNReportRequestKeyConnectElapsedTime;
extern NSString *const QNReportRequestKeyTLSConnectElapsedTime;
extern NSString *const QNReportRequestKeyRequestElapsedTime;
extern NSString *const QNReportRequestKeyWaitElapsedTime;
extern NSString *const QNReportRequestKeyResponseElapsedTime;
extern NSString *const QNReportRequestKeyFileOffset;
extern NSString *const QNReportRequestKeyBytesSent;
extern NSString *const QNReportRequestKeyBytesTotal;
extern NSString *const QNReportRequestKeyPid;
extern NSString *const QNReportRequestKeyTid;
extern NSString *const QNReportRequestKeyTargetRegionId;
extern NSString *const QNReportRequestKeyCurrentRegionId;
extern NSString *const QNReportRequestKeyErrorType;
extern NSString *const QNReportRequestKeyErrorDescription;
extern NSString *const QNReportRequestKeyUpType;
extern NSString *const QNReportRequestKeyOsName;
extern NSString *const QNReportRequestKeyOsVersion;
extern NSString *const QNReportRequestKeySDKName;
extern NSString *const QNReportRequestKeySDKVersion;
extern NSString *const QNReportRequestKeyClientTime;
extern NSString *const QNReportRequestKeyHttpClient;
extern NSString *const QNReportRequestKeyNetworkType;
extern NSString *const QNReportRequestKeySignalStrength;
extern NSString *const QNReportRequestKeyPrefetchedDnsSource;
extern NSString *const QNReportRequestKeyDnsSource;
extern NSString *const QNReportRequestKeyDnsErrorMessage;
extern NSString *const QNReportRequestKeyPrefetchedBefore;
extern NSString *const QNReportRequestKeyPrefetchedErrorMessage;
extern NSString *const QNReportRequestKeyNetworkMeasuring;
extern NSString *const QNReportRequestKeyPerceptiveSpeed;
extern NSString *const QNReportRequestKeyHijacking;
//MARK:-- 分块上传统计⽇志
extern NSString *const QNReportBlockKeyLogType;
extern NSString *const QNReportBlockKeyUpTime;
extern NSString *const QNReportBlockKeyTargetBucket;
extern NSString *const QNReportBlockKeyTargetKey;
extern NSString *const QNReportBlockKeyTargetRegionId;
extern NSString *const QNReportBlockKeyCurrentRegionId;
extern NSString *const QNReportBlockKeyTotalElapsedTime;
extern NSString *const QNReportBlockKeyBytesSent;
extern NSString *const QNReportBlockKeyRecoveredFrom;
extern NSString *const QNReportBlockKeyFileSize;
extern NSString *const QNReportBlockKeyPid;
extern NSString *const QNReportBlockKeyTid;
extern NSString *const QNReportBlockKeyUpApiVersion;
extern NSString *const QNReportBlockKeyClientTime;
extern NSString *const QNReportBlockKeyOsName;
extern NSString *const QNReportBlockKeyOsVersion;
extern NSString *const QNReportBlockKeySDKName;
extern NSString *const QNReportBlockKeySDKVersion;
extern NSString *const QNReportBlockKeyPerceptiveSpeed;
extern NSString *const QNReportBlockKeyHijacking;
//MARK:-- 上传质量统计
extern NSString *const QNReportQualityKeyLogType;
extern NSString *const QNReportQualityKeyUpType;
extern NSString *const QNReportQualityKeyUpTime;
extern NSString *const QNReportQualityKeyResult;
extern NSString *const QNReportQualityKeyTargetBucket;
extern NSString *const QNReportQualityKeyTargetKey;
extern NSString *const QNReportQualityKeyTotalElapsedTime;
extern NSString *const QNReportQualityKeyUcQueryElapsedTime;
extern NSString *const QNReportQualityKeyRequestsCount;
extern NSString *const QNReportQualityKeyRegionsCount;
extern NSString *const QNReportQualityKeyBytesSent;
extern NSString *const QNReportQualityKeyFileSize;
extern NSString *const QNReportQualityKeyCloudType;
extern NSString *const QNReportQualityKeyErrorType;
extern NSString *const QNReportQualityKeyErrorDescription;
extern NSString *const QNReportQualityKeyOsName;
extern NSString *const QNReportQualityKeyOsVersion;
extern NSString *const QNReportQualityKeySDKName;
extern NSString *const QNReportQualityKeySDKVersion;
extern NSString *const QNReportQualityKeyPerceptiveSpeed;
extern NSString *const QNReportQualityKeyHijacking;
NS_ASSUME_NONNULL_END

257
Pods/Qiniu/QiniuSDK/Collect/QNReportItem.m generated Normal file
View File

@@ -0,0 +1,257 @@
//
// QNReportItem.m
// QiniuSDK
//
// Created by yangsen on 2020/5/12.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNReportItem.h"
#import "QNAsyncRun.h"
#import "QNLogUtil.h"
@interface QNReportItem()
@property(nonatomic, strong)NSMutableDictionary *keyValues;
@end
@implementation QNReportItem
+ (instancetype)item{
QNReportItem *item = [[QNReportItem alloc] init];
return item;
}
- (instancetype)init{
if (self = [super init]) {
[self initData];
}
return self;
}
- (void)initData{
_keyValues = [NSMutableDictionary dictionary];
}
- (void)setReportValue:(id _Nullable)value forKey:(NSString * _Nullable)key{
if (!value || !key || ![key isKindOfClass:[NSString class]]) {
return;
}
if ([value isKindOfClass:[NSString class]] && [(NSString *)value length] > 1024) {
value = [(NSString *)value substringToIndex:1024];
}
[self.keyValues setValue:value forKey:key];
}
- (void)removeReportValueForKey:(NSString * _Nullable)key{
if (!key) {
return;
}
[self.keyValues removeObjectForKey:key];
}
- (NSString *)toJson{
NSString *jsonString = @"{}";
if (!self.keyValues || self.keyValues.count == 0) {
return jsonString;
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.keyValues
options:NSJSONWritingFragmentsAllowed
error:nil];
if (!jsonData) {
return jsonString;
}
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return jsonString;
}
@end
@implementation QNUploadInfoReporter(ReportItem)
- (void)reportItem:(QNReportItem *)item token:(NSString *)token{
NSString *itemJsonString = [item toJson];
QNLogInfo(@"up log:%@", itemJsonString);
if (itemJsonString && ![itemJsonString isEqualToString:@"{}"]) {
[kQNReporter report:itemJsonString token:token];
}
}
@end
@implementation QNResponseInfo(Report)
- (NSNumber *)requestReportStatusCode{
return @(self.statusCode);
}
- (NSString *)requestReportErrorType{
NSString *errorType = nil;
if (self.statusCode == -1){
errorType = @"network_error";
} else if (self.statusCode == kQNLocalIOError){
errorType = @"local_io_error";
} else if (self.statusCode == 100){
errorType = @"protocol_error";
} else if (self.statusCode > 199 && self.statusCode < 300) {
// NSURLErrorFailingURLErrorKey
} else if (self.statusCode > 299){
errorType = @"response_error";
} else if (self.statusCode == -1003){
errorType = @"unknown_host";
} else if (self.statusCode == -1009){
errorType = @"network_slow";
} else if (self.statusCode == -1001){
errorType = @"timeout";
} else if (self.statusCode == -1004){
errorType = @"cannot_connect_to_host";
} else if (self.statusCode == -1005 || self.statusCode == -1021){
errorType = @"transmission_error";
} else if ((self.statusCode <= -1200 && self.statusCode >= -1206) || self.statusCode == -2000 || self.statusCode == -9807){
errorType = @"ssl_error";
} else if (self.statusCode == -1015 || self.statusCode == -1016 || self.statusCode == -1017){
errorType = @"parse_error";
} else if (self.statusCode == -1007 || self.statusCode == -1010 || self.statusCode == kQNMaliciousResponseError){
errorType = @"malicious_response";
} else if (self.statusCode == kQNUnexpectedSysCallError
|| (self.statusCode > -1130 && self.statusCode <= -1010)){
errorType = @"unexpected_syscall_error";
} else if (self.statusCode == kQNRequestCancelled
|| self.statusCode == NSURLErrorCancelled){
errorType = @"user_canceled";
} else {
errorType = @"unknown_error";
}
return errorType;
}
- (NSString *)qualityResult{
NSString *result = nil;
if (self.statusCode > 199 && self.statusCode < 300) {
result = @"ok";
} else if (self.statusCode > 399 &&
(self.statusCode < 500 || self.statusCode == 573 || self.statusCode == 579 ||
self.statusCode == 608 || self.statusCode == 612 || self.statusCode == 614 || self.statusCode == 630 || self.statusCode == 631 ||
self.statusCode == 701)) {
result = @"bad_request";
} else if (self.statusCode == kQNZeroDataSize){
result = @"zero_size_file";
} else if (self.statusCode == kQNFileError){
result = @"invalid_file";
} else if (self.statusCode == kQNInvalidToken
|| self.statusCode == kQNInvalidArgument){
result = @"invalid_args";
}
if (result == nil) {
result = [self requestReportErrorType];
}
return result;
}
@end
//MARK:--
NSString * const QNReportLogTypeRequest = @"request";
NSString * const QNReportLogTypeBlock = @"block";
NSString * const QNReportLogTypeQuality = @"quality";
//MARK:--
NSString * const QNReportRequestKeyLogType = @"log_type";
NSString * const QNReportRequestKeyUpTime = @"up_time";
NSString * const QNReportRequestKeyStatusCode = @"status_code";
NSString * const QNReportRequestKeyRequestId = @"req_id";
NSString * const QNReportRequestKeyHost = @"host";
NSString * const QNReportRequestKeyHttpVersion = @"http_version";
NSString * const QNReportRequestKeyRemoteIp = @"remote_ip";
NSString * const QNReportRequestKeyPort = @"port";
NSString * const QNReportRequestKeyTargetBucket = @"target_bucket";
NSString * const QNReportRequestKeyTargetKey = @"target_key";
NSString * const QNReportRequestKeyTotalElapsedTime = @"total_elapsed_time";
NSString * const QNReportRequestKeyDnsElapsedTime = @"dns_elapsed_time";
NSString * const QNReportRequestKeyConnectElapsedTime = @"connect_elapsed_time";
NSString * const QNReportRequestKeyTLSConnectElapsedTime = @"tls_connect_elapsed_time";
NSString * const QNReportRequestKeyRequestElapsedTime = @"request_elapsed_time";
NSString * const QNReportRequestKeyWaitElapsedTime = @"wait_elapsed_time";
NSString * const QNReportRequestKeyResponseElapsedTime = @"response_elapsed_time";
NSString * const QNReportRequestKeyFileOffset = @"file_offset";
NSString * const QNReportRequestKeyBytesSent = @"bytes_sent";
NSString * const QNReportRequestKeyBytesTotal = @"bytes_total";
NSString * const QNReportRequestKeyPid = @"pid";
NSString * const QNReportRequestKeyTid = @"tid";
NSString * const QNReportRequestKeyTargetRegionId = @"target_region_id";
NSString * const QNReportRequestKeyCurrentRegionId = @"current_region_id";
NSString * const QNReportRequestKeyErrorType = @"error_type";
NSString * const QNReportRequestKeyErrorDescription = @"error_description";
NSString * const QNReportRequestKeyUpType = @"up_type";
NSString * const QNReportRequestKeyOsName = @"os_name";
NSString * const QNReportRequestKeyOsVersion = @"os_version";
NSString * const QNReportRequestKeySDKName = @"sdk_name";
NSString * const QNReportRequestKeySDKVersion = @"sdk_version";
NSString * const QNReportRequestKeyClientTime = @"client_time";
NSString * const QNReportRequestKeyHttpClient = @"http_client";
NSString * const QNReportRequestKeyNetworkType = @"network_type";
NSString * const QNReportRequestKeySignalStrength = @"signal_strength";
NSString * const QNReportRequestKeyPrefetchedDnsSource = @"prefetched_dns_source";
NSString * const QNReportRequestKeyDnsSource = @"dns_source";
NSString * const QNReportRequestKeyDnsErrorMessage = @"dns_error_message";
NSString * const QNReportRequestKeyPrefetchedBefore = @"prefetched_before";
NSString * const QNReportRequestKeyPrefetchedErrorMessage = @"prefetched_error_message";
NSString * const QNReportRequestKeyNetworkMeasuring = @"network_measuring";
NSString * const QNReportRequestKeyPerceptiveSpeed = @"perceptive_speed";
NSString * const QNReportRequestKeyHijacking = @"hijacking";
//MARK:--
NSString * const QNReportBlockKeyLogType = @"log_type";
NSString * const QNReportBlockKeyUpTime = @"up_time";
NSString * const QNReportBlockKeyTargetBucket = @"target_bucket";
NSString * const QNReportBlockKeyTargetKey = @"target_key";
NSString * const QNReportBlockKeyTargetRegionId = @"target_region_id";
NSString * const QNReportBlockKeyCurrentRegionId = @"current_region_id";
NSString * const QNReportBlockKeyTotalElapsedTime = @"total_elapsed_time";
NSString * const QNReportBlockKeyBytesSent = @"bytes_sent";
NSString * const QNReportBlockKeyRecoveredFrom = @"recovered_from";
NSString * const QNReportBlockKeyFileSize = @"file_size";
NSString * const QNReportBlockKeyPid = @"pid";
NSString * const QNReportBlockKeyTid = @"tid";
NSString * const QNReportBlockKeyUpApiVersion = @"up_api_version";
NSString * const QNReportBlockKeyClientTime = @"client_time";
NSString * const QNReportBlockKeyOsName = @"os_name";
NSString * const QNReportBlockKeyOsVersion = @"os_version";
NSString * const QNReportBlockKeySDKName = @"sdk_name";
NSString * const QNReportBlockKeySDKVersion = @"sdk_version";
NSString * const QNReportBlockKeyPerceptiveSpeed = @"perceptive_speed";
NSString * const QNReportBlockKeyHijacking = @"hijacking";
//MARK:--
NSString * const QNReportQualityKeyLogType = @"log_type";
NSString * const QNReportQualityKeyUpType = @"up_type";
NSString * const QNReportQualityKeyUpTime = @"up_time";
NSString * const QNReportQualityKeyResult = @"result";
NSString * const QNReportQualityKeyTargetBucket = @"target_bucket";
NSString * const QNReportQualityKeyTargetKey = @"target_key";
NSString * const QNReportQualityKeyTotalElapsedTime = @"total_elapsed_time";
NSString * const QNReportQualityKeyUcQueryElapsedTime = @"uc_query_elapsed_time";
NSString * const QNReportQualityKeyRequestsCount = @"requests_count";
NSString * const QNReportQualityKeyRegionsCount = @"regions_count";
NSString * const QNReportQualityKeyBytesSent = @"bytes_sent";
NSString * const QNReportQualityKeyFileSize = @"file_size";
NSString * const QNReportQualityKeyCloudType = @"cloud_type";
NSString * const QNReportQualityKeyErrorType = @"error_type";
NSString * const QNReportQualityKeyErrorDescription = @"error_description";
NSString * const QNReportQualityKeyOsName = @"os_name";
NSString * const QNReportQualityKeyOsVersion = @"os_version";
NSString * const QNReportQualityKeySDKName = @"sdk_name";
NSString * const QNReportQualityKeySDKVersion = @"sdk_version";
NSString * const QNReportQualityKeyPerceptiveSpeed = @"perceptive_speed";
NSString * const QNReportQualityKeyHijacking = @"hijacking";

View File

@@ -0,0 +1,36 @@
//
// QNUploadInfoReporter.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/6/24.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <UIKit/UIKit.h>
#endif
#define kQNReporter [QNUploadInfoReporter sharedInstance]
@interface QNUploadInfoReporter : NSObject
- (id)init __attribute__((unavailable("Use sharedInstance: instead.")));
+ (instancetype)sharedInstance;
/**
* 上报统计信息
*
* @param jsonString 需要记录的json字符串
* @param token 上传凭证
*
*/
- (void)report:(NSString *)jsonString token:(NSString *)token;
/**
* 清空统计信息
*/
- (void)clean;
@end

View File

@@ -0,0 +1,242 @@
//
// QNUploadInfoReporter.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/6/24.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNZoneInfo.h"
#import "QNUploadInfoReporter.h"
#import "QNResponseInfo.h"
#import "QNUtils.h"
#import "QNFile.h"
#import "QNUpToken.h"
#import "QNUserAgent.h"
#import "QNAsyncRun.h"
#import "QNVersion.h"
#import "QNReportConfig.h"
#import "NSData+QNGZip.h"
#import "QNTransactionManager.h"
#import "QNRequestTransaction.h"
#define kQNUplogDelayReportTransactionName @"com.qiniu.uplog"
@interface QNUploadInfoReporter ()
@property (nonatomic, strong) QNReportConfig *config;
@property (nonatomic, assign) NSTimeInterval lastReportTime;
@property (nonatomic, strong) NSString *recorderFilePath;
@property (nonatomic, strong) NSString *recorderTempFilePath;
@property (nonatomic, copy) NSString *X_Log_Client_Id;
@property (nonatomic, strong) QNRequestTransaction *transaction;
@property (nonatomic, assign) BOOL isReporting;
@property (nonatomic, strong) dispatch_queue_t recordQueue;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation QNUploadInfoReporter
+ (instancetype)sharedInstance {
static QNUploadInfoReporter *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_config = [QNReportConfig sharedInstance];
_lastReportTime = 0;
_recorderFilePath = [NSString stringWithFormat:@"%@/%@", _config.recordDirectory, @"qiniu.log"];
_recorderTempFilePath = [NSString stringWithFormat:@"%@/%@", _config.recordDirectory, @"qiniuTemp.log"];
_recordQueue = dispatch_queue_create("com.qiniu.reporter", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)clean {
[self cleanRecorderFile];
[self cleanTempRecorderFile];
}
- (void)cleanRecorderFile {
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:_recorderFilePath]) {
NSError *error = nil;
[manager removeItemAtPath:_recorderFilePath error:&error];
if (error) {
NSLog(@"remove recorder file failed: %@", error);
return;
}
}
}
- (void)cleanTempRecorderFile {
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:_recorderTempFilePath]) {
NSError *error = nil;
[manager removeItemAtPath:_recorderTempFilePath error:&error];
if (error) {
NSLog(@"remove recorder temp file failed: %@", error);
return;
}
}
}
- (BOOL)checkReportAvailable {
if (!_config.isReportEnable) {
return NO;
}
if (_config.maxRecordFileSize <= _config.uploadThreshold) {
NSLog(@"maxRecordFileSize must be larger than uploadThreshold");
return NO;
}
return YES;
}
- (void)report:(NSString *)jsonString token:(NSString *)token {
if (![self checkReportAvailable] || !jsonString || !token || token.length == 0) {
return;
}
//
dispatch_async(self.recordQueue, ^{
[kQNReporter saveReportJsonString:jsonString];
[kQNReporter reportToServerIfNeeded:token];
});
}
- (void)saveReportJsonString:(NSString *)jsonString {
NSString *finalRecordInfo = [jsonString stringByAppendingString:@"\n"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:self.recorderFilePath]) {
// recordFile
[finalRecordInfo writeToFile:_recorderFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
} else {
NSDictionary *recorderFileAttr = [fileManager attributesOfItemAtPath:self.recorderFilePath error:nil];
if ([recorderFileAttr fileSize] > self.config.maxRecordFileSize) {
return;
}
NSFileHandle *fileHandler = nil;
@try {
// recorder
fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:_recorderFilePath];
[fileHandler seekToEndOfFile];
[fileHandler writeData: [finalRecordInfo dataUsingEncoding:NSUTF8StringEncoding]];
} @catch (NSException *exception) {
NSLog(@"NSFileHandle cannot write data: %@", exception.description);
} @finally {
[fileHandler closeFile];
}
}
}
- (void)reportToServerIfNeeded:(NSString *)tokenString {
BOOL needToReport = NO;
long currentTime = [[NSDate date] timeIntervalSince1970];
long interval = self.config.interval * 10;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *recorderFileAttr = [fileManager attributesOfItemAtPath:self.recorderFilePath error:nil];
if ([fileManager fileExistsAtPath:self.recorderTempFilePath]) {
needToReport = YES;
} else if ((self.lastReportTime == 0 || (currentTime - self.lastReportTime) >= interval || [recorderFileAttr fileSize] > self.config.uploadThreshold) &&
([fileManager moveItemAtPath:self.recorderFilePath toPath:self.recorderTempFilePath error:nil])) {
needToReport = YES;
}
if (needToReport && !self.isReporting) {
[self reportToServer:tokenString];
} else {
// interval
if (![fileManager fileExistsAtPath:self.recorderFilePath] || [recorderFileAttr fileSize] == 0) {
return;
}
NSArray *transactionList = [kQNTransactionManager transactionsForName:kQNUplogDelayReportTransactionName];
if (transactionList != nil && transactionList.count > 1) {
return;
}
if (transactionList != nil && transactionList.count == 1) {
QNTransaction *transaction = transactionList.firstObject;
if (transaction != nil && !transaction.isExecuting) {
return;
}
}
QNTransaction *transaction = [QNTransaction transaction:kQNUplogDelayReportTransactionName after:interval action:^{
[kQNReporter reportToServerIfNeeded:tokenString];
}];
[kQNTransactionManager addTransaction:transaction];
}
}
- (void)reportToServer:(NSString *)tokenString {
if (tokenString == nil) {
return;
}
QNUpToken *token = [QNUpToken parse:tokenString];
if (!token.isValid) {
return;
}
NSData *logData = [self getLogData];
if (logData == nil) {
return;
}
self.isReporting = YES;
logData = [NSData qn_gZip:logData];
QNRequestTransaction *transaction = [self createUploadRequestTransaction:token];
[transaction reportLog:logData logClientId:self.X_Log_Client_Id complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
if (responseInfo.isOK) {
self.lastReportTime = [[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970];
if (!self.X_Log_Client_Id) {
self.X_Log_Client_Id = responseInfo.responseHeader[@"x-log-client-id"];
}
[self cleanTempRecorderFile];
} else {
NSLog(@"upload info report failed: %@", responseInfo);
}
self.isReporting = NO;
[self destroyUploadRequestTransaction:transaction];
}];
}
- (NSData *)getLogData {
return [NSData dataWithContentsOfFile:_recorderTempFilePath];
}
- (QNRequestTransaction *)createUploadRequestTransaction:(QNUpToken *)token{
if (self.config.serverURL) {
}
NSArray *hosts = nil;
if (self.config.serverHost) {
hosts = @[self.config.serverHost];
}
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithHosts:hosts
regionId:QNZoneInfoEmptyRegionId
token:token];
self.transaction = transaction;
return transaction;
}
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction{
self.transaction = nil;
}
@end

27
Pods/Qiniu/QiniuSDK/Common/QNAutoZone.h generated Normal file
View File

@@ -0,0 +1,27 @@
//
// QNAutoZone.h
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNZone.h"
NS_ASSUME_NONNULL_BEGIN
@class QNFixedZone;
@interface QNAutoZone : QNZone
+ (instancetype)zoneWithUcHosts:(NSArray *)ucHosts;
+ (void)clearCache;
/**
* 当 查询失败时,会使用 zones 进行上传,默认不配置。
*/
- (void)setDefaultZones:(NSArray <QNFixedZone *> *)zones;
@end
NS_ASSUME_NONNULL_END

254
Pods/Qiniu/QiniuSDK/Common/QNAutoZone.m generated Normal file
View File

@@ -0,0 +1,254 @@
//
// QNAutoZone.m
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNCache.h"
#import "QNUtils.h"
#import "QNAutoZone.h"
#import "QNConfig.h"
#import "QNRequestTransaction.h"
#import "QNZoneInfo.h"
#import "QNUpToken.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
#import "QNFixedZone.h"
#import "QNSingleFlight.h"
#import "QNFileRecorder.h"
#import "QNUrlSafeBase64.h"
#import "QNUploadRequestMetrics.h"
@interface QNUCQuerySingleFlightValue : NSObject
@property(nonatomic, strong) QNResponseInfo *responseInfo;
@property(nonatomic, strong) NSDictionary *response;
@property(nonatomic, strong) QNUploadRegionRequestMetrics *metrics;
@end
@implementation QNUCQuerySingleFlightValue
@end
@interface QNAutoZone ()
@property(nonatomic, strong) NSArray *ucHosts;
@property(nonatomic, strong) QNFixedZone *defaultZone;
//
@property(nonatomic, strong) NSMutableDictionary <NSString *, QNZonesInfo *> *zonesDic;
@property(nonatomic, strong) NSMutableArray <QNRequestTransaction *> *transactions;
@end
@implementation QNAutoZone
+ (QNSingleFlight *)UCQuerySingleFlight {
static QNSingleFlight *singleFlight = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleFlight = [[QNSingleFlight alloc] init];
});
return singleFlight;
}
+ (instancetype)zoneWithUcHosts:(NSArray *)ucHosts {
QNAutoZone *zone = [[self alloc] init];
zone.ucHosts = [ucHosts copy];
return zone;
}
+ (QNCache *)zoneShareCache {
static QNCache *queryCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
QNCacheOption *option = [[QNCacheOption alloc] init];
option.version = @"v2";
queryCache = [QNCache cache:[QNZonesInfo class] option:option];
});
return queryCache;
}
+ (void)clearCache {
[[QNAutoZone zoneShareCache] clearMemoryCache];
[[QNAutoZone zoneShareCache] clearDiskCache];
}
- (instancetype)init {
if (self = [super init]) {
_zonesDic = [NSMutableDictionary dictionary];
_transactions = [NSMutableArray array];
}
return self;
}
- (void)setDefaultZones:(NSArray <QNFixedZone *> *)zones {
self.defaultZone = [QNFixedZone combineZones:zones];
}
- (QNZonesInfo *)getZonesInfoWithToken:(QNUpToken *_Nullable)token {
if (token == nil) return nil;
NSString *cacheKey = [self makeCacheKey:[QNConfiguration defaultConfiguration] akAndBucket:token.index];
QNZonesInfo *zonesInfo = nil;
@synchronized (self) {
zonesInfo = self.zonesDic[cacheKey];
}
zonesInfo = [zonesInfo copy];
return zonesInfo;
}
- (void)setZonesInfo:(QNZonesInfo *)info forKey:(NSString *)key {
if (info == nil) {
return;
}
@synchronized (self) {
self.zonesDic[key] = info;
}
}
- (void)preQuery:(QNUpToken *)token on:(QNPrequeryReturn)ret {
[self query:[QNConfiguration defaultConfiguration] token:token on:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, QNZonesInfo * _Nullable zonesInfo) {
if (!ret) {
return;
}
if (responseInfo.isOK) {
ret(0, responseInfo, metrics);
} else {
ret(responseInfo.statusCode, responseInfo, metrics);
}
}];
}
- (void)query:(QNConfiguration *)config token:(QNUpToken *)token on:(QNQueryReturn)ret {
if (token == nil || ![token isValid]) {
ret([QNResponseInfo responseInfoWithInvalidToken:@"invalid token"], nil, nil);
return;
}
QNUploadRegionRequestMetrics *cacheMetrics = [QNUploadRegionRequestMetrics emptyMetrics];
[cacheMetrics start];
NSString *cacheKey = [self makeCacheKey:config akAndBucket:token.index];
QNZonesInfo *zonesInfo = [[QNAutoZone zoneShareCache] cacheForKey:cacheKey];
// zonesInfo 使
if (zonesInfo != nil && zonesInfo.isValid && !zonesInfo.isTemporary) {
[cacheMetrics end];
[self setZonesInfo:zonesInfo forKey:cacheKey];
ret([QNResponseInfo successResponse], cacheMetrics, zonesInfo);
return;
}
kQNWeakSelf;
QNSingleFlight *singleFlight = [QNAutoZone UCQuerySingleFlight];
[singleFlight perform:token.index action:^(QNSingleFlightComplete _Nonnull complete) {
kQNStrongSelf;
QNRequestTransaction *transaction = [self createUploadRequestTransaction:config token:token];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction queryUploadHosts:^(QNResponseInfo *_Nullable responseInfo, QNUploadRegionRequestMetrics *_Nullable metrics, NSDictionary *_Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
QNUCQuerySingleFlightValue *value = [[QNUCQuerySingleFlightValue alloc] init];
value.responseInfo = responseInfo;
value.response = response;
value.metrics = metrics;
complete(value, nil);
[self destroyUploadRequestTransaction:transaction];
}];
} complete:^(id _Nullable value, NSError *_Nullable error) {
kQNStrongSelf;
QNResponseInfo *responseInfo = [(QNUCQuerySingleFlightValue *) value responseInfo];
NSDictionary *response = [(QNUCQuerySingleFlightValue *) value response];
QNUploadRegionRequestMetrics *metrics = [(QNUCQuerySingleFlightValue *) value metrics];
if (responseInfo && responseInfo.isOK) {
QNZonesInfo *zonesInfo = [QNZonesInfo infoWithDictionary:response];
if ([zonesInfo isValid]) {
[self setZonesInfo:zonesInfo forKey:cacheKey];
[[QNAutoZone zoneShareCache] cache:zonesInfo forKey:cacheKey atomically:false];
ret(responseInfo, metrics, zonesInfo);
} else {
responseInfo = [QNResponseInfo errorResponseInfo:NSURLErrorCannotDecodeRawData errorDesc:[NSString stringWithFormat:@"origin response:%@", responseInfo]];
ret(responseInfo, metrics, nil);
}
} else {
if (self.defaultZone != nil) {
//
QNZonesInfo *info = [self.defaultZone getZonesInfoWithToken:token];
[self setZonesInfo:info forKey:cacheKey];
responseInfo = [QNResponseInfo successResponseWithDesc:[NSString stringWithFormat:@"origin response:%@", responseInfo]];
ret(responseInfo, metrics, info);
} else if (zonesInfo != nil) {
// 使
[self setZonesInfo:zonesInfo forKey:cacheKey];
responseInfo = [QNResponseInfo successResponseWithDesc:[NSString stringWithFormat:@"origin response:%@", responseInfo]];
ret(responseInfo, metrics, zonesInfo);
} else {
ret(responseInfo, metrics, nil);
}
}
}];
}
- (QNRequestTransaction *)createUploadRequestTransaction:(QNConfiguration *)config token:(QNUpToken *)token {
if (config == nil) {
config = [QNConfiguration defaultConfiguration];
}
NSArray *hosts = nil;
if (self.ucHosts && self.ucHosts.count > 0) {
hosts = [self.ucHosts copy];
} else {
hosts = kQNPreQueryHosts;
}
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithConfig:config
uploadOption:[QNUploadOption defaultOptions]
hosts:hosts
regionId:QNZoneInfoEmptyRegionId
key:@""
token:token];
@synchronized (self) {
[self.transactions addObject:transaction];
}
return transaction;
}
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction {
if (transaction) {
@synchronized (self) {
[self.transactions removeObject:transaction];
}
}
}
- (NSString *)makeCacheKey:(QNConfiguration *)config akAndBucket:(NSString *)akAndBucket {
NSString *key = akAndBucket;
if (config != nil) {
key = [NSString stringWithFormat:@"%@:%d",key, config.accelerateUploading];
}
NSString *hosts = @"";
for (NSString *host in self.ucHosts) {
if (!host) {
continue;
}
hosts = [NSString stringWithFormat:@"%@:%@", hosts, host];
}
NSString *cacheKey = [NSString stringWithFormat:@"%@:%@", hosts, key];
return [QNUrlSafeBase64 encodeString:cacheKey];
}
@end

18
Pods/Qiniu/QiniuSDK/Common/QNConfig.h generated Normal file
View File

@@ -0,0 +1,18 @@
//
// QNConfig.h
// QiniuSDK
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
//MARK: -- 内部布置 尽量不要修改
#define kQNPreQueryHost00 @"uc.qiniuapi.com"
#define kQNPreQueryHost01 @"kodo-config.qiniuapi.com"
#define kQNPreQueryHost02 @"uc.qbox.me"
#define kQNPreQueryHost03 @"api.qiniu.com"
#define kQNPreQueryHosts @[kQNPreQueryHost00, kQNPreQueryHost01, kQNPreQueryHost02]
#define kQNUpLogHost @"uplog.qbox.me"

68
Pods/Qiniu/QiniuSDK/Common/QNErrorCode.h generated Normal file
View File

@@ -0,0 +1,68 @@
//
// QNErrorCode.h
// QiniuSDK
//
// Created by yangsen on 2020/10/21.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* StatusCode >= 100 见https://developer.qiniu.com/kodo/3928/error-responses
* 除上述链接及下面定义外的状态码依据 iOS 标准库定义
*/
/**
* 中途取消的状态码
*/
extern const int kQNRequestCancelled;
/**
* 网络错误状态码
*/
extern const int kQNNetworkError;
/**
* 错误参数状态码
*/
extern const int kQNInvalidArgument;
/**
* 0 字节文件或数据
*/
extern const int kQNZeroDataSize;
/**
* 错误token状态码
*/
extern const int kQNInvalidToken;
/**
* 读取文件错误状态码
*/
extern const int kQNFileError;
/**
* 本地 I/O 错误
*/
extern const int kQNLocalIOError;
/**
* ⽤户劫持错误 错误
*/
extern const int kQNMaliciousResponseError;
/**
* 没有可用的Host 错误【废弃】
*/
extern const int kQNNoUsableHostError NS_UNAVAILABLE;
/**
* SDK 内部错误
*/
extern const int kQNSDKInteriorError;
/**
* 非预期的系统调用 错误
*/
extern const int kQNUnexpectedSysCallError;

25
Pods/Qiniu/QiniuSDK/Common/QNErrorCode.m generated Normal file
View File

@@ -0,0 +1,25 @@
//
// QNErrorCode.m
// QiniuSDK
//
// Created by yangsen on 2020/10/21.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNErrorCode.h"
const int kQNUnexpectedSysCallError = -10;
const int kQNNoUsableHostError = -9;
const int kQNSDKInteriorError = -9;
const int kQNMaliciousResponseError = -8;
const int kQNLocalIOError = -7;
const int kQNZeroDataSize = -6;
const int kQNInvalidToken = -5;
const int kQNFileError = -4;
const int kQNInvalidArgument = -3;
const int kQNRequestCancelled = -2;
const int kQNNetworkError = -1;

112
Pods/Qiniu/QiniuSDK/Common/QNFixedZone.h generated Normal file
View File

@@ -0,0 +1,112 @@
//
// QNFixZone.h
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNZone.h"
#import "QNDefine.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNFixedZone : QNZone
/**
* zone 0 华东
*
* @return 实例
*/
+ (instancetype)zone0 kQNDeprecated("use createWithRegionId instead");
/**
* zoneCnEast2 华东-浙江2
*
* @return 实例
*/
+ (instancetype)zoneCnEast2 kQNDeprecated("use createWithRegionId instead");
/**
* zone 1 华北
*
* @return 实例
*/
+ (instancetype)zone1 kQNDeprecated("use createWithRegionId instead");
/**
* zone 2 华南
*
* @return 实例
*/
+ (instancetype)zone2 kQNDeprecated("use createWithRegionId instead");
/**
* zone Na0 北美
*
* @return 实例
*/
+ (instancetype)zoneNa0 kQNDeprecated("use createWithRegionId instead");
/**
* zone As0 新加坡
*
* @return 实例
*/
+ (instancetype)zoneAs0 kQNDeprecated("use createWithRegionId instead");
/**
* Zone初始化方法
*
* @param upList 默认上传服务器地址列表
* @return Zone实例
*/
- (instancetype)initWithUpDomainList:(NSArray<NSString *> *)upList;
/**
* Zone初始化方法
*
* @param accUpList 加速上传服务器地址列表
* @param upList 默认上传服务器地址列表
* @param oldUpList 支持 SNI 的上传服务器地址列表
* @param regionId 区域 ID
* @return Zone实例
*/
- (instancetype)initWithAccUpDomainList:(NSArray<NSString *> *)accUpList
upList:(NSArray<NSString *> *)upList
oldUpList:(NSArray<NSString *> *)oldUpList
regionId:(NSString *)regionId;
/**
* Zone初始化方法
*
* @param upList 默认上传服务器地址列表
*
* @return Zone实例
*/
+ (instancetype)createWithHost:(NSArray<NSString *> *)upList;
/**
* Zone初始化方法
* regionId 参考链接https://developer.qiniu.com/kodo/1671/region-endpoint-fq
*
* @param regionId 根据区域 ID 创建 Zone
*
* @return Zone 实例
*/
+ (instancetype)createWithRegionId:(NSString *)regionId;
/**
* 获取本地所有固定zone信息
*/
+ (QNFixedZone *)localsZoneInfo DEPRECATED_ATTRIBUTE;
/**
* 合并区域
*/
+ (QNFixedZone *)combineZones:(NSArray<QNFixedZone *> *)zones;
@end
NS_ASSUME_NONNULL_END

199
Pods/Qiniu/QiniuSDK/Common/QNFixedZone.m generated Normal file
View File

@@ -0,0 +1,199 @@
//
// QNFixZone.m
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNFixedZone.h"
#import "QNZoneInfo.h"
#import "QNResponseInfo.h"
@interface QNFixedZone ()
@property (nonatomic, strong) QNZonesInfo *zonesInfo;
@end
@implementation QNFixedZone
+ (instancetype)zone0 {
static QNFixedZone *z0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
z0 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload.qiniup.com", @"up.qiniup.com"]
oldUpList:@[@"upload.qbox.me", @"up.qbox.me"]
regionId:@"z0"];
});
return z0;
}
+ (instancetype)zoneCnEast2 {
static QNFixedZone *zoneCnEast2 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
zoneCnEast2 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload-cn-east-2.qiniup.com", @"up-cn-east-2.qiniup.com"]
oldUpList:nil
regionId:@"cn-east-2"];
});
return zoneCnEast2;
}
+ (instancetype)zone1 {
static QNFixedZone *z1 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
z1 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload-z1.qiniup.com", @"up-z1.qiniup.com"]
oldUpList:@[@"upload-z1.qbox.me", @"up-z1.qbox.me"]
regionId:@"z1"];
});
return z1;
}
+ (instancetype)zone2 {
static QNFixedZone *z2 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
z2 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload-z2.qiniup.com", @"up-z2.qiniup.com"]
oldUpList:@[@"upload-z2.qbox.me", @"up-z2.qbox.me"]
regionId:@"z2"];
});
return z2;
}
+ (instancetype)zoneNa0 {
static QNFixedZone *zNa0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
zNa0 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload-na0.qiniup.com", @"up-na0.qiniup.com"]
oldUpList:@[@"upload-na0.qbox.me", @"up-na0.qbox.me"]
regionId:@"na0"];
});
return zNa0;
}
+ (instancetype)zoneAs0 {
static QNFixedZone *zAs0 = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
zAs0 = [[QNFixedZone alloc] initWithUpDomainList:@[@"upload-as0.qiniup.com", @"up-as0.qiniup.com"]
oldUpList:@[@"upload-as0.qbox.me", @"up-as0.qbox.me"]
regionId:@"as0"];;
});
return zAs0;
}
+ (QNFixedZone *)localsZoneInfo{
NSArray *zones = @[[QNFixedZone zone0],
[QNFixedZone zone1],
[QNFixedZone zone2],
[QNFixedZone zoneCnEast2],
[QNFixedZone zoneNa0],
[QNFixedZone zoneAs0]];
QNFixedZone *zone = [self combineZones:zones];
if (zone) {
[zone.zonesInfo toTemporary];
}
return zone;
}
+ (QNFixedZone *)combineZones:(NSArray<QNFixedZone *> *)zones {
if (zones == nil || zones.count == 0) {
return nil;
}
NSMutableArray <QNZoneInfo *> *zoneInfoArray = [NSMutableArray array];
for (QNFixedZone *zone in zones) {
if (zone.zonesInfo.zonesInfo) {
[zoneInfoArray addObjectsFromArray:zone.zonesInfo.zonesInfo];
}
}
QNFixedZone *fixedZone = [[QNFixedZone alloc] init];
fixedZone.zonesInfo = [[QNZonesInfo alloc] initWithZonesInfo:[zoneInfoArray copy]];
[fixedZone.zonesInfo toTemporary];
return fixedZone;
}
+ (instancetype)createWithHost:(NSArray<NSString *> *)upList {
return [[QNFixedZone alloc] initWithUpDomainList:upList oldUpList:nil regionId:nil];
}
+ (instancetype)createWithRegionId:(NSString *)regionId {
NSArray *upList = @[
[NSString stringWithFormat:@"upload-%@.qiniup.com", regionId],
[NSString stringWithFormat:@"up-%@.qiniup.com", regionId],
];
return [[QNFixedZone alloc] initWithUpDomainList:upList oldUpList:nil regionId:regionId];
}
- (QNZonesInfo *)createZonesInfo:(NSArray <NSString *> *)upDomains
regionId:(NSString *)regionId {
return [self createZonesInfo:upDomains oldUpDomains:nil regionId:regionId];
}
- (QNZonesInfo *)createZonesInfo:(NSArray <NSString *> *)upDomains
oldUpDomains:(NSArray <NSString *> *)oldUpDomains
regionId:(NSString *)regionId {
return [self createZonesInfo:nil domains:upDomains oldDomains:oldUpDomains regionId:regionId];
}
- (QNZonesInfo *)createZonesInfo:(NSArray <NSString *> *)accDomains
domains:(NSArray <NSString *> *)domains
oldDomains:(NSArray <NSString *> *)oldDomains
regionId:(NSString *)regionId {
if ((!accDomains || accDomains.count == 0) && (!domains || domains.count == 0)) {
return nil;
}
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoWithAccHosts:accDomains
mainHosts:domains
oldHosts:oldDomains
regionId:regionId];
QNZonesInfo *zonesInfo = [[QNZonesInfo alloc] initWithZonesInfo:@[zoneInfo]];
return zonesInfo;
}
- (instancetype)initWithUpDomainList:(NSArray<NSString *> *)upList {
if (self = [super init]) {
self.zonesInfo = [self createZonesInfo:upList regionId:nil];
}
return self;
}
- (instancetype)initWithUpDomainList:(NSArray<NSString *> *)upList
regionId:(NSString *)regionId {
if (self = [super init]) {
self.zonesInfo = [self createZonesInfo:upList regionId:regionId];
}
return self;
}
- (instancetype)initWithUpDomainList:(NSArray<NSString *> *)upList
oldUpList:(NSArray<NSString *> *)oldUpList
regionId:(NSString *)regionId {
if (self = [super init]) {
self.zonesInfo = [self createZonesInfo:upList oldUpDomains:oldUpList regionId:regionId];
}
return self;
}
- (instancetype)initWithAccUpDomainList:(NSArray<NSString *> *)accUpList
upList:(NSArray<NSString *> *)upList
oldUpList:(NSArray<NSString *> *)oldUpList
regionId:(NSString *)regionId {
if (self = [super init]) {
self.zonesInfo = [self createZonesInfo:accUpList domains:upList oldDomains:oldUpList regionId:regionId];
}
return self;
}
- (QNZonesInfo *)getZonesInfoWithToken:(QNUpToken *)token {
return self.zonesInfo;
}
- (void)query:(QNConfiguration * _Nullable)config token:(QNUpToken * _Nullable)token on:(QNQueryReturn _Nullable)ret {
ret([QNResponseInfo successResponse], nil, self.zonesInfo);
}
@end

38
Pods/Qiniu/QiniuSDK/Common/QNZone.h generated Normal file
View File

@@ -0,0 +1,38 @@
//
// QNZone.h
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class QNConfiguration, QNResponseInfo, QNUpToken, QNZonesInfo, QNUploadRegionRequestMetrics;
typedef void (^QNPrequeryReturn)(int code, QNResponseInfo * _Nullable httpResponseInfo, QNUploadRegionRequestMetrics * _Nullable metrics);
typedef void (^QNQueryReturn)(QNResponseInfo * _Nullable httpResponseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, QNZonesInfo * _Nullable zonesInfo);
@interface QNZone : NSObject
/// 根据token查询相关 Zone 信息【内部使用】
/// @param token token 信息
/// @param ret 查询回调
- (void)preQuery:(QNUpToken * _Nullable)token on:(QNPrequeryReturn _Nullable)ret;
/// 根据token获取ZonesInfo 【内部使用】
/// @param token token信息
- (QNZonesInfo *)getZonesInfoWithToken:(QNUpToken * _Nullable)token;
/// 根据token查询相关 Zone 信息【内部使用】
/// @param config 配置信息
/// @param token token 信息
/// @param ret 查询回调
- (void)query:(QNConfiguration * _Nullable)config token:(QNUpToken * _Nullable)token on:(QNQueryReturn _Nullable)ret;
@end
NS_ASSUME_NONNULL_END

30
Pods/Qiniu/QiniuSDK/Common/QNZone.m generated Normal file
View File

@@ -0,0 +1,30 @@
//
// QNZone.m
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNZone.h"
#import "QNUpToken.h"
#import "QNZoneInfo.h"
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
@implementation QNZone
- (QNZonesInfo *)getZonesInfoWithToken:(QNUpToken *)token {
return nil;
}
- (void)preQuery:(QNUpToken *)token
on:(QNPrequeryReturn)ret {
ret(0, nil, nil);
}
- (void)query:(QNConfiguration * _Nullable)config token:(QNUpToken * _Nullable)token on:(QNQueryReturn _Nullable)ret {
ret([QNResponseInfo responseInfoWithSDKInteriorError:@"impl query"], nil, nil);
}
@end

68
Pods/Qiniu/QiniuSDK/Common/QNZoneInfo.h generated Normal file
View File

@@ -0,0 +1,68 @@
//
// QNZoneInfo.h
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNCache.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const QNZoneInfoSDKDefaultIOHost;
extern NSString *const QNZoneInfoEmptyRegionId;
@interface QNZoneInfo : NSObject
@property(nonatomic, assign, readonly)long ttl;
@property(nonatomic, assign, readonly)BOOL http3Enabled;
@property(nonatomic, strong, readonly)NSArray<NSString *> *domains;
@property(nonatomic, strong, readonly)NSArray<NSString *> *acc_domains;
@property(nonatomic, strong, readonly)NSArray<NSString *> *old_domains;
@property(nonatomic, copy, readonly)NSString *regionId;
@property(nonatomic, strong, readonly)NSArray <NSString *> *allHosts;
@property(nonatomic, strong, readonly)NSDictionary *detailInfo;
+ (QNZoneInfo *)zoneInfoWithMainHosts:(NSArray <NSString *> *)mainHosts
regionId:(NSString * _Nullable)regionId;
+ (QNZoneInfo *)zoneInfoWithMainHosts:(NSArray <NSString *> *)mainHosts
oldHosts:(NSArray <NSString *> * _Nullable)oldHosts
regionId:(NSString * _Nullable)regionId;
+ (QNZoneInfo *)zoneInfoWithAccHosts:(NSArray <NSString *> *)accHosts
mainHosts:(NSArray <NSString *> *)mainHosts
oldHosts:(NSArray <NSString *> * _Nullable)oldHosts
regionId:(NSString * _Nullable)regionId;
/// 根据键值对构造对象 【内部使用】
/// @param detailInfo 键值对信息
+ (QNZoneInfo *)zoneInfoFromDictionary:(NSDictionary *)detailInfo;
- (BOOL)isValid;
@end
@interface QNZonesInfo : NSObject <QNCacheObject>
@property (nonatomic, strong, readonly) NSDate *buildDate;
@property (nonatomic, assign, readonly) BOOL isTemporary;
@property (nonatomic, assign, readonly) BOOL isValid;
@property (nonatomic, strong, readonly) NSArray<QNZoneInfo *> *zonesInfo;
/// 根据键值对构造对象 【内部使用】
/// @param dictionary 键值对信息
+ (instancetype)infoWithDictionary:(NSDictionary *)dictionary;
- (instancetype)initWithZonesInfo:(NSArray<QNZoneInfo *> *)zonesInfo;
// 转成临时 zones, 临时 Zones不建议长期使用
- (void)toTemporary;
@end
NS_ASSUME_NONNULL_END

250
Pods/Qiniu/QiniuSDK/Common/QNZoneInfo.m generated Normal file
View File

@@ -0,0 +1,250 @@
//
// QNZoneInfo.m
// QiniuSDK
//
// Created by yangsen on 2020/4/16.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNZoneInfo.h"
#import "QNUtils.h"
NSString * const QNZoneInfoSDKDefaultIOHost = @"default_io_host";
NSString * const QNZoneInfoEmptyRegionId = @"none";
@interface QNZoneInfo()
@property(nonatomic, strong) NSDate *buildDate;
@property(nonatomic, copy) NSString *regionId;
@property(nonatomic, assign) long ttl;
@property(nonatomic, assign) BOOL http3Enabled;
@property(nonatomic, strong) NSArray<NSString *> *domains;
@property(nonatomic, strong) NSArray<NSString *> *acc_domains;
@property(nonatomic, strong) NSArray<NSString *> *old_domains;
@property(nonatomic, strong) NSArray <NSString *> *allHosts;
@property(nonatomic, strong) NSDictionary *detailInfo;
@end
@implementation QNZoneInfo
+ (QNZoneInfo *)zoneInfoWithMainHosts:(NSArray <NSString *> *)mainHosts
regionId:(NSString * _Nullable)regionId{
return [self zoneInfoWithMainHosts:mainHosts oldHosts:nil regionId:regionId];
}
+ (QNZoneInfo *)zoneInfoWithMainHosts:(NSArray <NSString *> *)mainHosts
oldHosts:(NSArray <NSString *> * _Nullable)oldHosts
regionId:(NSString * _Nullable)regionId{
if (!mainHosts || ![mainHosts isKindOfClass:[NSArray class]] || mainHosts.count == 0) {
return nil;
}
if (mainHosts && ![mainHosts isKindOfClass:[NSArray class]]) {
mainHosts = nil;
}
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoFromDictionary:@{@"ttl" : @(-1),
@"region" : regionId ?: QNZoneInfoEmptyRegionId,
@"up" : @{@"domains" : mainHosts ?: @[],
@"old" : oldHosts ?: @[]},
}];
return zoneInfo;
}
+ (QNZoneInfo *)zoneInfoWithAccHosts:(NSArray <NSString *> *)accHosts
mainHosts:(NSArray <NSString *> *)mainHosts
oldHosts:(NSArray <NSString *> * _Nullable)oldHosts
regionId:(NSString * _Nullable)regionId {
if ((!accHosts || ![accHosts isKindOfClass:[NSArray class]] || accHosts.count == 0) &&
(!mainHosts || ![mainHosts isKindOfClass:[NSArray class]] || mainHosts.count == 0)) {
return nil;
}
if (accHosts && ![accHosts isKindOfClass:[NSArray class]]) {
accHosts = nil;
}
if (mainHosts && ![mainHosts isKindOfClass:[NSArray class]]) {
mainHosts = nil;
}
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoFromDictionary:@{@"ttl" : @(-1),
@"region" : regionId ?: QNZoneInfoEmptyRegionId,
@"up" : @{@"acc_domains" : accHosts ?: @[],
@"domains" : mainHosts ?: @[],
@"old" : oldHosts ?: @[]},
}];
return zoneInfo;
}
+ (QNZoneInfo *)zoneInfoFromDictionary:(NSDictionary *)detail {
if (![detail isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSMutableDictionary *detailInfo = [detail mutableCopy];
if (detailInfo[@"timestamp"] == nil) {
detailInfo[@"timestamp"] = @([QNUtils currentTimestamp]/1000);
}
long timestamp = [detailInfo[@"timestamp"] longValue];
NSString *regionId = [detailInfo objectForKey:@"region"];
if (regionId == nil) {
regionId = QNZoneInfoEmptyRegionId;
}
long ttl = [[detailInfo objectForKey:@"ttl"] longValue];
BOOL http3Enabled = false;
if ([detailInfo[@"features"] isKindOfClass:[NSDictionary class]] &&
[detailInfo[@"features"][@"http3"] isKindOfClass:[NSDictionary class]]) {
http3Enabled = [detailInfo[@"features"][@"http3"][@"enabled"] boolValue];
}
NSDictionary *up = [detailInfo objectForKey:@"up"];
NSArray *acc_domains = [up objectForKey:@"acc_domains"];
NSArray *domains = [up objectForKey:@"domains"];
NSArray *old_domains = [up objectForKey:@"old"];
NSMutableArray *allHosts = [NSMutableArray array];
QNZoneInfo *zoneInfo = [[QNZoneInfo alloc] init:ttl regionId:regionId];
zoneInfo.buildDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
zoneInfo.http3Enabled = http3Enabled;
if ([acc_domains isKindOfClass:[NSArray class]]) {
zoneInfo.acc_domains = acc_domains;
[allHosts addObjectsFromArray:domains];
}
if ([domains isKindOfClass:[NSArray class]]) {
zoneInfo.domains = domains;
[allHosts addObjectsFromArray:domains];
}
if ([old_domains isKindOfClass:[NSArray class]]) {
zoneInfo.old_domains = old_domains;
[allHosts addObjectsFromArray:old_domains];
}
zoneInfo.allHosts = [allHosts copy];
zoneInfo.detailInfo = [detailInfo copy];
return zoneInfo;
}
- (instancetype)init:(long)ttl
regionId:(NSString *)regionId {
if (self = [super init]) {
_ttl = ttl;
_buildDate = [NSDate date];
_regionId = regionId;
}
return self;
}
- (BOOL)isValid{
if (self.allHosts == nil || self.allHosts.count == 0) {
return false;
}
if (self.ttl < 0) {
return true;
}
NSDate *currentDate = [NSDate date];
return self.ttl > [currentDate timeIntervalSinceDate:self.buildDate];
}
- (id)copyWithZone:(NSZone *)zone {
QNZoneInfo *zoneInfo = [[QNZoneInfo allocWithZone:zone] init];
zoneInfo.ttl = self.ttl;
zoneInfo.buildDate = self.buildDate;
zoneInfo.http3Enabled = self.http3Enabled;
zoneInfo.regionId = self.regionId;
zoneInfo.domains = [self.domains copy];
zoneInfo.old_domains = [self.old_domains copy];
zoneInfo.allHosts = [self.allHosts copy];
zoneInfo.detailInfo = [self.detailInfo copy];
return zoneInfo;
}
@end
@interface QNZonesInfo()
@property (nonatomic, strong) NSDate *buildDate;
@property (nonatomic, assign) BOOL isTemporary;
@property (nonatomic, strong) NSArray<QNZoneInfo *> *zonesInfo;
@property (nonatomic, strong) NSDictionary *detailInfo;
@end
@implementation QNZonesInfo
+ (instancetype)infoWithDictionary:(NSDictionary *)dictionary {
return [[self alloc] initWithDictionary:dictionary];
}
- (nonnull id<QNCacheObject>)initWithDictionary:(nullable NSDictionary *)dictionary {
NSMutableArray *zonesInfo = [NSMutableArray array];
NSArray *hosts = dictionary[@"hosts"];
if ([hosts isKindOfClass:[NSArray class]]) {
for (NSInteger i = 0; i < hosts.count; i++) {
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoFromDictionary:hosts[i]];
if (zoneInfo && [zoneInfo allHosts].count > 0) {
[zonesInfo addObject:zoneInfo];
}
}
}
return [self initWithZonesInfo:zonesInfo];
}
- (instancetype)initWithZonesInfo:(NSArray<QNZoneInfo *> *)zonesInfo{
self = [super init];
if (self) {
_buildDate = [NSDate date];
_zonesInfo = zonesInfo;
NSMutableArray *zoneInfos = [NSMutableArray array];
if (zonesInfo != nil) {
for (NSInteger i = 0; i < zonesInfo.count; i++) {
if (zonesInfo[i].detailInfo != nil) {
[zoneInfos addObject:zonesInfo[i].detailInfo];
}
}
}
self.detailInfo = @{@"hosts": [zoneInfos copy]};
}
return self;
}
- (void)toTemporary {
_isTemporary = true;
}
- (BOOL)isValid {
if ([self.zonesInfo count] == 0) {
return false;
}
BOOL valid = true;
for (QNZoneInfo *info in self.zonesInfo) {
if (![info isValid]) {
valid = false;
break;
}
}
return valid;
}
- (id)copyWithZone:(NSZone *)zone {
NSMutableArray *zonesInfoArray = [NSMutableArray array];
for (QNZoneInfo *info in self.zonesInfo) {
[zonesInfoArray addObject:[info copy]];
}
QNZonesInfo *zonesInfo = [[QNZonesInfo allocWithZone:zone] init];
zonesInfo.zonesInfo = [zonesInfoArray copy];
zonesInfo.isTemporary = self.isTemporary;
return zonesInfo;
}
- (nullable NSDictionary *)toDictionary {
return [self.detailInfo copy];
}
@end

View File

@@ -0,0 +1,22 @@
//
// QNConnectChecker.h
// QiniuSDK_Mac
//
// Created by yangsen on 2021/1/8.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadRequestMetrics.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNConnectChecker : NSObject
+ (QNUploadSingleRequestMetrics *)check;
+ (BOOL)isConnected:(QNUploadSingleRequestMetrics *)metrics;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,152 @@
//
// QNConnectChecker.m
// QiniuSDK_Mac
//
// Created by yangsen on 2021/1/8.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNLogUtil.h"
#import "QNConfiguration.h"
#import "QNSingleFlight.h"
#import "QNConnectChecker.h"
#import "QNUploadSystemClient.h"
@interface QNConnectChecker()
@end
@implementation QNConnectChecker
+ (QNSingleFlight *)singleFlight {
static QNSingleFlight *singleFlight = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleFlight = [[QNSingleFlight alloc] init];
});
return singleFlight;
}
+ (dispatch_queue_t)checkQueue {
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.qiniu.NetworkCheckQueue", DISPATCH_QUEUE_CONCURRENT);
});
return queue;
}
+ (BOOL)isConnected:(QNUploadSingleRequestMetrics *)metrics {
return metrics && ((NSHTTPURLResponse *)metrics.response).statusCode > 99;
}
+ (QNUploadSingleRequestMetrics *)check {
__block QNUploadSingleRequestMetrics *metrics = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self check:^(QNUploadSingleRequestMetrics *metricsP) {
metrics = metricsP;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return metrics;
}
+ (void)check:(void (^)(QNUploadSingleRequestMetrics *))complete {
QNSingleFlight *singleFlight = [self singleFlight];
kQNWeakSelf;
[singleFlight perform:@"connect_check" action:^(QNSingleFlightComplete _Nonnull singleFlightComplete) {
kQNStrongSelf;
[self checkAllHosts:^(QNUploadSingleRequestMetrics *metrics) {
singleFlightComplete(metrics, nil);
}];
} complete:^(id _Nullable value, NSError * _Nullable error) {
if (complete) {
complete(value);
}
}];
}
+ (void)checkAllHosts:(void (^)(QNUploadSingleRequestMetrics *metrics))complete {
__block int completeCount = 0;
__block BOOL isCompleted = false;
kQNWeakSelf;
NSArray *allHosts = [kQNGlobalConfiguration.connectCheckURLStrings copy];
if (allHosts.count == 0) {
QNUploadSingleRequestMetrics *metrics = [QNUploadSingleRequestMetrics emptyMetrics];
[metrics start];
[metrics end];
metrics.error = [NSError errorWithDomain:@"com.qiniu.NetworkCheck" code:NSURLErrorUnsupportedURL userInfo:@{@"user_info":@"check host is empty"}];
complete(metrics);
return;
}
for (NSString *host in allHosts) {
[self checkHost:host complete:^(QNUploadSingleRequestMetrics *metrics) {
kQNStrongSelf;
BOOL isHostConnected = [self isConnected:metrics];
@synchronized (self) {
completeCount += 1;
}
if (isHostConnected || completeCount == allHosts.count) {
@synchronized (self) {
if (isCompleted) {
QNLogInfo(@"== check all hosts has completed totalCount:%d completeCount:%d", allHosts.count, completeCount);
return;
} else {
QNLogInfo(@"== check all hosts completed totalCount:%d completeCount:%d", allHosts.count, completeCount);
isCompleted = true;
}
}
complete(metrics);
} else {
QNLogInfo(@"== check all hosts not completed totalCount:%d completeCount:%d", allHosts.count, completeCount);
}
}];
}
}
+ (void)checkHost:(NSString *)host complete:(void (^)(QNUploadSingleRequestMetrics *metrics))complete {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
request.URL = [NSURL URLWithString:host];
request.HTTPMethod = @"HEAD";
request.timeoutInterval = kQNGlobalConfiguration.connectCheckTimeout;
__block BOOL hasCallback = false;
QNUploadSingleRequestMetrics *timeoutMetric = [QNUploadSingleRequestMetrics emptyMetrics];
[timeoutMetric start];
QNUploadSystemClient *client = [[QNUploadSystemClient alloc] init];
[client request:request server:nil connectionProxy:nil progress:nil complete:^(NSURLResponse *response, QNUploadSingleRequestMetrics * metrics, NSData * _Nullable data, NSError * error) {
@synchronized (self) {
if (hasCallback) {
return;
}
hasCallback = true;
}
QNLogInfo(@"== checkHost:%@ responseInfo:%@", host, response);
complete(metrics);
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * kQNGlobalConfiguration.connectCheckTimeout), [self checkQueue], ^{
@synchronized (self) {
if (hasCallback) {
return;
}
hasCallback = true;
}
[client cancel];
[timeoutMetric end];
timeoutMetric.error = [NSError errorWithDomain:@"com.qiniu.NetworkCheck" code:NSURLErrorTimedOut userInfo:nil];
complete(timeoutMetric);
});
}
@end

41
Pods/Qiniu/QiniuSDK/Http/Dns/QNDns.h generated Normal file
View File

@@ -0,0 +1,41 @@
//
// QNDns.h
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol QNIDnsNetworkAddress <NSObject>
/// 域名
@property(nonatomic, copy, readonly)NSString *hostValue;
/// 地址IP信息
@property(nonatomic, copy, readonly)NSString *ipValue;
/// ip有效时间 单位:秒
@property(nonatomic, strong, readonly)NSNumber *ttlValue;
/// ip预取来源, 自定义dns返回 @"customized"
@property(nonatomic, copy, readonly)NSString *sourceValue;
/// 解析到host时的时间戳 单位:秒
@property(nonatomic, strong, readonly)NSNumber *timestampValue;
@end
@protocol QNDnsDelegate <NSObject>
/// 根据host获取解析结果
/// @param host 域名
- (NSArray < id <QNIDnsNetworkAddress> > *)query:(NSString *)host;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,29 @@
//
// QNDnsCacheFile.h
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNDnsCacheFile : NSObject<QNRecorderDelegate>
/// DNS解析信息本地缓存路径
@property(nonatomic, copy, readonly)NSString *directory;
/// 构造方法 路径不存在或进行创建创建失败返回为nil
/// @param directory 路径
/// @param error 构造错误时,会有值
+ (instancetype _Nullable)dnsCacheFile:(NSString *)directory
error:(NSError **)error;
- (void)clearCache:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,91 @@
//
// QNDnsCacheFile.m
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNDnsCacheFile.h"
@interface QNDnsCacheFile()
@property(nonatomic, copy)NSString *directory;
@end
@implementation QNDnsCacheFile
+ (instancetype)dnsCacheFile:(NSString *)directory
error:(NSError **)error{
NSError *err = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&err];
if (err != nil) {
if (error != nil) *error = err;
return nil;
}
QNDnsCacheFile *f = [[QNDnsCacheFile alloc] init];
f.directory = directory;
return f;
}
- (NSError *)set:(NSString *)key data:(NSData *)value {
@synchronized (self) {
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = [self pathOfKey:key];
if ([fileManager fileExistsAtPath:filePath]) {
[fileManager removeItemAtPath:filePath error:&error];
}
[fileManager createFileAtPath:filePath contents:value attributes:nil];
return error;
}
}
- (NSData *)get:(NSString *)key {
return [NSData dataWithContentsOfFile:[self pathOfKey:key]];
}
- (NSError *)del:(NSString *)key {
@synchronized (self) {
NSError *error = nil;
NSString *path = [self pathOfKey:key];
if (path) {
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:path error:&error];
}
return error;
}
}
- (void)clearCache:(NSError *__autoreleasing _Nullable *)error {
@synchronized (self) {
NSError *err;
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:self.directory error:&err];
if (err != nil) {
if (error != nil) *error = err;
return;
}
[fileManager createDirectoryAtPath:self.directory withIntermediateDirectories:YES attributes:nil error:&err];
if (error != nil) {
*error = err;
}
}
}
- (NSString *)getFileName{
return @"dnsCache";
}
- (NSString *)pathOfKey:(NSString *)key {
return [QNDnsCacheFile pathJoin:key path:_directory];
}
+ (NSString *)pathJoin:(NSString *)key
path:(NSString *)path {
return [[NSString alloc] initWithFormat:@"%@/%@", path, key];
}
@end

View File

@@ -0,0 +1,41 @@
//
// QNDnsCacheKey.h
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNDnsCacheInfo : NSObject
/// 缓存时间戳
@property(nonatomic, copy, readonly)NSString *currentTime;
/// 缓存时本地IP
@property(nonatomic, copy, readonly)NSString *localIp;
/// 缓存信息
@property(nonatomic, copy, readonly)NSDictionary *info;
//MARK: -- 构造方法
/// 根据json构造对象
/// @param jsonData json数据
+ (instancetype)dnsCacheInfo:(NSData *)jsonData;
/// 根据属性构造对象
/// @param currentTime 缓存时间戳
/// @param localIp 缓存时本地IP
/// @param info 缓存信息
+ (instancetype)dnsCacheInfo:(NSString *)currentTime
localIp:(NSString *)localIp
info:(NSDictionary *)info;
/// 转化Json数据
- (NSData *)jsonData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,58 @@
//
// QNDnsCacheKey.m
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNDnsCacheInfo.h"
@interface QNDnsCacheInfo()
///
@property(nonatomic, copy)NSString *currentTime;
/// IP
@property(nonatomic, copy)NSString *localIp;
///
@property(nonatomic, copy)NSDictionary *info;
@end
@implementation QNDnsCacheInfo
+ (instancetype)dnsCacheInfo:(NSData *)jsonData{
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
if (!info || info.count == 0 ||
(!info[@"currentTime"] && !info[@"localIp"] && !info[@"info"])) {
return nil;
}
return [QNDnsCacheInfo dnsCacheInfo:info[@"currentTime"]
localIp:info[@"localIp"]
info:info[@"info"]];;
}
+ (instancetype)dnsCacheInfo:(NSString *)currentTime
localIp:(NSString *)localIp
info:(NSDictionary *)info{
QNDnsCacheInfo *cacheInfo = [[QNDnsCacheInfo alloc] init];
cacheInfo.currentTime = currentTime;
cacheInfo.localIp = localIp;
cacheInfo.info = info;
return cacheInfo;
}
- (NSData *)jsonData{
NSMutableDictionary *cacheInfo = [NSMutableDictionary dictionary];
if (self.currentTime) {
cacheInfo[@"currentTime"] = self.currentTime;
}
if (self.localIp) {
cacheInfo[@"localIp"] = self.localIp;
}
if (self.info) {
cacheInfo[@"info"] = self.info;
}
return [NSJSONSerialization dataWithJSONObject:cacheInfo options:NSJSONWritingPrettyPrinted error:nil];
}
@end

View File

@@ -0,0 +1,66 @@
//
// QNDnsPrefetch.h
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNDns.h"
#import "QNUpToken.h"
#import "QNConfiguration.h"
#import "QNTransactionManager.h"
NS_ASSUME_NONNULL_BEGIN
#define kQNDnsPrefetch [QNDnsPrefetch shared]
@interface QNDnsPrefetch : NSObject
/// 最近一次预取错误信息
@property(nonatomic, copy, readonly)NSString *lastPrefetchedErrorMessage;
+ (instancetype)shared;
/// 根据host从缓存中读取DNS信息
/// @param host 域名
- (NSArray <id <QNIDnsNetworkAddress> > *)getInetAddressByHost:(NSString *)host;
/// 通过安全的方式预取 dns
- (NSString *)prefetchHostBySafeDns:(NSString *)host error:(NSError **)error;
- (void)clearDnsCache:(NSError **)error;
@end
@interface QNTransactionManager(Dns)
/// 添加加载本地dns事务
- (void)addDnsLocalLoadTransaction;
/// 添加检测并预取dns事务 如果未开启DNS 或 事务队列中存在token对应的事务未处理则返回NO
/// @param currentZone 当前区域
/// @param token token信息
- (BOOL)addDnsCheckAndPrefetchTransaction:(QNZone *)currentZone token:(QNUpToken *)token;
- (BOOL)addDnsCheckAndPrefetchTransaction:(QNConfiguration *)config zone:(QNZone *)currentZone token:(QNUpToken *)token;
/// 设置定时事务检测已缓存DNS有效情况事务 无效会重新预取
- (void)setDnsCheckWhetherCachedValidTransactionAction;
@end
#define kQNDnsSourceDoh @"doh"
#define kQNDnsSourceUdp @"dns"
#define kQNDnsSourceDnspod @"dnspod"
#define kQNDnsSourceSystem @"system"
#define kQNDnsSourceCustom @"customized"
#define kQNDnsSourceNone @"none"
BOOL kQNIsDnsSourceDoh(NSString * _Nullable source);
BOOL kQNIsDnsSourceUdp(NSString * _Nullable source);
BOOL kQNIsDnsSourceDnsPod(NSString * _Nullable source);
BOOL kQNIsDnsSourceSystem(NSString * _Nullable source);
BOOL kQNIsDnsSourceCustom(NSString * _Nullable source);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,874 @@
//
// QNDnsPrefetch.m
// QnDNS
//
// Created by yangsen on 2020/3/26.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNDnsPrefetch.h"
#import "QNInetAddress.h"
#import "QNDnsCacheInfo.h"
#import "QNZoneInfo.h"
#import "QNDefine.h"
#import "QNConfig.h"
#import "QNDnsCacheFile.h"
#import "QNUtils.h"
#import "QNAsyncRun.h"
#import "QNFixedZone.h"
#import "QNAutoZone.h"
#import <HappyDNS/HappyDNS.h>
//MARK: --
@interface QNDnsNetworkAddress : NSObject<QNIDnsNetworkAddress>
@property(nonatomic, copy)NSString *hostValue;
@property(nonatomic, copy)NSString *ipValue;
@property(nonatomic, strong)NSNumber *ttlValue;
@property(nonatomic, copy)NSString *sourceValue;
@property(nonatomic, strong)NSNumber *timestampValue;
/// addressDatajson String / Dictionary / Data / QNIDnsNetworkAddress
+ (instancetype)inetAddress:(id)addressInfo;
///
- (BOOL)isValid;
/// json
- (NSString *)toJsonInfo;
///
- (NSDictionary *)toDictionary;
@end
@implementation QNDnsNetworkAddress
+ (instancetype)inetAddress:(id)addressInfo{
NSDictionary *addressDic = nil;
if ([addressInfo isKindOfClass:[NSDictionary class]]) {
addressDic = (NSDictionary *)addressInfo;
} else if ([addressInfo isKindOfClass:[NSString class]]){
NSData *data = [(NSString *)addressInfo dataUsingEncoding:NSUTF8StringEncoding];
addressDic = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableLeaves
error:nil];
} else if ([addressInfo isKindOfClass:[NSData class]]) {
addressDic = [NSJSONSerialization JSONObjectWithData:(NSData *)addressInfo
options:NSJSONReadingMutableLeaves
error:nil];
} else if ([addressInfo conformsToProtocol:@protocol(QNIDnsNetworkAddress)]){
id <QNIDnsNetworkAddress> address = (id <QNIDnsNetworkAddress> )addressInfo;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
if ([address respondsToSelector:@selector(hostValue)] && [address hostValue]) {
dic[@"hostValue"] = [address hostValue];
}
if ([address respondsToSelector:@selector(ipValue)] && [address ipValue]) {
dic[@"ipValue"] = [address ipValue];
}
if ([address respondsToSelector:@selector(ttlValue)] && [address ttlValue]) {
dic[@"ttlValue"] = [address ttlValue];
}
if ([address respondsToSelector:@selector(sourceValue)] && [address sourceValue]) {
dic[@"sourceValue"] = [address sourceValue];
} else {
dic[@"sourceValue"] = kQNDnsSourceCustom;
}
if ([address respondsToSelector:@selector(timestampValue)] && [address timestampValue]) {
dic[@"timestampValue"] = [address timestampValue];
}
addressDic = [dic copy];
}
if (addressDic) {
QNDnsNetworkAddress *address = [[QNDnsNetworkAddress alloc] init];
[address setValuesForKeysWithDictionary:addressDic];
return address;
} else {
return nil;
}
}
/// ttl
- (BOOL)needRefresh{
if (!self.timestampValue || !self.ipValue || self.ipValue.length == 0) {
return NO;
}
NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
return currentTimestamp > (self.timestampValue.doubleValue + self.ttlValue.doubleValue);
}
/// ttl
- (BOOL)isValid{
if (!self.timestampValue || !self.ipValue || self.ipValue.length == 0) {
return NO;
}
NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
return currentTimestamp < (self.timestampValue.doubleValue + kQNGlobalConfiguration.dnsCacheMaxTTL);
}
- (NSString *)toJsonInfo{
NSString *defaultString = @"{}";
NSDictionary *infoDic = [self toDictionary];
if (!infoDic) {
return defaultString;
}
NSData *infoData = [NSJSONSerialization dataWithJSONObject:infoDic
options:NSJSONWritingPrettyPrinted
error:nil];
if (!infoData) {
return defaultString;
}
NSString *infoStr = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding];
if (!infoStr) {
return defaultString;
} else {
return infoStr;
}
}
- (NSDictionary *)toDictionary{
return [self dictionaryWithValuesForKeys:@[@"ipValue", @"hostValue", @"ttlValue", @"sourceValue", @"timestampValue"]];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{}
@end
//MARK: -- HappyDNS
@interface QNRecord(DNS)<QNIDnsNetworkAddress>
@end
@implementation QNRecord(DNS)
- (NSString *)hostValue{
return nil;
}
- (NSString *)ipValue{
return self.value;
}
- (NSNumber *)ttlValue{
return @(self.ttl);
}
- (NSNumber *)timestampValue{
return @(self.timeStamp);
}
- (NSString *)sourceValue{
if (self.source == QNRecordSourceSystem) {
return kQNDnsSourceSystem;
} else if (self.source == QNRecordSourceDoh) {
return [NSString stringWithFormat:@"%@<%@>", kQNDnsSourceDoh, self.server];
} else if (self.source == QNRecordSourceUdp) {
return [NSString stringWithFormat:@"%@<%@>", kQNDnsSourceUdp, self.server];
} else if (self.source == QNRecordSourceDnspodEnterprise) {
return kQNDnsSourceDnspod;
} else if (self.ipValue == nil || self.ipValue.length == 0) {
return kQNDnsSourceNone;
} else {
return kQNDnsSourceCustom;
}
}
@end
@interface QNInternalDns : NSObject
@property(nonatomic, strong)id<QNDnsDelegate> dns;
@property(nonatomic, strong)id<QNResolverDelegate> resolver;
@end
@implementation QNInternalDns
+ (instancetype)dnsWithDns:(id<QNDnsDelegate>)dns {
QNInternalDns *interDns = [[QNInternalDns alloc] init];
interDns.dns = dns;
return interDns;
}
+ (instancetype)dnsWithResolver:(id<QNResolverDelegate>)resolver {
QNInternalDns *interDns = [[QNInternalDns alloc] init];
interDns.resolver = resolver;
return interDns;
}
- (NSArray < id <QNIDnsNetworkAddress> > *)query:(NSString *)host error:(NSError **)error {
if (self.dns && [self.dns respondsToSelector:@selector(query:)]) {
return [self.dns query:host];
} else if (self.resolver) {
NSArray <QNRecord *>* records = [self.resolver query:[[QNDomain alloc] init:host] networkInfo:nil error:error];
return [self filterRecords:records];
}
return nil;
}
- (NSArray <QNRecord *>*)filterRecords:(NSArray <QNRecord *>*)records {
NSMutableArray <QNRecord *> *newRecords = [NSMutableArray array];
for (QNRecord *record in records) {
if (record.type == kQNTypeA || record.type == kQNTypeAAAA) {
[newRecords addObject:record];
}
}
return [newRecords copy];
}
@end
//MARK: -- DNS Prefetcher
@interface QNDnsPrefetch()
// dns 3s
@property(nonatomic, assign)int dnsPrefetchTimeout;
//
@property(nonatomic, copy)NSString *lastPrefetchedErrorMessage;
///
@property(atomic, assign)BOOL isPrefetching;
/// AutoZone
@property(nonatomic, strong)dispatch_semaphore_t getAutoZoneSemaphore;
/// DNSkey
@property(nonatomic, strong)QNDnsCacheInfo *dnsCacheInfo;
// dns
@property(nonatomic, strong)QNInternalDns *customDns;
// dns
@property(nonatomic, strong)QNInternalDns *systemDns;
/// prefetch hosts
@property(nonatomic, strong)NSMutableSet *prefetchHosts;
/// DNS
/// 线线
@property(nonatomic, strong)NSMutableDictionary <NSString *, NSArray<QNDnsNetworkAddress *>*> *addressDictionary;
@property(nonatomic, strong)QNDnsCacheFile *diskCache;
@end
@implementation QNDnsPrefetch
+ (instancetype)shared{
static QNDnsPrefetch *prefetcher = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
prefetcher = [[QNDnsPrefetch alloc] init];
});
return prefetcher;
}
- (instancetype)init{
if (self = [super init]) {
_isPrefetching = NO;
_dnsPrefetchTimeout = 3;
}
return self;
}
//MARK: -- uploadManager
/// 使falsetrue
- (BOOL)recoverCache{
id <QNRecorderDelegate> recorder = nil;
NSError *error;
recorder = [QNDnsCacheFile dnsCacheFile:kQNGlobalConfiguration.dnsCacheDir
error:&error];
if (error) {
return YES;
}
NSData *data = [recorder get:[QNIP local]];
if (!data) {
return YES;
}
QNDnsCacheInfo *cacheInfo = [QNDnsCacheInfo dnsCacheInfo:data];
if (!cacheInfo) {
return YES;
}
NSString *localIp = [QNIP local];
if (!localIp || localIp.length == 0 || ![cacheInfo.localIp isEqualToString:localIp]) {
return YES;
}
[self setDnsCacheInfo:cacheInfo];
return [self recoverDnsCache:cacheInfo.info];
}
/// DNS
- (void)localFetch{
if ([self prepareToPreFetch] == NO) {
return;
}
NSArray *hosts = [self getLocalPreHost];
@synchronized (self) {
[self.prefetchHosts addObjectsFromArray:hosts];
}
[self preFetchHosts:hosts];
[self recorderDnsCache];
[self endPreFetch];
}
//MARK: --
/// tokenDns YESNO
- (void)checkAndPrefetchDnsIfNeed:(QNConfiguration *)config zone:(QNZone *)currentZone token:(QNUpToken *)token{
if ([self prepareToPreFetch] == NO) {
return;
}
NSArray *hosts = [self getCurrentZoneHosts:config zone:currentZone token:token];
if (hosts == nil) {
return;
}
@synchronized (self) {
[self.prefetchHosts addObjectsFromArray:hosts];
}
[self preFetchHosts:hosts];
[self recorderDnsCache];
[self endPreFetch];
}
/// dns
- (void)checkWhetherCachedDnsValid{
if ([self prepareToPreFetch] == NO) {
return;
}
NSArray *hosts = nil;
@synchronized (self) {
hosts = [self.prefetchHosts allObjects];
}
[self preFetchHosts:hosts];
[self recorderDnsCache];
[self endPreFetch];
}
//MARK: -- DNS
/// hostDNS
- (NSArray <id <QNIDnsNetworkAddress> > *)getInetAddressByHost:(NSString *)host{
if ([self isDnsOpen] == NO) {
return nil;
}
[self clearDnsCacheIfNeeded];
NSArray <QNDnsNetworkAddress *> *addressList = nil;
@synchronized (self) {
addressList = self.addressDictionary[host];
}
if (addressList && addressList.count > 0 && [addressList.firstObject isValid]) {
return addressList;
} else {
return nil;
}
}
- (void)invalidNetworkAddressOfHost:(NSString *)host {
if (host == nil || host.length == 0) {
return;
}
@synchronized (self) {
[self.addressDictionary removeObjectForKey:host];
}
}
- (void)clearDnsCache:(NSError *__autoreleasing _Nullable *)error {
[self clearDnsMemoryCache];
[self clearDnsDiskCache:error];
}
//MARK: --
//MARK: -- dns
- (NSString *)prefetchHostBySafeDns:(NSString *)host error:(NSError * __autoreleasing *)error {
if (host == nil) {
return nil;
}
[self invalidNetworkAddressOfHost:host];
NSError *err = nil;
NSArray *nextFetchHosts = @[host];
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:self.customDns error:&err];
if (nextFetchHosts.count == 0) {
return [self getInetAddressByHost:host].firstObject.sourceValue;
}
if (!kQNGlobalConfiguration.dohEnable) {
if (error != nil && err) {
*error = err;
}
return nil;
}
if (kQNGlobalConfiguration.dohIpv4Servers && [kQNGlobalConfiguration.dohIpv4Servers count] > 0) {
QNDohResolver *dohResolver = [QNDohResolver resolverWithServers:kQNGlobalConfiguration.dohIpv4Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *doh = [QNInternalDns dnsWithResolver:dohResolver];
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:doh error:&err];
if (nextFetchHosts.count == 0) {
return [self getInetAddressByHost:host].firstObject.sourceValue;
}
if (error != nil && err) {
*error = err;
}
}
if ([QNIP isIpV6FullySupported] && kQNGlobalConfiguration.dohIpv6Servers && [kQNGlobalConfiguration.dohIpv6Servers count] > 0) {
QNDohResolver *dohResolver = [QNDohResolver resolverWithServers:kQNGlobalConfiguration.dohIpv6Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *doh = [QNInternalDns dnsWithResolver:dohResolver];
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:doh error:&err];
if (error != nil && err) {
*error = err;
}
}
if (nextFetchHosts.count == 0) {
return [self getInetAddressByHost:host].firstObject.sourceValue;
} else {
return nil;
}
}
- (BOOL)prepareToPreFetch {
if ([self isDnsOpen] == NO) {
return NO;
}
self.lastPrefetchedErrorMessage = nil;
if (self.isPrefetching == YES) {
return NO;
}
[self clearDnsCacheIfNeeded];
self.isPrefetching = YES;
return YES;
}
- (void)endPreFetch{
self.isPrefetching = NO;
}
- (void)preFetchHosts:(NSArray <NSString *> *)fetchHosts {
NSError *err = nil;
[self preFetchHosts:fetchHosts error:&err];
self.lastPrefetchedErrorMessage = err.description;
}
- (void)preFetchHosts:(NSArray <NSString *> *)fetchHosts error:(NSError **)error {
NSArray *nextFetchHosts = fetchHosts;
//
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:self.customDns error:error];
if (nextFetchHosts.count == 0) {
return;
}
//
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:self.systemDns error:error];
if (nextFetchHosts.count == 0) {
return;
}
// doh
if (kQNGlobalConfiguration.dohEnable) {
if (kQNGlobalConfiguration.dohIpv4Servers && [kQNGlobalConfiguration.dohIpv4Servers count] > 0) {
QNDohResolver *dohResolver = [QNDohResolver resolverWithServers:kQNGlobalConfiguration.dohIpv4Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *doh = [QNInternalDns dnsWithResolver:dohResolver];
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:doh error:error];
if (nextFetchHosts.count == 0) {
return;
}
}
if ([QNIP isIpV6FullySupported] && kQNGlobalConfiguration.dohIpv6Servers && [kQNGlobalConfiguration.dohIpv6Servers count] > 0) {
QNDohResolver *dohResolver = [QNDohResolver resolverWithServers:kQNGlobalConfiguration.dohIpv6Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *doh = [QNInternalDns dnsWithResolver:dohResolver];
nextFetchHosts = [self preFetchHosts:nextFetchHosts dns:doh error:error];
if (nextFetchHosts.count == 0) {
return;
}
}
}
// udp
if (kQNGlobalConfiguration.udpDnsEnable) {
if (kQNGlobalConfiguration.udpDnsIpv4Servers && [kQNGlobalConfiguration.udpDnsIpv4Servers count] > 0) {
QNDnsUdpResolver *udpDnsResolver = [QNDnsUdpResolver resolverWithServerIPs:kQNGlobalConfiguration.udpDnsIpv4Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *udpDns = [QNInternalDns dnsWithResolver:udpDnsResolver];
[self preFetchHosts:nextFetchHosts dns:udpDns error:error];
}
if ([QNIP isIpV6FullySupported] && kQNGlobalConfiguration.udpDnsIpv6Servers && [kQNGlobalConfiguration.udpDnsIpv6Servers count] > 0) {
QNDnsUdpResolver *udpDnsResolver = [QNDnsUdpResolver resolverWithServerIPs:kQNGlobalConfiguration.udpDnsIpv6Servers recordType:kQNTypeA timeout:kQNGlobalConfiguration.dnsResolveTimeout];
QNInternalDns *udpDns = [QNInternalDns dnsWithResolver:udpDnsResolver];
[self preFetchHosts:nextFetchHosts dns:udpDns error:error];
}
}
}
- (NSArray *)preFetchHosts:(NSArray <NSString *> *)preHosts dns:(QNInternalDns *)dns error:(NSError **)error {
if (!preHosts || preHosts.count == 0) {
return nil;
}
if (!dns) {
return [preHosts copy];
}
int dnsRepreHostNum = kQNGlobalConfiguration.dnsRepreHostNum;
NSMutableArray *failHosts = [NSMutableArray array];
for (NSString *host in preHosts) {
int rePreNum = 0;
BOOL isSuccess = NO;
while (rePreNum < dnsRepreHostNum) {
if ([self preFetchHost:host dns:dns error:error]) {
isSuccess = YES;
break;
}
rePreNum += 1;
}
if (!isSuccess) {
[failHosts addObject:host];
}
}
return [failHosts copy];
}
- (BOOL)preFetchHost:(NSString *)preHost dns:(QNInternalDns *)dns error:(NSError **)error {
if (!preHost || preHost.length == 0) {
return NO;
}
NSDictionary *addressDictionary = nil;
@synchronized (self) {
addressDictionary = [self.addressDictionary copy];
}
NSArray<QNDnsNetworkAddress *>* preAddressList = addressDictionary[preHost];
if (preAddressList && ![preAddressList.firstObject needRefresh]) {
return YES;
}
NSArray <id <QNIDnsNetworkAddress> > * addressList = [dns query:preHost error:error];
if (addressList && addressList.count > 0) {
NSMutableArray *addressListP = [NSMutableArray array];
for (id <QNIDnsNetworkAddress>inetAddress in addressList) {
QNDnsNetworkAddress *address = [QNDnsNetworkAddress inetAddress:inetAddress];
if (address) {
address.hostValue = preHost;
if (!address.ttlValue) {
address.ttlValue = @(kQNDefaultDnsCacheTime);
}
if (!address.timestampValue) {
address.timestampValue = @([[NSDate date] timeIntervalSince1970]);
}
[addressListP addObject:address];
}
}
addressListP = [addressListP copy];
@synchronized (self) {
self.addressDictionary[preHost] = addressListP;
}
return YES;
} else {
return NO;
}
}
//MARK: --
- (BOOL)recoverDnsCache:(NSDictionary *)dataDic{
if (dataDic == nil) {
return NO;
}
NSMutableDictionary *records = [NSMutableDictionary dictionary];
for (NSString *key in dataDic.allKeys) {
NSArray *ips = dataDic[key];
if ([ips isKindOfClass:[NSArray class]]) {
NSMutableArray <QNDnsNetworkAddress *> * addressList = [NSMutableArray array];
for (NSDictionary *ipInfo in ips) {
if ([ipInfo isKindOfClass:[NSDictionary class]]) {
QNDnsNetworkAddress *address = [QNDnsNetworkAddress inetAddress:ipInfo];
if (address) {
[addressList addObject:address];
}
}
}
if (addressList.count > 0) {
records[key] = [addressList copy];
}
}
}
@synchronized (self) {
[self.addressDictionary setValuesForKeysWithDictionary:records];
}
return NO;
}
- (BOOL)recorderDnsCache{
NSTimeInterval currentTime = [QNUtils currentTimestamp];
NSString *localIp = [QNIP local];
if (localIp == nil || localIp.length == 0) {
return NO;
}
NSError *error;
id <QNRecorderDelegate> recorder = [QNDnsCacheFile dnsCacheFile:kQNGlobalConfiguration.dnsCacheDir
error:&error];
if (error) {
return NO;
}
NSDictionary *addressDictionary = nil;
@synchronized (self) {
addressDictionary = [self.addressDictionary copy];
}
NSMutableDictionary *addressInfo = [NSMutableDictionary dictionary];
for (NSString *key in addressDictionary.allKeys) {
NSArray *addressModelList = addressDictionary[key];
NSMutableArray * addressDicList = [NSMutableArray array];
for (QNDnsNetworkAddress *ipInfo in addressModelList) {
NSDictionary *addressDic = [ipInfo toDictionary];
if (addressDic) {
[addressDicList addObject:addressDic];
}
}
if (addressDicList.count > 0) {
addressInfo[key] = addressDicList;
}
}
QNDnsCacheInfo *cacheInfo = [QNDnsCacheInfo dnsCacheInfo:[NSString stringWithFormat:@"%.0lf",currentTime]
localIp:localIp
info:addressInfo];
NSData *cacheData = [cacheInfo jsonData];
if (!cacheData) {
return NO;
}
[self setDnsCacheInfo:cacheInfo];
[recorder set:localIp data:cacheData];
return true;
}
- (void)clearDnsCacheIfNeeded{
NSString *localIp = [QNIP local];
if (localIp == nil || (self.dnsCacheInfo && ![localIp isEqualToString:self.dnsCacheInfo.localIp])) {
[self clearDnsMemoryCache];
}
}
- (void)clearDnsMemoryCache {
@synchronized (self) {
[self.addressDictionary removeAllObjects];
}
}
- (void)clearDnsDiskCache:(NSError **)error {
[self.diskCache clearCache:error];
}
//MARK: -- hosts
- (NSArray <NSString *> *)getLocalPreHost{
NSMutableArray *localHosts = [NSMutableArray array];
[localHosts addObject:kQNUpLogHost];
return [localHosts copy];
}
- (NSArray <NSString *> *)getCurrentZoneHosts:(QNConfiguration *)config
zone:(QNZone *)currentZone
token:(QNUpToken *)token{
if (!currentZone || !token || !token.token) {
return nil;
}
__block QNZonesInfo *zonesInfo = nil;
[currentZone query:config token:token on:^(QNResponseInfo * _Nullable httpResponseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, QNZonesInfo * _Nullable info) {
zonesInfo = info;
dispatch_semaphore_signal(self.semaphore);
}];
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (zonesInfo == nil) {
return nil;
}
QNZonesInfo *autoZonesInfo = [currentZone getZonesInfoWithToken:token];
NSMutableArray *autoHosts = [NSMutableArray array];
NSArray *zoneInfoList = autoZonesInfo.zonesInfo;
for (QNZoneInfo *info in zoneInfoList) {
if (info.allHosts) {
[autoHosts addObjectsFromArray:info.allHosts];
}
}
return [autoHosts copy];
}
//MARK: --
- (BOOL)isDnsOpen{
return [kQNGlobalConfiguration isDnsOpen];
}
- (QNDnsCacheInfo *)dnsCacheInfo{
if (_dnsCacheInfo == nil) {
_dnsCacheInfo = [[QNDnsCacheInfo alloc] init];
}
return _dnsCacheInfo;
}
- (NSMutableDictionary<NSString *,NSArray<QNDnsNetworkAddress *> *> *)addressDictionary{
if (_addressDictionary == nil) {
_addressDictionary = [NSMutableDictionary dictionary];
}
return _addressDictionary;
}
- (dispatch_semaphore_t)semaphore{
if (_getAutoZoneSemaphore == NULL) {
_getAutoZoneSemaphore = dispatch_semaphore_create(0);
}
return _getAutoZoneSemaphore;
}
- (QNDnsCacheFile *)diskCache {
if (!_diskCache) {
NSError *error;
QNDnsCacheFile *cache = [QNDnsCacheFile dnsCacheFile:kQNGlobalConfiguration.dnsCacheDir error:&error];
if (!error) {
_diskCache = cache;
}
}
return _diskCache;
}
- (QNInternalDns *)customDns {
if (_customDns == nil && kQNGlobalConfiguration.dns) {
_customDns = [QNInternalDns dnsWithDns:kQNGlobalConfiguration.dns];
}
return _customDns;
}
- (QNInternalDns *)systemDns {
if (_systemDns == nil) {
_systemDns = [QNInternalDns dnsWithResolver:[[QNResolver alloc] initWithAddress:nil timeout:self.dnsPrefetchTimeout]];
}
return _systemDns;
}
- (NSMutableSet *)prefetchHosts {
if (!_prefetchHosts) {
_prefetchHosts = [NSMutableSet set];
}
return _prefetchHosts;
}
@end
//MARK: -- DNS
@implementation QNTransactionManager(Dns)
#define kQNLoadLocalDnsTransactionName @"QNLoadLocalDnsTransaction"
#define kQNDnsCheckAndPrefetchTransactionName @"QNDnsCheckAndPrefetchTransactionName"
- (void)addDnsLocalLoadTransaction{
if ([kQNDnsPrefetch isDnsOpen] == NO) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
QNTransaction *transaction = [QNTransaction transaction:kQNLoadLocalDnsTransactionName after:0 action:^{
[kQNDnsPrefetch recoverCache];
[kQNDnsPrefetch localFetch];
}];
[[QNTransactionManager shared] addTransaction:transaction];
[self setDnsCheckWhetherCachedValidTransactionAction];
});
}
- (BOOL)addDnsCheckAndPrefetchTransaction:(QNZone *)currentZone token:(QNUpToken *)token {
return [self addDnsCheckAndPrefetchTransaction:[QNConfiguration defaultConfiguration] zone:currentZone token:token];
}
- (BOOL)addDnsCheckAndPrefetchTransaction:(QNConfiguration *)config zone:(QNZone *)currentZone token:(QNUpToken *)token {
if (!token) {
return NO;
}
if ([kQNDnsPrefetch isDnsOpen] == NO) {
return NO;
}
BOOL ret = NO;
@synchronized (kQNDnsPrefetch) {
QNTransactionManager *transactionManager = [QNTransactionManager shared];
if (![transactionManager existTransactionsForName:token.token]) {
QNTransaction *transaction = [QNTransaction transaction:token.token after:0 action:^{
[kQNDnsPrefetch checkAndPrefetchDnsIfNeed:config zone:currentZone token:token];
}];
[transactionManager addTransaction:transaction];
ret = YES;
}
}
return ret;
}
- (void)setDnsCheckWhetherCachedValidTransactionAction{
if ([kQNDnsPrefetch isDnsOpen] == NO) {
return;
}
@synchronized (kQNDnsPrefetch) {
QNTransactionManager *transactionManager = [QNTransactionManager shared];
QNTransaction *transaction = [transactionManager transactionsForName:kQNDnsCheckAndPrefetchTransactionName].firstObject;
if (!transaction) {
QNTransaction *transaction = [QNTransaction timeTransaction:kQNDnsCheckAndPrefetchTransactionName
after:10
interval:120
action:^{
[kQNDnsPrefetch checkWhetherCachedDnsValid];
}];
[transactionManager addTransaction:transaction];
} else {
[transactionManager performTransaction:transaction];
}
}
}
@end
BOOL kQNIsDnsSourceDoh(NSString * _Nullable source) {
return [source containsString:kQNDnsSourceDoh];
}
BOOL kQNIsDnsSourceUdp(NSString * _Nullable source) {
return [source containsString:kQNDnsSourceUdp];
}
BOOL kQNIsDnsSourceDnsPod(NSString * _Nullable source) {
return [source containsString:kQNDnsSourceDnspod];
}
BOOL kQNIsDnsSourceSystem(NSString * _Nullable source) {
return [source containsString:kQNDnsSourceSystem];
}
BOOL kQNIsDnsSourceCustom(NSString * _Nullable source) {
return [source containsString:kQNDnsSourceCustom];
}

View File

@@ -0,0 +1,37 @@
//
// QNInetAddress.h
// QiniuSDK
//
// Created by 杨森 on 2020/7/27.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNDns.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNInetAddress : NSObject <QNIDnsNetworkAddress>
@property(nonatomic, copy)NSString *hostValue;
@property(nonatomic, copy)NSString *ipValue;
@property(nonatomic, strong)NSNumber *ttlValue;
@property(nonatomic, strong)NSNumber *timestampValue;
@property(nonatomic, copy)NSString *sourceValue;
/// 构造方法 addressData为json
/// @param addressInfo 地址信息类型可能为String / Dictionary / Data / 遵循 QNInetAddressDelegate的实例
+ (instancetype)inetAddress:(id)addressInfo;
/// 是否有效,根据时间戳判断
- (BOOL)isValid;
/// 对象转json
- (NSString *)toJsonInfo;
/// 对象转字典
- (NSDictionary *)toDictionary;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,95 @@
//
// QNInetAddress.m
// QiniuSDK
//
// Created by on 2020/7/27.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNInetAddress.h"
@interface QNInetAddress()
@end
@implementation QNInetAddress
+ (instancetype)inetAddress:(id)addressInfo{
NSDictionary *addressDic = nil;
if ([addressInfo isKindOfClass:[NSDictionary class]]) {
addressDic = (NSDictionary *)addressInfo;
} else if ([addressInfo isKindOfClass:[NSString class]]){
NSData *data = [(NSString *)addressInfo dataUsingEncoding:NSUTF8StringEncoding];
addressDic = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableLeaves
error:nil];
} else if ([addressInfo isKindOfClass:[NSData class]]) {
addressDic = [NSJSONSerialization JSONObjectWithData:(NSData *)addressInfo
options:NSJSONReadingMutableLeaves
error:nil];
} else if ([addressInfo conformsToProtocol:@protocol(QNIDnsNetworkAddress)]){
id <QNIDnsNetworkAddress> address = (id <QNIDnsNetworkAddress> )addressInfo;
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
if ([address respondsToSelector:@selector(hostValue)] && [address hostValue]) {
dic[@"hostValue"] = [address hostValue];
}
if ([address respondsToSelector:@selector(ipValue)] && [address ipValue]) {
dic[@"ipValue"] = [address ipValue];
}
if ([address respondsToSelector:@selector(ttlValue)] && [address ttlValue]) {
dic[@"ttlValue"] = [address ttlValue];
}
if ([address respondsToSelector:@selector(timestampValue)] && [address timestampValue]) {
dic[@"timestampValue"] = [address timestampValue];
}
addressDic = [dic copy];
}
if (addressDic) {
QNInetAddress *address = [[QNInetAddress alloc] init];
[address setValuesForKeysWithDictionary:addressDic];
return address;
} else {
return nil;
}
}
- (BOOL)isValid{
if (!self.timestampValue || !self.ipValue || self.ipValue.length == 0) {
return NO;
}
NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
if (currentTimestamp > self.timestampValue.doubleValue + self.ttlValue.doubleValue) {
return NO;
} else {
return YES;
}
}
- (NSString *)toJsonInfo{
NSString *defaultString = @"{}";
NSDictionary *infoDic = [self toDictionary];
if (!infoDic) {
return defaultString;
}
NSData *infoData = [NSJSONSerialization dataWithJSONObject:infoDic
options:NSJSONWritingPrettyPrinted
error:nil];
if (!infoData) {
return defaultString;
}
NSString *infoStr = [[NSString alloc] initWithData:infoData encoding:NSUTF8StringEncoding];
if (!infoStr) {
return defaultString;
} else {
return infoStr;
}
}
- (NSDictionary *)toDictionary{
return [self dictionaryWithValuesForKeys:@[@"ipValue", @"hostValue", @"ttlValue", @"timestampValue"]];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{}
@end

View File

@@ -0,0 +1,37 @@
//
// QNNetworkStatusManager.h
// QiniuSDK
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNNetworkStatus : NSObject
/// 网速 单位kb/s 默认200kb/s
@property(nonatomic, assign, readonly)int speed;
@end
#define kQNNetworkStatusManager [QNNetworkStatusManager sharedInstance]
@interface QNNetworkStatusManager : NSObject
+ (instancetype)sharedInstance;
+ (NSString *)getNetworkStatusType:(NSString *)host
ip:(NSString *)ip;
- (QNNetworkStatus *)getNetworkStatus:(NSString *)type;
- (void)updateNetworkStatus:(NSString *)type
speed:(int)speed;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,93 @@
//
// QNNetworkStatusManager.m
// QiniuSDK
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUtils.h"
#import "QNCache.h"
#import "QNAsyncRun.h"
#import "QNFileRecorder.h"
#import "QNRecorderDelegate.h"
#import "QNNetworkStatusManager.h"
@interface QNNetworkStatus()<QNCacheObject>
@property(nonatomic, assign)int speed;
@end
@implementation QNNetworkStatus
- (instancetype)init{
if (self = [super init]) {
_speed = 200;
}
return self;
}
- (nonnull id<QNCacheObject>)initWithDictionary:(nullable NSDictionary *)dictionary {
QNNetworkStatus *status = [[QNNetworkStatus alloc] init];
status.speed = [dictionary[@"speed"] intValue];
return status;
}
- (NSDictionary *)toDictionary{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:@(self.speed) forKey:@"speed"];
return dictionary;
}
+ (QNNetworkStatus *)statusFromDictionary:(NSDictionary *)dictionary{
QNNetworkStatus *status = [[QNNetworkStatus alloc] init];
status.speed = [dictionary[@"speed"] intValue];
return status;
}
@end
@interface QNNetworkStatusManager()
@property(nonatomic, strong)QNCache *cache;
@end
@implementation QNNetworkStatusManager
+ (instancetype)sharedInstance{
static QNNetworkStatusManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[QNNetworkStatusManager alloc] init];
[manager initData];
});
return manager;
}
- (void)initData{
QNCacheOption *option = [[QNCacheOption alloc] init];
option.version = @"v1.0.2";
option.flushCount = 10;
self.cache = [QNCache cache:[QNNetworkStatus class] option:option];
}
+ (NSString *)getNetworkStatusType:(NSString *)host
ip:(NSString *)ip {
return [QNUtils getIpType:ip host:host];
}
- (QNNetworkStatus *)getNetworkStatus:(NSString *)type{
if (type == nil || type.length == 0) {
return nil;
}
return [self.cache cacheForKey:type];
}
- (void)updateNetworkStatus:(NSString *)type speed:(int)speed{
if (type == nil || type.length == 0) {
return;
}
QNNetworkStatus *status = [[QNNetworkStatus alloc] init];
status.speed = speed;
[self.cache cache:status forKey:type atomically:false];
}
@end

View File

@@ -0,0 +1,24 @@
//
// QNUploadServerNetworkStatus.h
// QiniuSDK
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNUploadServer.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadServerNetworkStatus : NSObject
+ (QNUploadServer *)getBetterNetworkServer:(QNUploadServer *)serverA
serverB:(QNUploadServer *)serverB;
+ (BOOL)isServerNetworkBetter:(QNUploadServer *)serverA
thanServerB:(QNUploadServer *)serverB;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,40 @@
//
// QNUploadServerNetworkStatus.m
// QiniuSDK
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUtils.h"
#import "QNNetworkStatusManager.h"
#import "QNUploadServerNetworkStatus.h"
@implementation QNUploadServerNetworkStatus
+ (QNUploadServer *)getBetterNetworkServer:(QNUploadServer *)serverA serverB:(QNUploadServer *)serverB {
return [self isServerNetworkBetter:serverA thanServerB:serverB] ? serverA : serverB;
}
+ (BOOL)isServerNetworkBetter:(QNUploadServer *)serverA thanServerB:(QNUploadServer *)serverB {
if (serverA == nil) {
return NO;
} else if (serverB == nil) {
return YES;
}
NSString *serverTypeA = [QNNetworkStatusManager getNetworkStatusType:serverA.host ip:serverA.ip];
NSString *serverTypeB = [QNNetworkStatusManager getNetworkStatusType:serverB.host ip:serverB.ip];
if (serverTypeA == nil) {
return NO;
} else if (serverTypeB == nil) {
return YES;
}
QNNetworkStatus *serverStatusA = [kQNNetworkStatusManager getNetworkStatus:serverTypeA];
QNNetworkStatus *serverStatusB = [kQNNetworkStatusManager getNetworkStatus:serverTypeB];
return serverStatusB.speed < serverStatusA.speed;
}
@end

85
Pods/Qiniu/QiniuSDK/Http/QNResponseInfo.h generated Executable file
View File

@@ -0,0 +1,85 @@
//
// QNResponseInfo.h
// QiniuSDK
//
// Created by bailong on 14/10/2.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNErrorCode.h"
/**
* 上传完成后返回的状态信息
*/
@interface QNResponseInfo : NSObject
/// 状态码,具体见 QNErrorCode.h
@property (readonly) int statusCode;
/// response 信息
@property (nonatomic, copy, readonly) NSDictionary *responseDictionary;
/// response 头信息
@property (nonatomic, copy, readonly) NSDictionary *responseHeader;
/// response message
@property (nonatomic, copy, readonly) NSString *message;
/// 七牛服务器生成的请求ID用来跟踪请求信息如果使用过程中出现问题请反馈此ID
@property (nonatomic, copy, readonly) NSString *reqId;
/// 七牛服务器内部跟踪记录
@property (nonatomic, copy, readonly) NSString *xlog;
/// cdn服务器内部跟踪记录
@property (nonatomic, copy, readonly) NSString *xvia;
/// 错误信息,出错时请反馈此记录
@property (nonatomic, copy, readonly) NSError *error;
/// 服务器域名
@property (nonatomic, copy, readonly) NSString *host;
/// 客户端id
@property (nonatomic, readonly) NSString *id;
/// 时间戳
@property (readonly) UInt64 timeStamp;
/// 是否取消
@property (nonatomic, readonly, getter=isCancelled) BOOL canceled;
/// 成功的请求
@property (nonatomic, readonly, getter=isOK) BOOL ok;
/// 是否网络错误
@property (nonatomic, readonly, getter=isConnectionBroken) BOOL broken;
/// 是否TLS错误
@property (nonatomic, readonly) BOOL isTlsError;
/// 是否可以再次重试当遇到权限等怎么重试都不可能成功的问题时返回NO
@property (nonatomic, readonly) BOOL couldRetry;
/// 单个host是否可以再次重试
@property (nonatomic, readonly) BOOL couldHostRetry;
/// 单个Region是否可以再次重试
@property (nonatomic, readonly) BOOL couldRegionRetry;
/// 当前host是否可达
@property (nonatomic, readonly) BOOL canConnectToHost;
/// 当前host是否可用
@property (nonatomic, readonly) BOOL isHostUnavailable;
/// 在断点续上传过程中ctx 信息已过期。
@property (nonatomic, readonly) BOOL isCtxExpiedError;
/// 是否是加速配置错误
@property (nonatomic, readonly) BOOL isTransferAccelerationConfigureError;
/// 是否为 七牛响应
@property (nonatomic, readonly, getter=isNotQiniu) BOOL notQiniu;
//MARK:-- 构造函数 【内部使用】
+ (instancetype)successResponse;
+ (instancetype)successResponseWithDesc:(NSString *)desc;
+ (instancetype)cancelResponse;
+ (instancetype)responseInfoWithNetworkError:(NSString *)desc;
+ (instancetype)responseInfoWithInvalidArgument:(NSString *)desc;
+ (instancetype)responseInfoWithInvalidToken:(NSString *)desc;
+ (instancetype)responseInfoWithFileError:(NSError *)error;
+ (instancetype)responseInfoOfZeroData:(NSString *)path;
+ (instancetype)responseInfoWithLocalIOError:(NSString *)desc;
+ (instancetype)responseInfoWithMaliciousResponseError:(NSString *)desc;
// 使用responseInfoWithSDKInteriorError替代
+ (instancetype)responseInfoWithNoUsableHostError:(NSString *)desc NS_UNAVAILABLE;
+ (instancetype)responseInfoWithSDKInteriorError:(NSString *)desc;
+ (instancetype)responseInfoWithUnexpectedSysCallError:(NSString *)desc;
+ (instancetype)errorResponseInfo:(int)errorType
errorDesc:(NSString *)errorDesc;
- (instancetype)initWithResponseInfoHost:(NSString *)host
response:(NSHTTPURLResponse *)response
body:(NSData *)body
error:(NSError *)error;
@end

341
Pods/Qiniu/QiniuSDK/Http/QNResponseInfo.m generated Executable file
View File

@@ -0,0 +1,341 @@
//
// QNResponseInfo.m
// QiniuSDK
//
// Created by bailong on 14/10/2.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import "QNErrorCode.h"
#import "QNResponseInfo.h"
#import "QNUserAgent.h"
#import "QNUtils.h"
static NSString *kQNErrorDomain = @"qiniu.com";
@interface QNResponseInfo ()
@property (assign) int statusCode;
@property (nonatomic, copy) NSString *message;
@property (nonatomic, copy) NSString *reqId;
@property (nonatomic, copy) NSString *xlog;
@property (nonatomic, copy) NSString *xvia;
@property (nonatomic, copy) NSError *error;
@property (nonatomic, copy) NSString *host;
@property (nonatomic, copy) NSString *id;
@property (assign) UInt64 timeStamp;
@end
@implementation QNResponseInfo
+ (instancetype)successResponse{
return [QNResponseInfo successResponseWithDesc:@"inter:ok"];
}
+ (instancetype)successResponseWithDesc:(NSString *)desc {
QNResponseInfo *responseInfo = [[QNResponseInfo alloc] init];
responseInfo.statusCode = 200;
responseInfo.message = desc;
responseInfo.xlog = @"inter:xlog";
responseInfo.reqId = @"inter:reqid";
return responseInfo;
}
+ (instancetype)cancelResponse {
return [QNResponseInfo errorResponseInfo:kQNRequestCancelled
errorDesc:@"cancelled by user"];
}
+ (instancetype)responseInfoWithNetworkError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNNetworkError
errorDesc:desc];
}
+ (instancetype)responseInfoWithInvalidArgument:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNInvalidArgument
errorDesc:desc];
}
+ (instancetype)responseInfoWithInvalidToken:(NSString *)desc {
return [QNResponseInfo errorResponseInfo:kQNInvalidToken
errorDesc:desc];
}
+ (instancetype)responseInfoWithFileError:(NSError *)error {
return [QNResponseInfo errorResponseInfo:kQNFileError
errorDesc:nil
error:error];
}
+ (instancetype)responseInfoOfZeroData:(NSString *)path {
NSString *desc;
if (path == nil) {
desc = @"data size is 0";
} else {
desc = [[NSString alloc] initWithFormat:@"file %@ size is 0", path];
}
return [QNResponseInfo errorResponseInfo:kQNZeroDataSize
errorDesc:desc];
}
+ (instancetype)responseInfoWithLocalIOError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNLocalIOError
errorDesc:desc];
}
+ (instancetype)responseInfoWithMaliciousResponseError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNMaliciousResponseError
errorDesc:desc];
}
+ (instancetype)responseInfoWithNoUsableHostError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNSDKInteriorError
errorDesc:desc];
}
+ (instancetype)responseInfoWithSDKInteriorError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNSDKInteriorError
errorDesc:desc];
}
+ (instancetype)responseInfoWithUnexpectedSysCallError:(NSString *)desc{
return [QNResponseInfo errorResponseInfo:kQNUnexpectedSysCallError
errorDesc:desc];
}
+ (instancetype)errorResponseInfo:(int)errorType
errorDesc:(NSString *)errorDesc{
return [self errorResponseInfo:errorType errorDesc:errorDesc error:nil];
}
+ (instancetype)errorResponseInfo:(int)errorType
errorDesc:(NSString *)errorDesc
error:(NSError *)error{
QNResponseInfo *response = [[QNResponseInfo alloc] init];
response.statusCode = errorType;
response.message = errorDesc;
if (error) {
response.error = error;
} else {
NSError *error = [[NSError alloc] initWithDomain:kQNErrorDomain
code:errorType
userInfo:@{ @"error" : response.message ?: @"error" }];
response.error = error;
}
return response;
}
- (instancetype)initWithResponseInfoHost:(NSString *)host
response:(NSHTTPURLResponse *)response
body:(NSData *)body
error:(NSError *)error {
self = [super init];
if (self) {
_host = host;
_timeStamp = [[NSDate date] timeIntervalSince1970];
if (response) {
int statusCode = (int)[response statusCode];
NSDictionary *headers = [response allHeaderFields];
_responseHeader = [headers copy];
_statusCode = statusCode;
_reqId = headers[@"x-reqid"];
_xlog = headers[@"x-log"];
_xvia = headers[@"x-via"] ?: headers[@"x-px"] ?: headers[@"fw-via"];
if (_statusCode == 200 && _reqId == nil && _xlog == nil) {
_statusCode = kQNMaliciousResponseError;
_message = @"this is a malicious response";
_responseDictionary = nil;
_error = [[NSError alloc] initWithDomain:kQNErrorDomain code:_statusCode userInfo:@{@"error" : _message}];
} else if (error) {
_error = error;
_statusCode = (int)error.code;
_message = [NSString stringWithFormat:@"%@", error];
_responseDictionary = nil;
} else {
NSMutableDictionary *errorUserInfo = [@{@"errorHost" : host ?: @""} mutableCopy];
if (!body) {
_message = @"no response data";
_error = nil;
_responseDictionary = nil;
} else {
NSError *tmp = nil;
NSDictionary *responseInfo = nil;
responseInfo = [NSJSONSerialization JSONObjectWithData:body options:NSJSONReadingMutableLeaves error:&tmp];
if (tmp){
_message = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
_error = nil;
_responseDictionary = nil;
} else if (statusCode >= 200 && statusCode < 300) {
_error = nil;
_message = @"ok";
_responseDictionary = responseInfo;
} else {
NSString *errorString = responseInfo[@"error"];
if (errorString) {
[errorUserInfo setDictionary:@{@"error" : errorString}];
_message = errorString;
_error = [[NSError alloc] initWithDomain:kQNErrorDomain code:statusCode userInfo:errorUserInfo];
} else {
_message = errorString;
_error = nil;
}
_responseDictionary = responseInfo;
}
}
}
} else if (error) {
_error = error;
_statusCode = (int)error.code;
_message = [NSString stringWithFormat:@"%@", error];
_responseDictionary = nil;
} else {
_statusCode = kQNUnexpectedSysCallError;
_message = @"no response";
}
}
return self;
}
- (BOOL)isCancelled {
return _statusCode == kQNRequestCancelled || _statusCode == -999;
}
- (BOOL)isTlsError{
if (_statusCode == NSURLErrorServerCertificateHasBadDate
|| _statusCode == NSURLErrorClientCertificateRejected
|| _statusCode == NSURLErrorClientCertificateRequired) {
return true;
} else {
return false;
}
}
- (BOOL)isQiniu {
// reqId is nill means the server is not qiniu
return ![self isNotQiniu];
}
- (BOOL)isNotQiniu {
// reqId is nill means the server is not qiniu
return (_statusCode == kQNMaliciousResponseError) || (_statusCode > 0 && _reqId == nil && _xlog == nil);
}
- (BOOL)isOK {
return (_statusCode >= 200 && _statusCode < 300) && _error == nil && (_reqId != nil || _xlog != nil);
}
- (BOOL)couldRetry {
if (![self isQiniu]) {
return YES;
}
if ([self isCtxExpiedError]) {
return YES;
}
if ([self isTransferAccelerationConfigureError]) {
return YES;
}
if (self.isCancelled
|| _statusCode == 100
|| (_statusCode > 300 && _statusCode < 400)
|| (_statusCode > 400 && _statusCode < 500 && _statusCode != 406)
|| _statusCode == 501 || _statusCode == 573
|| _statusCode == 608 || _statusCode == 612 || _statusCode == 614 || _statusCode == 616
|| _statusCode == 619 || _statusCode == 630 || _statusCode == 631 || _statusCode == 640
|| (_statusCode != kQNLocalIOError && _statusCode != kQNUnexpectedSysCallError && _statusCode < -1 && _statusCode > -1000)) {
return NO;
} else {
return YES;
}
}
- (BOOL)couldRegionRetry{
if (![self isQiniu]) {
return YES;
}
if ([self isTransferAccelerationConfigureError]) {
return YES;
}
if (self.isCancelled
|| _statusCode == 100
|| (_statusCode > 300 && _statusCode < 500 && _statusCode != 406)
|| _statusCode == 501 || _statusCode == 573 || _statusCode == 579
|| _statusCode == 608 || _statusCode == 612 || _statusCode == 614 || _statusCode == 616
|| _statusCode == 619 || _statusCode == 630 || _statusCode == 631 || _statusCode == 640
|| _statusCode == 701
|| (_statusCode != kQNLocalIOError && _statusCode != kQNUnexpectedSysCallError && _statusCode < -1 && _statusCode > -1000)) {
return NO;
} else {
return YES;
}
}
- (BOOL)couldHostRetry{
if (![self isQiniu]) {
return YES;
}
if ([self isTransferAccelerationConfigureError]) {
return NO;
}
if (self.isCancelled
|| _statusCode == 100
|| (_statusCode > 300 && _statusCode < 500 && _statusCode != 406)
|| _statusCode == 501 || _statusCode == 502 || _statusCode == 503
|| _statusCode == 571 || _statusCode == 573 || _statusCode == 579 || _statusCode == 599
|| _statusCode == 608 || _statusCode == 612 || _statusCode == 614 || _statusCode == 616
|| _statusCode == 619 || _statusCode == 630 || _statusCode == 631 || _statusCode == 640
|| _statusCode == 701
|| (_statusCode != kQNLocalIOError && _statusCode != kQNUnexpectedSysCallError && _statusCode < -1 && _statusCode > -1000)) {
return NO;
} else {
return YES;
}
}
- (BOOL)canConnectToHost{
if (_statusCode > 99 || self.isCancelled) {
return true;
} else {
return false;
}
}
- (BOOL)isHostUnavailable{
// timeout
if ([self isTransferAccelerationConfigureError] ||
_statusCode == 502 || _statusCode == 503 || _statusCode == 504 || _statusCode == 599) {
return true;
} else {
return false;
}
}
- (BOOL)isCtxExpiedError {
return _statusCode == 701 || (_statusCode == 612 && [_message containsString:@"no such uploadId"]);
}
- (BOOL)isTransferAccelerationConfigureError {
NSString *errorDesc = [NSString stringWithFormat:@"%@", self.error];
return [errorDesc containsString:@"transfer acceleration is not configured on this bucket"];
}
- (BOOL)isConnectionBroken {
return _statusCode == kQNNetworkError || _statusCode == NSURLErrorNotConnectedToInternet;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@= id: %@, ver: %@, status: %d, requestId: %@, xlog: %@, xvia: %@, host: %@ time: %llu error: %@>", NSStringFromClass([self class]), _id, [QNUtils sdkVersion], _statusCode, _reqId, _xlog, _xvia, _host, _timeStamp, _error];
}
@end

View File

@@ -0,0 +1,128 @@
//
// QNUploadRequestMetrics.h
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNUploadRegionInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadMetrics : NSObject
@property (nonatomic, nullable, strong, readonly) NSDate *startDate;
@property (nonatomic, nullable, strong, readonly) NSDate *endDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalElapsedTime;
//MARK:-- 构造
+ (instancetype)emptyMetrics;
- (void)start;
- (void)end;
@end
#define kQNMetricsRequestHijacked @"forsure"
#define kQNMetricsRequestMaybeHijacked @"maybe"
@interface QNUploadSingleRequestMetrics : QNUploadMetrics
// 请求的 httpVersion
@property (nonatomic, copy)NSString *httpVersion;
// 请求是否劫持
@property (nonatomic, copy)NSString *hijacked;
@property (nonatomic, assign, readonly)BOOL isForsureHijacked;
@property (nonatomic, assign, readonly)BOOL isMaybeHijacked;
@property (nonatomic, copy) NSString *syncDnsSource;
@property (nonatomic, strong) NSError *syncDnsError;
// 只有进行网络检测才会有 connectCheckMetrics
@property (nonatomic, nullable , strong) QNUploadSingleRequestMetrics *connectCheckMetrics;
// 错误信息
@property (nonatomic, nullable , strong) NSError *error;
@property (nonatomic, nullable, copy) NSURLRequest *request;
@property (nonatomic, nullable, copy) NSURLResponse *response;
@property (nonatomic, nullable, copy) NSDate *domainLookupStartDate;
@property (nonatomic, nullable, copy) NSDate *domainLookupEndDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalDnsTime;
@property (nonatomic, nullable, copy) NSDate *connectStartDate;
@property (nonatomic, nullable, copy) NSDate *connectEndDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalConnectTime;
@property (nonatomic, nullable, copy) NSDate *secureConnectionStartDate;
@property (nonatomic, nullable, copy) NSDate *secureConnectionEndDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalSecureConnectTime;
@property (nonatomic, nullable, copy) NSDate *requestStartDate;
@property (nonatomic, nullable, copy) NSDate *requestEndDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalRequestTime;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalWaitTime;
@property (nonatomic, nullable, copy) NSDate *responseStartDate;
@property (nonatomic, nullable, copy) NSDate *responseEndDate;
@property (nonatomic, nullable, strong, readonly) NSNumber *totalResponseTime;
@property (nonatomic, assign) int64_t countOfRequestHeaderBytesSent;
@property (nonatomic, assign) int64_t countOfRequestBodyBytesSent;
@property (nonatomic, assign) int64_t countOfResponseHeaderBytesReceived;
@property (nonatomic, assign) int64_t countOfResponseBodyBytesReceived;
@property (nonatomic, nullable, copy) NSString *localAddress;
@property (nonatomic, nullable, copy) NSNumber *localPort;
@property (nonatomic, nullable, copy) NSString *remoteAddress;
@property (nonatomic, nullable, copy) NSNumber *remotePort;
@property (nonatomic, strong, readonly) NSNumber *totalBytes;
@property (nonatomic, strong, readonly) NSNumber *bytesSend;
@property (nonatomic, strong, readonly) NSNumber *perceptiveSpeed;
@end
@interface QNUploadRegionRequestMetrics : QNUploadMetrics
@property (nonatomic, strong, readonly) NSNumber *requestCount;
@property (nonatomic, strong, readonly) NSNumber *bytesSend;
@property (nonatomic, strong, readonly) id <QNUploadRegion> region;
@property (nonatomic, strong, readonly) QNUploadSingleRequestMetrics *lastMetrics;
@property (nonatomic, copy, readonly) NSArray<QNUploadSingleRequestMetrics *> *metricsList;
//MARK:-- 构造
- (instancetype)initWithRegion:(id <QNUploadRegion>)region;
- (void)addMetricsList:(NSArray <QNUploadSingleRequestMetrics *> *)metricsList;
- (void)addMetrics:(QNUploadRegionRequestMetrics*)metrics;
@end
@interface QNUploadTaskMetrics : QNUploadMetrics
@property (nonatomic, copy, readonly) NSString *upType;
@property (nonatomic, strong, readonly) NSNumber *requestCount;
@property (nonatomic, strong, readonly) NSNumber *bytesSend;
@property (nonatomic, strong, readonly) NSNumber *regionCount;
@property (nonatomic, strong, readonly) QNUploadRegionRequestMetrics *lastMetrics;
@property (nonatomic, strong) QNUploadRegionRequestMetrics *ucQueryMetrics;
@property (nonatomic, strong) NSArray<id <QNUploadRegion>> *regions;
+ (instancetype)taskMetrics:(NSString *)upType;
- (void)addMetrics:(QNUploadRegionRequestMetrics *)metrics;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,351 @@
//
// QNUploadRequestMetrics.m
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUtils.h"
#import "QNUploadRequestMetrics.h"
#import "NSURLRequest+QNRequest.h"
#import "QNZoneInfo.h"
@interface QNUploadMetrics()
@property (nullable, strong) NSDate *startDate;
@property (nullable, strong) NSDate *endDate;
@end
@implementation QNUploadMetrics
//MARK:--
+ (instancetype)emptyMetrics {
return [[self alloc] init];
}
- (NSNumber *)totalElapsedTime{
return [QNUtils dateDuration:self.startDate endDate:self.endDate];
}
- (void)start {
self.startDate = [NSDate date];
}
- (void)end {
self.endDate = [NSDate date];
}
@end
@interface QNUploadSingleRequestMetrics()
@property (nonatomic, assign) int64_t countOfRequestHeaderBytes;
@property (nonatomic, assign) int64_t countOfRequestBodyBytes;
@end
@implementation QNUploadSingleRequestMetrics
+ (instancetype)emptyMetrics{
QNUploadSingleRequestMetrics *metrics = [[QNUploadSingleRequestMetrics alloc] init];
return metrics;
}
- (instancetype)init{
if (self = [super init]) {
[self initData];
}
return self;
}
- (void)initData{
_countOfRequestHeaderBytesSent = 0;
_countOfRequestBodyBytesSent = 0;
_countOfResponseHeaderBytesReceived = 0;
_countOfResponseBodyBytesReceived = 0;
}
- (void)setRequest:(NSURLRequest *)request{
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:request.URL
cachePolicy:request.cachePolicy
timeoutInterval:request.timeoutInterval];
newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;
self.countOfRequestHeaderBytes = [NSString stringWithFormat:@"%@", request.allHTTPHeaderFields].length;
self.countOfRequestBodyBytes = [request.qn_getHttpBody length];
_totalBytes = @(self.countOfRequestHeaderBytes + self.countOfRequestBodyBytes);
_request = [newRequest copy];
}
- (void)setResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]] &&
[(NSHTTPURLResponse *)response statusCode] >= 200 &&
[(NSHTTPURLResponse *)response statusCode] < 300) {
_countOfRequestHeaderBytesSent = _countOfRequestHeaderBytes;
_countOfRequestBodyBytesSent = _countOfRequestBodyBytes;
}
if (_countOfResponseBodyBytesReceived <= 0) {
_countOfResponseBodyBytesReceived = response.expectedContentLength;
}
if (_countOfResponseHeaderBytesReceived <= 0 && [response isKindOfClass:[NSHTTPURLResponse class]]) {
_countOfResponseHeaderBytesReceived = [NSString stringWithFormat:@"%@", [(NSHTTPURLResponse *)response allHeaderFields]].length;
}
_response = [response copy];
}
- (BOOL)isForsureHijacked {
return [self.hijacked isEqualToString:kQNMetricsRequestHijacked];
}
- (BOOL)isMaybeHijacked {
return [self.hijacked isEqualToString:kQNMetricsRequestMaybeHijacked];
}
- (NSNumber *)totalElapsedTime{
return [self timeFromStartDate:self.startDate
toEndDate:self.endDate];
}
- (NSNumber *)totalDnsTime{
return [self timeFromStartDate:self.domainLookupStartDate
toEndDate:self.domainLookupEndDate];
}
- (NSNumber *)totalConnectTime{
return [self timeFromStartDate:self.connectStartDate
toEndDate:self.connectEndDate];
}
- (NSNumber *)totalSecureConnectTime{
return [self timeFromStartDate:self.secureConnectionStartDate
toEndDate:self.secureConnectionEndDate];
}
- (NSNumber *)totalRequestTime{
return [self timeFromStartDate:self.requestStartDate
toEndDate:self.requestEndDate];
}
- (NSNumber *)totalWaitTime{
return [self timeFromStartDate:self.requestEndDate
toEndDate:self.responseStartDate];
}
- (NSNumber *)totalResponseTime{
return [self timeFromStartDate:self.responseStartDate
toEndDate:self.responseEndDate];
}
- (NSNumber *)bytesSend{
int64_t totalBytes = [self totalBytes].integerValue;
int64_t senderBytes = self.countOfRequestBodyBytesSent + self.countOfRequestHeaderBytesSent;
int64_t bytes = MIN(totalBytes, senderBytes);
return @(bytes);
}
- (NSNumber *)timeFromStartDate:(NSDate *)startDate toEndDate:(NSDate *)endDate{
return [QNUtils dateDuration:startDate endDate:endDate];
}
- (NSNumber *)perceptiveSpeed {
int64_t size = self.bytesSend.longLongValue + _countOfResponseHeaderBytesReceived + _countOfResponseBodyBytesReceived;
if (size == 0 || self.totalElapsedTime == nil) {
return nil;
}
return [QNUtils calculateSpeed:size totalTime:self.totalElapsedTime.longLongValue];
}
@end
@interface QNUploadRegionRequestMetrics()
@property (nonatomic, strong) id <QNUploadRegion> region;
@property (nonatomic, copy) NSMutableArray<QNUploadSingleRequestMetrics *> *metricsListInter;
@end
@implementation QNUploadRegionRequestMetrics
+ (instancetype)emptyMetrics{
QNUploadRegionRequestMetrics *metrics = [[QNUploadRegionRequestMetrics alloc] init];
return metrics;
}
- (instancetype)initWithRegion:(id<QNUploadRegion>)region{
if (self = [super init]) {
_region = region;
_metricsListInter = [NSMutableArray array];
}
return self;
}
- (QNUploadSingleRequestMetrics *)lastMetrics {
@synchronized (self) {
return self.metricsListInter.lastObject;
}
}
- (NSNumber *)requestCount{
if (self.metricsList) {
return @(self.metricsList.count);
} else {
return @(0);
}
}
- (NSNumber *)bytesSend{
if (self.metricsList) {
long long bytes = 0;
for (QNUploadSingleRequestMetrics *metrics in self.metricsList) {
bytes += metrics.bytesSend.longLongValue;
}
return @(bytes);
} else {
return @(0);
}
}
- (void)addMetricsList:(NSArray<QNUploadSingleRequestMetrics *> *)metricsList{
@synchronized (self) {
[_metricsListInter addObjectsFromArray:metricsList];
}
}
- (void)addMetrics:(QNUploadRegionRequestMetrics*)metrics{
if ([metrics.region.zoneInfo.regionId isEqualToString:self.region.zoneInfo.regionId]) {
@synchronized (self) {
[_metricsListInter addObjectsFromArray:metrics.metricsListInter];
}
}
}
- (NSArray<QNUploadSingleRequestMetrics *> *)metricsList{
@synchronized (self) {
return [_metricsListInter copy];
}
}
@end
@interface QNUploadTaskMetrics()
@property (nonatomic, copy) NSString *upType;
@property (nonatomic, copy) NSMutableArray<NSString *> *metricsKeys;
@property (nonatomic, strong) NSMutableDictionary<NSString *, QNUploadRegionRequestMetrics *> *metricsInfo;
@end
@implementation QNUploadTaskMetrics
+ (instancetype)emptyMetrics{
QNUploadTaskMetrics *metrics = [[QNUploadTaskMetrics alloc] init];
return metrics;
}
+ (instancetype)taskMetrics:(NSString *)upType {
QNUploadTaskMetrics *metrics = [self emptyMetrics];
metrics.upType = upType;
return metrics;
}
- (instancetype)init{
if (self = [super init]) {
_metricsKeys = [NSMutableArray array];
_metricsInfo = [NSMutableDictionary dictionary];
}
return self;
}
- (QNUploadRegionRequestMetrics *)lastMetrics {
if (self.metricsKeys.count < 1) {
return nil;
}
@synchronized (self) {
NSString *key = self.metricsKeys.lastObject;
if (key == nil) {
return nil;
}
return self.metricsInfo[key];
}
}
- (NSNumber *)totalElapsedTime{
NSDictionary *metricsInfo = [self syncCopyMetricsInfo];
if (metricsInfo) {
double time = 0;
for (QNUploadRegionRequestMetrics *metrics in metricsInfo.allValues) {
time += metrics.totalElapsedTime.doubleValue;
}
return time > 0 ? @(time) : nil;
} else {
return nil;
}
}
- (NSNumber *)requestCount{
NSDictionary *metricsInfo = [self syncCopyMetricsInfo];
if (metricsInfo) {
NSInteger count = 0;
for (QNUploadRegionRequestMetrics *metrics in metricsInfo.allValues) {
count += metrics.requestCount.integerValue;
}
return @(count);
} else {
return @(0);
}
}
- (NSNumber *)bytesSend{
NSDictionary *metricsInfo = [self syncCopyMetricsInfo];
if (metricsInfo) {
long long bytes = 0;
for (QNUploadRegionRequestMetrics *metrics in metricsInfo.allValues) {
bytes += metrics.bytesSend.longLongValue;
}
return @(bytes);
} else {
return @(0);
}
}
- (NSNumber *)regionCount{
NSDictionary *metricsInfo = [self syncCopyMetricsInfo];
if (metricsInfo) {
int count = 0;
for (QNUploadRegionRequestMetrics *metrics in metricsInfo.allValues) {
if (![metrics.region.zoneInfo.regionId isEqualToString:QNZoneInfoEmptyRegionId]) {
count += 1;
}
}
return @(count);
} else {
return @(0);
}
}
- (void)setUcQueryMetrics:(QNUploadRegionRequestMetrics *)ucQueryMetrics {
_ucQueryMetrics = ucQueryMetrics;
[self addMetrics:ucQueryMetrics];
}
- (void)addMetrics:(QNUploadRegionRequestMetrics *)metrics{
NSString *regionId = metrics.region.zoneInfo.regionId;
if (!regionId) {
return;
}
@synchronized (self) {
QNUploadRegionRequestMetrics *metricsOld = self.metricsInfo[regionId];
if (metricsOld) {
[metricsOld addMetrics:metrics];
} else {
[self.metricsKeys addObject:regionId];
self.metricsInfo[regionId] = metrics;
}
}
}
- (NSDictionary<NSString *, QNUploadRegionRequestMetrics *> *)syncCopyMetricsInfo {
@synchronized (self) {
return [_metricsInfo copy];
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// QNUploadRequestState.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadRequestState : NSObject
// old server 不验证tls sni
@property(nonatomic, assign)BOOL isUseOldServer;
// 用户是否取消
@property(nonatomic, assign)BOOL isUserCancel;
- (instancetype)copy;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,32 @@
//
// QNUploadRequestState.m
// QiniuSDK_Mac
//
// Created by yangsen on 2020/11/17.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNIUploadServer.h"
#import "QNUploadRequestState.h"
@implementation QNUploadRequestState
- (instancetype)init{
if (self = [super init]) {
[self initData];
}
return self;
}
- (void)initData{
_isUserCancel = NO;
_isUseOldServer = NO;
}
- (instancetype)copy {
QNUploadRequestState *state = [[QNUploadRequestState alloc] init];
state.isUserCancel = self.isUserCancel;
state.isUseOldServer = self.isUseOldServer;
return state;
}
@end

39
Pods/Qiniu/QiniuSDK/Http/QNUserAgent.h generated Normal file
View File

@@ -0,0 +1,39 @@
//
// QNUserAgent.h
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* UserAgent
*
*/
#define kQNUserAgent [QNUserAgent sharedInstance]
@interface QNUserAgent : NSObject
/**
* 用户id
*/
@property (copy, nonatomic, readonly) NSString *id;
/**
* UserAgent 字串
*/
- (NSString *)description;
/**
* UserAgent + AK 字串
* @param access access信息
*/
- (NSString *)getUserAgent:(NSString *)access;
/**
* 单例
*/
+ (instancetype)sharedInstance;
@end

91
Pods/Qiniu/QiniuSDK/Http/QNUserAgent.m generated Normal file
View File

@@ -0,0 +1,91 @@
//
// QNUserAgent.m
// QiniuSDK
//
// Created by bailong on 14-9-29.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import "QNUserAgent.h"
#import "QNUtils.h"
static NSString *qn_clientId(void) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
NSString *s = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
if (s == nil) {
s = @"simulator";
}
return s;
#else
long long now_timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
int r = arc4random() % 1000;
return [NSString stringWithFormat:@"%lld%u", now_timestamp, r];
#endif
}
static NSString *qn_userAgent(NSString *id, NSString *ak) {
NSString *addition = @"";
#if DEBUG
addition = @"_Debug";
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED
return [NSString stringWithFormat:@"QiniuObject-C%@/%@ (%@; iOS %@; %@; %@)", addition, [QNUtils sdkVersion], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], id, ak];
#else
return [NSString stringWithFormat:@"QiniuObject-C%@/%@ (Mac OS X %@; %@; %@)", addition, [QNUtils sdkVersion], [[NSProcessInfo processInfo] operatingSystemVersionString], id, ak];
#endif
}
@interface QNUserAgent ()
@property (nonatomic) NSString *ua;
@end
@implementation QNUserAgent
- (NSString *)description {
return _ua;
}
- (instancetype)init {
if (self = [super init]) {
_id = qn_clientId();
}
return self;
}
/**
* UserAgent
*/
- (NSString *)getUserAgent:(NSString *)access {
NSString *ak;
if (access == nil || access.length == 0) {
ak = @"-";
} else {
ak = access;
}
return qn_userAgent(_id, ak);
}
/**
*
*/
+ (instancetype)sharedInstance {
static QNUserAgent *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
@end

View File

@@ -0,0 +1,50 @@
//
// NSURLRequest+QNRequest.h
// AppTest
//
// Created by yangsen on 2020/4/8.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSURLRequest(QNRequest)
/// 请求id【内部使用】
/// 只有通过设置qn_domain才会有效
@property(nonatomic, strong, nullable, readonly)NSString *qn_identifier;
/// 请求domain【内部使用】
/// 只有通过NSMutableURLRequest设置才会有效
@property(nonatomic, strong, nullable, readonly)NSString *qn_domain;
/// 请求ip【内部使用】
/// 只有通过NSMutableURLRequest设置才会有效
@property(nonatomic, strong, nullable, readonly)NSString *qn_ip;
/// 请求头信息 去除七牛内部标记占位
@property(nonatomic, strong, nullable, readonly)NSDictionary *qn_allHTTPHeaderFields;
+ (instancetype)qn_requestWithURL:(NSURL *)url;
/// 获取请求体
- (NSData *)qn_getHttpBody;
- (BOOL)qn_isHttps;
@end
@interface NSMutableURLRequest(QNRequest)
/// 请求domain【内部使用】
@property(nonatomic, strong, nullable)NSString *qn_domain;
/// 请求ip【内部使用】
@property(nonatomic, strong, nullable)NSString *qn_ip;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,123 @@
//
// NSURLRequest+QNRequest.m
// AppTest
//
// Created by yangsen on 2020/4/8.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <objc/runtime.h>
#import "NSURLRequest+QNRequest.h"
@implementation NSURLRequest(QNRequest)
#define kQNURLRequestHostKey @"Host"
#define kQNURLRequestIPKey @"QNURLRequestIP"
#define kQNURLRequestIdentifierKey @"QNURLRequestIdentifier"
- (NSString *)qn_identifier{
return self.allHTTPHeaderFields[kQNURLRequestIdentifierKey];
}
- (NSString *)qn_domain{
NSString *host = self.allHTTPHeaderFields[kQNURLRequestHostKey];
if (host == nil) {
host = self.URL.host;
}
return host;
}
- (NSString *)qn_ip{
return self.allHTTPHeaderFields[kQNURLRequestIPKey];
}
- (NSDictionary *)qn_allHTTPHeaderFields{
NSDictionary *headerFields = [self.allHTTPHeaderFields copy];
NSMutableDictionary *headerFieldsNew = [NSMutableDictionary dictionary];
for (NSString *key in headerFields) {
if (![key isEqualToString:kQNURLRequestIdentifierKey]) {
[headerFieldsNew setObject:headerFields[key] forKey:key];
}
}
return [headerFieldsNew copy];
}
+ (instancetype)qn_requestWithURL:(NSURL *)url{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:url.host forHTTPHeaderField:kQNURLRequestHostKey];
return request;
}
- (NSData *)qn_getHttpBody{
if (self.HTTPBody ||
(![self.HTTPMethod isEqualToString:@"POST"] && ![self.HTTPMethod isEqualToString:@"PUT"])) {
return self.HTTPBody;
}
NSInteger maxLength = 1024;
uint8_t d[maxLength];
NSInputStream *stream = self.HTTPBodyStream;
NSMutableData *data = [NSMutableData data];
[stream open];
BOOL end = NO;
while (!end) {
NSInteger bytesRead = [stream read:d maxLength:maxLength];
if (bytesRead == 0) {
end = YES;
} else if (bytesRead == -1){
end = YES;
} else if (stream.streamError == nil){
[data appendBytes:(void *)d length:bytesRead];
}
}
[stream close];
return [data copy];
}
- (BOOL)qn_isHttps{
if ([self.URL.absoluteString rangeOfString:@"https://"].location != NSNotFound) {
return YES;
} else {
return NO;
}
}
@end
@implementation NSMutableURLRequest(QNRequest)
- (void)setQn_domain:(NSString *)qn_domain{
if (qn_domain) {
[self addValue:qn_domain forHTTPHeaderField:kQNURLRequestHostKey];
} else {
[self setValue:nil forHTTPHeaderField:kQNURLRequestHostKey];
}
NSString *identifier = [NSString stringWithFormat:@"%p-%@", &self, qn_domain];
[self setQn_identifier:identifier];
}
- (void)setQn_ip:(NSString *)qn_ip{
if (qn_ip) {
[self addValue:qn_ip forHTTPHeaderField:kQNURLRequestIPKey];
} else {
[self setValue:nil forHTTPHeaderField:kQNURLRequestIPKey];
}
}
- (void)setQn_identifier:(NSString *)qn_identifier{
if (qn_identifier) {
[self addValue:qn_identifier forHTTPHeaderField:kQNURLRequestIdentifierKey];
} else {
[self setValue:nil forHTTPHeaderField:kQNURLRequestIdentifierKey];
}
}
@end

View File

@@ -0,0 +1,17 @@
//
// QNCFHttpClient.h
// QiniuSDK
//
// Created by yangsen on 2021/10/11.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNRequestClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNCFHttpClient : NSObject <QNRequestClient>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,160 @@
//
// QNCFHttpClient.m
// QiniuSDK
//
// Created by yangsen on 2021/10/11.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import "QNCFHttpClient.h"
#import "QNCFHttpClientInner.h"
#import "NSURLRequest+QNRequest.h"
#import "QNUploadRequestMetrics.h"
#import "QNCFHttpThreadPool.h"
@interface QNCFHttpClient() <QNCFHttpClientInnerDelegate>
@property(nonatomic, assign)BOOL hasCallBack;
@property(nonatomic, assign)NSInteger redirectCount;
@property(nonatomic, assign)NSInteger maxRedirectCount;
@property(nonatomic, strong)NSURLRequest *request;
@property(nonatomic, strong)NSURLResponse *response;
@property(nonatomic, strong)NSDictionary *connectionProxy;
@property(nonatomic, strong)QNUploadSingleRequestMetrics *requestMetrics;
@property(nonatomic, strong)NSMutableData *responseData;
@property(nonatomic, copy)void(^progress)(long long totalBytesWritten, long long totalBytesExpectedToWrite);
@property(nonatomic, copy)QNRequestClientCompleteHandler complete;
@property(nonatomic, strong)QNCFHttpClientInner *httpClient;
@property(nonatomic, strong)QNCFHttpThread *thread;
@end
@implementation QNCFHttpClient
- (NSString *)clientId {
return @"CFNetwork";
}
- (instancetype)init {
if (self = [super init]) {
self.redirectCount = 0;
self.maxRedirectCount = 15;
self.hasCallBack = false;
}
return self;
}
- (void)request:(NSURLRequest *)request
server:(id <QNUploadServer>)server
connectionProxy:(NSDictionary *)connectionProxy
progress:(void (^)(long long, long long))progress
complete:(QNRequestClientCompleteHandler)complete {
self.thread = [[QNCFHttpThreadPool shared] getOneThread];
// ip 使
if (server && server.ip.length > 0 && server.host.length > 0) {
NSString *urlString = request.URL.absoluteString;
urlString = [urlString stringByReplacingOccurrencesOfString:server.host withString:server.ip];
NSMutableURLRequest *requestNew = [request mutableCopy];
requestNew.URL = [NSURL URLWithString:urlString];
requestNew.qn_domain = server.host;
self.request = [requestNew copy];
} else {
self.request = request;
}
self.connectionProxy = connectionProxy;
self.progress = progress;
self.complete = complete;
self.requestMetrics = [QNUploadSingleRequestMetrics emptyMetrics];
self.requestMetrics.request = self.request;
self.requestMetrics.remoteAddress = self.request.qn_ip;
self.requestMetrics.remotePort = self.request.qn_isHttps ? @443 : @80;
[self.requestMetrics start];
self.responseData = [NSMutableData data];
self.httpClient = [QNCFHttpClientInner client:self.request connectionProxy:connectionProxy];
self.httpClient.delegate = self;
[self.httpClient performSelector:@selector(main)
onThread:self.thread
withObject:nil
waitUntilDone:NO];
}
- (void)cancel {
if (self.thread) {
return;
}
[self.httpClient performSelector:@selector(cancel)
onThread:self.thread
withObject:nil
waitUntilDone:NO];
}
- (void)completeAction:(NSError *)error {
@synchronized (self) {
if (self.hasCallBack) {
return;
}
self.hasCallBack = true;
}
self.requestMetrics.response = self.response;
[self.requestMetrics end];
if (self.complete) {
self.complete(self.response, self.requestMetrics, self.responseData, error);
}
[[QNCFHttpThreadPool shared] subtractOperationCountOfThread:self.thread];
}
//MARK: -- delegate
- (void)didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
self.requestMetrics.countOfRequestBodyBytesSent = totalBytesSent;
if (self.progress) {
self.progress(totalBytesSent, totalBytesExpectedToSend);
}
}
- (void)didFinish {
self.requestMetrics.responseEndDate = [NSDate date];
[self completeAction:nil];
}
- (void)didLoadData:(nonnull NSData *)data {
[self.responseData appendData:data];
}
- (void)onError:(nonnull NSError *)error {
[self completeAction:error];
}
- (void)onReceiveResponse:(NSURLResponse *)response httpVersion:(NSString *)httpVersion{
self.requestMetrics.responseStartDate = [NSDate date];
if ([httpVersion isEqualToString:@"http/1.0"]) {
self.requestMetrics.httpVersion = @"1.0";
} else if ([httpVersion isEqualToString:@"http/1.1"]) {
self.requestMetrics.httpVersion = @"1.1";
} else if ([httpVersion isEqualToString:@"h2"]) {
self.requestMetrics.httpVersion = @"2";
} else if ([httpVersion isEqualToString:@"h3"]) {
self.requestMetrics.httpVersion = @"3";
} else {
self.requestMetrics.httpVersion = httpVersion;
}
self.response = response;
}
- (void)redirectedToRequest:(nonnull NSURLRequest *)request redirectResponse:(nonnull NSURLResponse *)redirectResponse {
if (self.redirectCount < self.maxRedirectCount) {
self.redirectCount += 1;
[self request:request server:nil connectionProxy:self.connectionProxy progress:self.progress complete:self.complete];
} else {
[self didFinish];
}
}
@end

View File

@@ -0,0 +1,43 @@
//
// QNHttpClient.h
// AppTest
//
// Created by yangsen on 2020/4/7.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol QNCFHttpClientInnerDelegate <NSObject>
- (void)redirectedToRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse;
- (void)onError:(NSError *)error;
- (void)didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void)onReceiveResponse:(NSURLResponse *)response httpVersion:(NSString *)httpVersion;
- (void)didLoadData:(NSData *)data;
- (void)didFinish;
@end
@interface QNCFHttpClientInner : NSOperation
@property(nonatomic, strong, readonly)NSMutableURLRequest *request;
@property(nonatomic, strong, readonly)NSDictionary *connectionProxy;
@property(nonatomic, weak)id <QNCFHttpClientInnerDelegate> delegate;
+ (instancetype)client:(NSURLRequest *)request connectionProxy:(NSDictionary *)connectionProxy;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,864 @@
//
// QNHttpClient.m
// AppTest
//
// Created by yangsen on 2020/4/7.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNErrorCode.h"
#import "QNDefine.h"
#import "QNCFHttpClientInner.h"
#import "NSURLRequest+QNRequest.h"
#import <sys/errno.h>
#define kQNCFHttpClientErrorDomain @"CFNetwork"
@interface QNCFHttpClientInner()<NSStreamDelegate>
@property(nonatomic, assign)BOOL isCallFinishOrError;
@property(nonatomic, assign)BOOL isCompleted;
@property(nonatomic, strong)NSMutableURLRequest *request;
@property(nonatomic, strong)NSDictionary *connectionProxy;
@property(nonatomic, assign)BOOL isReadResponseHeader;
@property(nonatomic, assign)BOOL isReadResponseBody;
@property(nonatomic, assign)BOOL isInputStreamEvaluated;
@property(nonatomic, strong)NSInputStream *inputStream;
//
@property(nonatomic, strong)NSTimer *progressTimer; //
@property(nonatomic, assign)int64_t totalBytesSent; //
@property(nonatomic, assign)int64_t totalBytesExpectedToSend; //
@end
@implementation QNCFHttpClientInner
+ (instancetype)client:(NSURLRequest *)request connectionProxy:(nonnull NSDictionary *)connectionProxy{
if (!request) {
return nil;
}
QNCFHttpClientInner *client = [[QNCFHttpClientInner alloc] init];
client.connectionProxy = connectionProxy;
client.request = [request mutableCopy];
client.isCompleted = false;
return client;
}
- (void)main {
[self prepare];
[self openInputStream];
[self startProgress];
}
- (void)prepare {
@autoreleasepool {
self.inputStream = [self createInputStream:self.request];
}
NSString *host = [self.request qn_domain];
if ([self.request qn_isHttps]) {
[self setInputStreamSNI:self.inputStream sni:host];
}
[self setupProgress];
}
- (void)releaseResource{
[self endProgress:YES];
[self closeInputStream];
}
- (void)cancel {
[self releaseResource];
[self delegate_onError:[self createError:NSURLErrorCancelled errorDescription:@"user cancel"]];
}
//MARK: -- request -> stream
- (NSInputStream *)createInputStream:(NSURLRequest *)urlRequest{
CFReadStreamRef readStream = NULL;
@autoreleasepool {
CFStringRef urlString = (__bridge CFStringRef) [urlRequest.URL absoluteString];
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault,
urlString,
NULL);
CFStringRef httpMethod = (__bridge CFStringRef) urlRequest.HTTPMethod;
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
httpMethod,
url,
kCFHTTPVersion1_1);
CFRelease(url);
NSDictionary *headFieldInfo = self.request.qn_allHTTPHeaderFields;
for (NSString *headerField in headFieldInfo) {
CFStringRef headerFieldP = (__bridge CFStringRef)headerField;
CFStringRef headerFieldValueP = (__bridge CFStringRef)(headFieldInfo[headerField]);
CFHTTPMessageSetHeaderFieldValue(request, headerFieldP, headerFieldValueP);
}
NSData *httpBody = [self.request qn_getHttpBody];
if (httpBody) {
CFDataRef bodyData = (__bridge CFDataRef) httpBody;
CFHTTPMessageSetBody(request, bodyData);
}
readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
CFRelease(request);
}
@autoreleasepool {
if (self.connectionProxy) {
for (NSString *key in self.connectionProxy.allKeys) {
NSObject *value = self.connectionProxy[key];
if (key.length > 0) {
CFReadStreamSetProperty(readStream, (__bridge CFTypeRef _Null_unspecified)key, (__bridge CFTypeRef _Null_unspecified)(value));
}
}
}
}
return (__bridge_transfer NSInputStream *) readStream;
}
- (void)setInputStreamSNI:(NSInputStream *)inputStream sni:(NSString *)sni{
if (!sni || sni.length == 0) {
return;
}
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
[settings setObject:NSStreamSocketSecurityLevelNegotiatedSSL
forKey:NSStreamSocketSecurityLevelKey];
[settings setObject:sni
forKey:(NSString *)kCFStreamSSLPeerName];
[inputStream setProperty:settings forKey:(NSString *)CFBridgingRelease(kCFStreamPropertySSLSettings)];
}
//MARK: -- stream action
- (void)openInputStream{
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.inputStream.delegate = self;
[self.inputStream open];
}
- (void)closeInputStream {
@synchronized (self) {
if (self.inputStream) {
[self.inputStream close];
[self.inputStream setDelegate:nil];
[self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.inputStream = nil;
}
}
}
- (BOOL)shouldEvaluateInputStreamServerTrust{
if (![self.request qn_isHttps] || self.isInputStreamEvaluated) {
return NO;
} else {
return YES;
}
}
- (void)inputStreamGetAndNotifyHttpResponse{
@synchronized (self) {
if (self.isReadResponseHeader) {
return;
}
self.isReadResponseHeader = YES;
}
CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
CFHTTPMessageRef httpMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(httpMessage);
NSDictionary *headInfo = (__bridge_transfer NSDictionary *)headerFields;
CFStringRef httpVersion = CFHTTPMessageCopyVersion(httpMessage);
NSString *httpVersionInfo = (__bridge_transfer NSString *)httpVersion;
CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(httpMessage);
if (![self isHttpRedirectStatusCode:statusCode]) {
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL statusCode:statusCode HTTPVersion:httpVersionInfo headerFields:headInfo];
[self delegate_onReceiveResponse:response httpVersion:httpVersionInfo];
}
CFRelease(httpMessage);
}
- (void)inputStreamGetAndNotifyHttpData{
@synchronized (self) {
if (self.isReadResponseBody) {
return;
}
self.isReadResponseBody = YES;
}
UInt8 buffer[16 * 1024];
UInt8 *buf = NULL;
NSUInteger length = 0;
if (![self.inputStream getBuffer:&buf length:&length]) {
NSInteger amount = [self.inputStream read:buffer maxLength:sizeof(buffer)];
buf = buffer;
length = amount;
}
NSData *data = [[NSData alloc] initWithBytes:buf length:length];
[self delegate_didLoadData:data];
}
- (BOOL)isInputStreamHttpResponseHeaderComplete{
CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
BOOL isComplete = CFHTTPMessageIsHeaderComplete(responseMessage);
CFRelease(responseMessage);
return isComplete;
}
- (BOOL)shouldInputStreamRedirect{
CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(responseMessage);
CFRelease(responseMessage);
return [self isHttpRedirectStatusCode:statusCode];
}
- (BOOL)isHttpRedirectStatusCode:(NSInteger)code{
if (code == 301 || code == 302 || code == 303 || code == 307) {
return YES;
} else {
return NO;
}
}
- (void)inputStreamRedirect{
CFReadStreamRef readStream = (__bridge CFReadStreamRef)self.inputStream;
CFHTTPMessageRef responseMessage = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(responseMessage);
NSDictionary *headInfo = (__bridge_transfer NSDictionary *)headerFields;
NSString *urlString = headInfo[@"Location"];
if (!urlString) {
urlString = headInfo[@"location"];
}
if (!urlString) {
return;
}
CFStringRef httpVersion = CFHTTPMessageCopyVersion(responseMessage);
NSString *httpVersionString = (__bridge_transfer NSString *)httpVersion;
CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(responseMessage);
NSDictionary *requestHeader = self.request.allHTTPHeaderFields;
if (statusCode == 303) {
NSMutableDictionary *header = [NSMutableDictionary dictionary];
if (requestHeader[@"User-Agent"]) {
header[@"User-Agent"] = requestHeader[@"User-Agent"];
}
if (requestHeader[@"Accept"]) {
header[@"Accept"] = requestHeader[@"Accept"];
}
requestHeader = [header copy];
}
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"GET";
[request setAllHTTPHeaderFields:requestHeader];
NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL
statusCode:statusCode
HTTPVersion:httpVersionString
headerFields:headInfo];
[self releaseResource];
[self delegate_redirectedToRequest:request redirectResponse:response];
CFRelease(responseMessage);
}
//MARK: -- NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
@autoreleasepool {
switch (eventCode) {
case NSStreamEventHasBytesAvailable:{
if (![self isInputStreamHttpResponseHeaderComplete]) {
break;
}
[self inputStreamGetAndNotifyHttpResponse];
[self inputStreamGetAndNotifyHttpData];
}
break;
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventErrorOccurred:{
[self releaseResource];
[self endProgress: YES];
[self delegate_onError:[self translateCFNetworkErrorIntoUrlError:[aStream streamError]]];
}
break;
case NSStreamEventEndEncountered:{
if ([self shouldInputStreamRedirect]) {
[self inputStreamRedirect];
} else {
[self inputStreamGetAndNotifyHttpResponse];
[self inputStreamGetAndNotifyHttpData];
[self releaseResource];
[self endProgress: NO];
[self delegate_didFinish];
}
}
break;
default:
break;
}
}
}
//MARK: -- progress and timer action
- (void)setupProgress{
self.totalBytesExpectedToSend = [self.request.qn_getHttpBody length];
}
- (void)startProgress{
[self createTimer];
}
- (void)endProgress:(BOOL)hasError{
[self invalidateTimer];
if (!hasError) {
[self delegate_didSendBodyData:self.totalBytesExpectedToSend - self.totalBytesSent
totalBytesSent:self.totalBytesExpectedToSend
totalBytesExpectedToSend:self.totalBytesExpectedToSend];
}
}
- (void)createTimer{
if (_progressTimer) {
[self invalidateTimer];
}
kQNWeakSelf;
NSTimer *timer = [NSTimer timerWithTimeInterval:0.3
target:weak_self
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[self timerAction];
_progressTimer = timer;
}
- (void)invalidateTimer{
[self.progressTimer invalidate];
self.progressTimer = nil;
}
- (void)timerAction{
long long totalBytesSent = [(NSNumber *)CFBridgingRelease(CFReadStreamCopyProperty((CFReadStreamRef)[self inputStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) longLongValue];
long long bytesSent = totalBytesSent - self.totalBytesSent;
self.totalBytesSent = totalBytesSent;
if (bytesSent > 0 && self.totalBytesSent <= self.totalBytesSent) {
[self delegate_didSendBodyData:bytesSent
totalBytesSent:self.totalBytesSent
totalBytesExpectedToSend:self.totalBytesExpectedToSend];
}
}
- (NSError *)translateCFNetworkErrorIntoUrlError:(NSError *)cfError{
if (cfError == nil) {
return nil;
}
NSInteger errorCode = kQNNetworkError;
NSString *errorInfo = [NSString stringWithFormat:@"cf client:[%ld] %@", (long)cfError.code, cfError.localizedDescription];
switch (cfError.code) {
case ENOENT: /* No such file or directory */
errorCode = NSFileNoSuchFileError;
break;
case EIO: /* Input/output error */
errorCode = kQNLocalIOError;
break;
case E2BIG: /* Argument list too long */
break;
case ENOEXEC: /* Exec format error */
errorCode = kQNLocalIOError;
break;
case EBADF: /* Bad file descriptor */
errorCode = kQNLocalIOError;
break;
case ECHILD: /* No child processes */
errorCode = kQNUnexpectedSysCallError;
break;
case EDEADLK: /* Resource deadlock avoided */
errorCode = kQNUnexpectedSysCallError;
break;
case ENOMEM: /* Cannot allocate memory */
errorCode = kQNUnexpectedSysCallError;
break;
case EACCES: /* Permission denied */
errorCode = NSURLErrorNoPermissionsToReadFile;
break;
case EFAULT: /* Bad address */
errorCode = NSURLErrorBadURL;
break;
case EBUSY: /* Device / Resource busy */
errorCode = kQNUnexpectedSysCallError;
break;
case EEXIST: /* File exists */
errorCode = kQNUnexpectedSysCallError;
break;
case ENODEV: /* Operation not supported by device */
errorCode = kQNUnexpectedSysCallError;
break;
case EISDIR: /* Is a directory */
errorCode = NSURLErrorFileIsDirectory;
break;
case ENOTDIR: /* Not a directory */
errorCode = kQNUnexpectedSysCallError;
break;
case EINVAL: /* Invalid argument */
errorCode = kQNUnexpectedSysCallError;
break;
case ENFILE: /* Too many open files in system */
errorCode = kQNUnexpectedSysCallError;
break;
case EMFILE: /* Too many open files */
errorCode = kQNUnexpectedSysCallError;
break;
case EFBIG: /* File too large */
errorCode = kQNUnexpectedSysCallError;
break;
case ENOSPC: /* No space left on device */
errorCode = kQNUnexpectedSysCallError;
break;
case ESPIPE: /* Illegal seek */
errorCode = kQNUnexpectedSysCallError;
break;
case EMLINK: /* Too many links */
errorCode = kQNUnexpectedSysCallError;
break;
case EPIPE: /* Broken pipe */
errorCode = kQNUnexpectedSysCallError;
break;
case EDOM: /* Numerical argument out of domain */
errorCode = kQNUnexpectedSysCallError;
break;
case ERANGE: /* Result too large */
errorCode = kQNUnexpectedSysCallError;
break;
case EAGAIN: /* Resource temporarily unavailable */
break;
case ENOTSOCK: /* Socket operation on non-socket */
break;
case EDESTADDRREQ: /* Destination address required */
errorCode = NSURLErrorBadURL;
break;
case EMSGSIZE: /* Message too long */
break;
case EPROTOTYPE: /* Protocol wrong type for socket */
break;
case ENOPROTOOPT: /* Protocol not available */
break;
case EPROTONOSUPPORT: /* Protocol not supported */
break;
case ENOTSUP: /* Operation not supported */
break;
case EPFNOSUPPORT: /* Protocol family not supported */
break;
case EAFNOSUPPORT: /* Address family not supported by protocol family */
break;
case EADDRINUSE: /* Address already in use */
break;
case EADDRNOTAVAIL: /* Can't assign requested address */
break;
case ENETDOWN: /* Network is down */
errorCode = NSURLErrorCannotConnectToHost;
break;
case ENETUNREACH: /* Network is unreachable */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case ENETRESET: /* Network dropped connection on reset */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case ECONNABORTED: /* Software caused connection abort */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case ECONNRESET: /* Connection reset by peer */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case ENOBUFS: /* No buffer space available */
errorCode = kQNUnexpectedSysCallError;
break;
case EISCONN: /* Socket is already connected */
break;
case ENOTCONN: /* Socket is not connected */
errorCode = NSURLErrorCannotConnectToHost;
break;
case ESHUTDOWN: /* Can't send after socket shutdown */
break;
case ETOOMANYREFS: /* Too many references: can't splice */
break;
case ETIMEDOUT: /* Operation timed out */
errorCode = NSURLErrorTimedOut;
break;
case ECONNREFUSED: /* Connection refused */
errorCode = NSURLErrorCannotConnectToHost;
break;
case ELOOP: /* Too many levels of symbolic links */
errorCode = kQNUnexpectedSysCallError;
break;
case ENAMETOOLONG: /* File name too long */
break;
case EHOSTDOWN: /* Host is down */
break;
case EHOSTUNREACH: /* No route to host */
break;
case ENOTEMPTY: /* Directory not empty */
break;
case EPROCLIM: /* Too many processes */
errorCode = kQNUnexpectedSysCallError;
break;
case EUSERS: /* Too many users */
errorCode = kQNUnexpectedSysCallError;
break;
case EDQUOT: /* Disc quota exceeded */
errorCode = kQNUnexpectedSysCallError;
break;
case ESTALE: /* Stale NFS file handle */
errorCode = kQNUnexpectedSysCallError;
break;
case EREMOTE: /* Too many levels of remote in path */
break;
case EBADRPC: /* RPC struct is bad */
errorCode = kQNUnexpectedSysCallError;
break;
case ERPCMISMATCH: /* RPC version wrong */
errorCode = kQNUnexpectedSysCallError;
break;
case EPROGUNAVAIL: /* RPC prog. not avail */
errorCode = kQNUnexpectedSysCallError;
break;
case EPROGMISMATCH: /* Program version wrong */
errorCode = kQNUnexpectedSysCallError;
break;
case EPROCUNAVAIL: /* Bad procedure for program */
errorCode = kQNUnexpectedSysCallError;
break;
case ENOLCK: /* No locks available */
errorCode = kQNUnexpectedSysCallError;
break;
case ENOSYS: /* Function not implemented */
errorCode = kQNUnexpectedSysCallError;
break;
case EFTYPE: /* Inappropriate file type or format */
break;
case EAUTH: /* Authentication error */
break;
case ENEEDAUTH: /* Need authenticator */
break;
case EPWROFF: /* Device power is off */
errorCode = kQNUnexpectedSysCallError;
break;
case EDEVERR: /* Device error, e.g. paper out */
errorCode = kQNUnexpectedSysCallError;
break;
case EOVERFLOW: /* Value too large to be stored in data type */
errorCode = kQNUnexpectedSysCallError;
break;
case EBADEXEC: /* Bad executable */
errorCode = kQNUnexpectedSysCallError;
break;
case EBADARCH: /* Bad CPU type in executable */
errorCode = kQNUnexpectedSysCallError;
break;
case ESHLIBVERS: /* Shared library version mismatch */
errorCode = kQNUnexpectedSysCallError;
break;
case EBADMACHO: /* Malformed Macho file */
errorCode = kQNUnexpectedSysCallError;
break;
case ECANCELED: /* Operation canceled */
errorCode = NSURLErrorCancelled;
break;
case EIDRM: /* Identifier removed */
break;
case ENOMSG: /* No message of desired type */
break;
case EILSEQ: /* Illegal byte sequence */
break;
case ENOATTR: /* Attribute not found */
break;
case EBADMSG: /* Bad message */
break;
case EMULTIHOP: /* Reserved */
break;
case ENODATA: /* No message available on STREAM */
break;
case ENOLINK: /* Reserved */
break;
case ENOSR: /* No STREAM resources */
break;
case ENOSTR: /* Not a STREAM */
break;
case EPROTO: /* Protocol error */
break;
case ETIME: /* STREAM ioctl timeout */
errorCode = NSURLErrorTimedOut;
break;
case EOPNOTSUPP: /* Operation not supported on socket */
break;
case ENOPOLICY: /* No such policy registered */
break;
case ENOTRECOVERABLE: /* State not recoverable */
break;
case EOWNERDEAD: /* Previous owner died */
errorCode = kQNUnexpectedSysCallError;
break;
case EQFULL: /* Interface output queue is full */
break;
case -9800: /* SSL protocol error */
errorCode = NSURLErrorSecureConnectionFailed;
break;
case -9801: /* Cipher Suite negotiation failure */
errorCode = NSURLErrorSecureConnectionFailed;
break;
case -9802: /* Fatal alert */
errorCode = kQNUnexpectedSysCallError;
break;
case -9803: /* I/O would block (not fatal) */
errorCode = kQNUnexpectedSysCallError;
break;
case -9804: /* attempt to restore an unknown session */
errorCode = kQNUnexpectedSysCallError;
break;
case -9805: /* connection closed gracefully */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case -9806: /* connection closed via error */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case -9807: /* invalid certificate chain */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9808: /* bad certificate format */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9809: /* underlying cryptographic error */
errorCode = NSURLErrorSecureConnectionFailed;
break;
case -9810: /* Internal error */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9811: /* module attach failure */
errorCode = kQNUnexpectedSysCallError;
break;
case -9812: /* valid cert chain, untrusted root */
errorCode = NSURLErrorServerCertificateHasUnknownRoot;
break;
case -9813: /* cert chain not verified by root */
errorCode = NSURLErrorServerCertificateHasUnknownRoot;
break;
case -9814: /* chain had an expired cert */
errorCode = NSURLErrorServerCertificateHasBadDate;
break;
case -9815: /* chain had a cert not yet valid */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9816: /* server closed session with no notification */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case -9817: /* insufficient buffer provided */
errorCode = NSURLErrorCannotDecodeRawData;
break;
case -9818: /* bad SSLCipherSuite */
errorCode = NSURLErrorClientCertificateRejected;
break;
case -9819: /* unexpected message received */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9820: /* bad MAC */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9821: /* decryption failed */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9822: /* record overflow */
errorCode = NSURLErrorDataLengthExceedsMaximum;
break;
case -9823: /* decompression failure */
errorCode = NSURLErrorDownloadDecodingFailedMidStream;
break;
case -9824: /* handshake failure */
errorCode = NSURLErrorClientCertificateRejected;
break;
case -9825: /* misc. bad certificate */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9826: /* bad unsupported cert format */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9827: /* certificate revoked */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9828: /* certificate expired */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9829: /* unknown certificate */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9830: /* illegal parameter */
errorCode = NSURLErrorCannotDecodeRawData;
break;
case -9831: /* unknown Cert Authority */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9832: /* access denied */
errorCode = NSURLErrorClientCertificateRejected;
break;
case -9833: /* decoding error */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9834: /* decryption error */
errorCode = NSURLErrorCannotDecodeRawData;
break;
case -9835: /* export restriction */
errorCode = NSURLErrorCannotConnectToHost;
break;
case -9836: /* bad protocol version */
errorCode = NSURLErrorCannotConnectToHost;
break;
case -9837: /* insufficient security */
errorCode = NSURLErrorClientCertificateRejected;
break;
case -9838: /* internal error */
errorCode = NSURLErrorTimedOut;
break;
case -9839: /* user canceled */
errorCode = NSURLErrorCancelled;
break;
case -9840: /* no renegotiation allowed */
errorCode = NSURLErrorCannotConnectToHost;
break;
case -9841: /* peer cert is valid, or was ignored if verification disabled */
errorCode = NSURLErrorServerCertificateNotYetValid;
break;
case -9842: /* server has requested a client cert */
errorCode = NSURLErrorClientCertificateRejected;
break;
case -9843: /* peer host name mismatch */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9844: /* peer dropped connection before responding */
errorCode = NSURLErrorNetworkConnectionLost;
break;
case -9845: /* decryption failure */
errorCode = NSURLErrorCannotDecodeRawData;
break;
case -9846: /* bad MAC */
errorCode = NSURLErrorNotConnectedToInternet;
break;
case -9847: /* record overflow */
errorCode = NSURLErrorDataLengthExceedsMaximum;
break;
case -9848: /* configuration error */
errorCode = kQNUnexpectedSysCallError;
break;
case -9849: /* unexpected (skipped) record in DTLS */
errorCode = kQNUnexpectedSysCallError;
break;
case -9850: /* weak ephemeral dh key */
errorCode = kQNUnexpectedSysCallError;
break;
case -9851: /* SNI */
errorCode = NSURLErrorClientCertificateRejected;
break;
default:
break;
}
return [NSError errorWithDomain:NSURLErrorDomain code:errorCode userInfo:@{@"UserInfo" : errorInfo ?: @""}];
}
//MARK: -- delegate action
- (void)delegate_redirectedToRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse{
if ([self.delegate respondsToSelector:@selector(redirectedToRequest:redirectResponse:)]) {
[self.delegate redirectedToRequest:request redirectResponse:redirectResponse];
}
}
- (void)delegate_onError:(NSError *)error{
@synchronized (self) {
if (self.isCallFinishOrError) {
return;
}
self.isCallFinishOrError = YES;
}
if ([self.delegate respondsToSelector:@selector(onError:)]) {
[self.delegate onError:error];
}
}
- (void)delegate_didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
if ([self.delegate respondsToSelector:@selector(didSendBodyData:
totalBytesSent:
totalBytesExpectedToSend:)]) {
[self.delegate didSendBodyData:bytesSent
totalBytesSent:totalBytesSent
totalBytesExpectedToSend:totalBytesExpectedToSend];
}
}
- (void)delegate_onReceiveResponse:(NSURLResponse *)response httpVersion:(NSString *)httpVersion{
if ([self.delegate respondsToSelector:@selector(onReceiveResponse:httpVersion:)]) {
[self.delegate onReceiveResponse:response httpVersion:httpVersion];
}
}
- (void)delegate_didLoadData:(NSData *)data{
if ([self.delegate respondsToSelector:@selector(didLoadData:)]) {
[self.delegate didLoadData:data];
}
}
- (void)delegate_didFinish{
@synchronized (self) {
if (self.isCallFinishOrError) {
return;
}
self.isCallFinishOrError = YES;
}
if ([self.delegate respondsToSelector:@selector(didFinish)]) {
[self.delegate didFinish];
}
}
// MARK: error
- (NSError *)createError:(NSInteger)errorCode errorDescription:(NSString *)errorDescription {
if (errorDescription) {
return [NSError errorWithDomain:kQNCFHttpClientErrorDomain
code:errorCode
userInfo:@{@"userInfo":errorDescription}];
} else {
return [NSError errorWithDomain:kQNCFHttpClientErrorDomain
code:NSURLErrorSecureConnectionFailed
userInfo:nil];
}
}
@end

View File

@@ -0,0 +1,31 @@
//
// QNCFHttpThreadPool.h
// Qiniu
//
// Created by yangsen on 2021/10/13.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNCFHttpThread : NSThread
@property(nonatomic, assign, readonly)NSInteger operationCount;
@end
@interface QNCFHttpThreadPool : NSObject
@property(nonatomic, assign, readonly)NSInteger maxOperationPerThread;
+ (instancetype)shared;
- (QNCFHttpThread *)getOneThread;
- (void)addOperationCountOfThread:(QNCFHttpThread *)thread;
- (void)subtractOperationCountOfThread:(QNCFHttpThread *)thread;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,128 @@
//
// QNCFHttpThreadPool.m
// Qiniu
//
// Created by yangsen on 2021/10/13.
//
#import "QNCFHttpThreadPool.h"
#import "QNTransactionManager.h"
@interface QNCFHttpThread()
@property(nonatomic, assign)BOOL isCompleted;
@property(nonatomic, assign)NSInteger operationCount;
@property(nonatomic, strong)NSDate *deadline;
@end
@implementation QNCFHttpThread
+ (instancetype)thread {
return [[QNCFHttpThread alloc] init];;
}
- (instancetype)init {
if (self = [super init]) {
self.isCompleted = NO;
self.operationCount = 0;
}
return self;
}
- (void)main {
@autoreleasepool {
[super main];
while (!self.isCompleted) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
}
}
- (void)cancel {
self.isCompleted = YES;
}
@end
@interface QNCFHttpThreadPool()
//
@property(nonatomic, assign)NSInteger threadLiveTime;
@property(nonatomic, assign)NSInteger maxOperationPerThread;
@property(nonatomic, strong)NSMutableArray *pool;
@end
@implementation QNCFHttpThreadPool
+ (instancetype)shared {
static QNCFHttpThreadPool *pool = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pool = [[QNCFHttpThreadPool alloc] init];
pool.threadLiveTime = 60;
pool.maxOperationPerThread = 1;
pool.pool = [NSMutableArray array];
[pool addThreadLiveChecker];
});
return pool;
}
- (void)addThreadLiveChecker {
QNTransaction *transaction = [QNTransaction timeTransaction:@"CFHttpThreadPool" after:0 interval:1 action:^{
[[QNCFHttpThreadPool shared] checkThreadLive];
}];
[kQNTransactionManager addTransaction:transaction];
}
- (void)checkThreadLive {
@synchronized (self) {
NSArray *pool = [self.pool copy];
for (QNCFHttpThread *thread in pool) {
if (thread.operationCount < 1 && thread.deadline && [thread.deadline timeIntervalSinceNow] < 0) {
[self.pool removeObject:thread];
[thread cancel];
}
}
}
}
- (QNCFHttpThread *)getOneThread {
QNCFHttpThread *thread = nil;
@synchronized (self) {
for (QNCFHttpThread *t in self.pool) {
if (t.operationCount < self.maxOperationPerThread) {
thread = t;
break;
}
}
if (thread == nil) {
thread = [QNCFHttpThread thread];
thread.name = [NSString stringWithFormat:@"com.qiniu.cfclient.%lu", (unsigned long)self.pool.count];
[thread start];
[self.pool addObject:thread];
}
thread.operationCount += 1;
thread.deadline = nil;
}
return thread;
}
- (void)addOperationCountOfThread:(QNCFHttpThread *)thread {
if (thread == nil) {
return;
}
@synchronized (self) {
thread.operationCount += 1;
thread.deadline = nil;
}
}
- (void)subtractOperationCountOfThread:(QNCFHttpThread *)thread {
if (thread == nil) {
return;
}
@synchronized (self) {
thread.operationCount -= 1;
if (thread.operationCount < 1) {
thread.deadline = [NSDate dateWithTimeIntervalSinceNow:self.threadLiveTime];
}
}
}
@end

View File

@@ -0,0 +1,17 @@
//
// QNUploadSystemClient.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/6.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNRequestClient.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadSystemClient : NSObject <QNRequestClient>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,202 @@
//
// QNUploadSystemClient.m
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/6.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUploadSystemClient.h"
#import "QNUserAgent.h"
#import "NSURLRequest+QNRequest.h"
@interface QNUploadSystemClient()<NSURLSessionDelegate>
@property(nonatomic, strong)NSURLRequest *request;
@property(nonatomic, strong)QNUploadSingleRequestMetrics *requestMetrics;
@property(nonatomic, strong)NSURLSessionDataTask *uploadTask;
@property(nonatomic, strong)NSMutableData *responseData;
@property(nonatomic, copy)void(^progress)(long long totalBytesWritten, long long totalBytesExpectedToWrite);
@property(nonatomic, copy)QNRequestClientCompleteHandler complete;
@end
@implementation QNUploadSystemClient
- (NSString *)clientId {
return @"NSURLSession";
}
- (void)request:(NSURLRequest *)request
server:(id <QNUploadServer>)server
connectionProxy:(NSDictionary *)connectionProxy
progress:(void (^)(long long, long long))progress
complete:(QNRequestClientCompleteHandler)complete {
// https 使 IP
if (!request.qn_isHttps && server && server.ip.length > 0 && server.host.length > 0) {
NSString *urlString = request.URL.absoluteString;
urlString = [urlString stringByReplacingOccurrencesOfString:server.host withString:server.ip];
NSMutableURLRequest *requestNew = [request mutableCopy];
requestNew.URL = [NSURL URLWithString:urlString];
requestNew.qn_domain = server.host;
self.request = [requestNew copy];
} else {
self.request = request;
}
self.requestMetrics = [QNUploadSingleRequestMetrics emptyMetrics];
self.requestMetrics.remoteAddress = self.request.qn_isHttps ? nil : server.ip;
self.requestMetrics.remotePort = self.request.qn_isHttps ? @443 : @80;
[self.requestMetrics start];
self.responseData = [NSMutableData data];
self.progress = progress;
self.complete = complete;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
if (connectionProxy) {
configuration.connectionProxyDictionary = connectionProxy;
}
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:self.request];
[uploadTask resume];
self.uploadTask = uploadTask;
}
- (void)cancel{
[self.uploadTask cancel];
}
//MARK:-- NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
[self.requestMetrics end];
self.requestMetrics.request = task.currentRequest;
self.requestMetrics.response = task.response;
self.requestMetrics.error = error;
self.complete(task.response, self.requestMetrics,self.responseData, error);
[session finishTasksAndInvalidate];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(ios(10.0)) {
NSURLSessionTaskTransactionMetrics *transactionMetrics = metrics.transactionMetrics.lastObject;
self.requestMetrics.domainLookupStartDate = transactionMetrics.domainLookupStartDate;
self.requestMetrics.domainLookupEndDate = transactionMetrics.domainLookupEndDate;
self.requestMetrics.connectStartDate = transactionMetrics.connectStartDate;
self.requestMetrics.secureConnectionStartDate = transactionMetrics.secureConnectionStartDate;
self.requestMetrics.secureConnectionEndDate = transactionMetrics.secureConnectionEndDate;
self.requestMetrics.connectEndDate = transactionMetrics.connectEndDate;
self.requestMetrics.requestStartDate = transactionMetrics.requestStartDate;
self.requestMetrics.requestEndDate = transactionMetrics.requestEndDate;
self.requestMetrics.responseStartDate = transactionMetrics.responseStartDate;
self.requestMetrics.responseEndDate = transactionMetrics.responseEndDate;
if ([transactionMetrics.networkProtocolName isEqualToString:@"http/1.0"]) {
self.requestMetrics.httpVersion = @"1.0";
} else if ([transactionMetrics.networkProtocolName isEqualToString:@"http/1.1"]) {
self.requestMetrics.httpVersion = @"1.1";
} else if ([transactionMetrics.networkProtocolName isEqualToString:@"h2"]) {
self.requestMetrics.httpVersion = @"2";
} else if ([transactionMetrics.networkProtocolName isEqualToString:@"h3"]) {
self.requestMetrics.httpVersion = @"3";
} else {
self.requestMetrics.httpVersion = transactionMetrics.networkProtocolName;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, macOS 10.15, *)) {
if (transactionMetrics.remoteAddress) {
self.requestMetrics.remoteAddress = transactionMetrics.remoteAddress;
self.requestMetrics.remotePort = transactionMetrics.remotePort;
}
if (transactionMetrics.countOfRequestHeaderBytesSent > 0) {
self.requestMetrics.countOfRequestHeaderBytesSent = transactionMetrics.countOfRequestHeaderBytesSent;
}
if (transactionMetrics.countOfResponseHeaderBytesReceived > 0) {
self.requestMetrics.countOfResponseHeaderBytesReceived = transactionMetrics.countOfResponseHeaderBytesReceived;
}
if (transactionMetrics.countOfResponseBodyBytesReceived > 0) {
self.requestMetrics.countOfResponseBodyBytesReceived = transactionMetrics.countOfResponseBodyBytesReceived;
}
}
#endif
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
self.requestMetrics.countOfRequestBodyBytesSent = totalBytesSent;
if (self.progress) {
self.progress(totalBytesSent, totalBytesExpectedToSend);
}
}
/*
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain {
NSMutableArray *policies = [NSMutableArray array];
if (domain) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (@available(iOS 13.0, macOS 10.14, *)) {
CFErrorRef error = NULL;
BOOL ret = SecTrustEvaluateWithError(serverTrust, &error);
return ret && (error == nil);
} else {
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
if (!challenge) {
return;
}
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
if (!host) {
host = self.request.URL.host;
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
completionHandler(disposition,credential);
}
*/
@end

View File

@@ -0,0 +1,56 @@
//
// QNHttpRequest.h
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNHttpSingleRequest.h"
#import "QNUploadRegionInfo.h"
NS_ASSUME_NONNULL_BEGIN
@class QNUploadRequestState, QNResponseInfo, QNConfiguration, QNUploadOption, QNUpToken, QNUploadRegionRequestMetrics;
typedef void(^QNRegionRequestCompleteHandler)(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response);
@interface QNHttpRegionRequest : NSObject
@property(nonatomic, strong, readonly)QNConfiguration *config;
@property(nonatomic, strong, readonly)QNUploadOption *uploadOption;
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
token:(QNUpToken *)token
region:(id <QNUploadRegion>)region
requestInfo:(QNUploadRequestInfo *)requestInfo
requestState:(QNUploadRequestState *)requestState;
- (void)get:(NSString * _Nullable)action
headers:(NSDictionary * _Nullable)headers
shouldRetry:(BOOL(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))shouldRetry
complete:(QNRegionRequestCompleteHandler)complete;
- (void)post:(NSString * _Nullable)action
headers:(NSDictionary * _Nullable)headers
body:(NSData * _Nullable)body
shouldRetry:(BOOL(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))shouldRetry
progress:(void(^_Nullable)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRegionRequestCompleteHandler)complete;
- (void)put:(NSString *)action
headers:(NSDictionary * _Nullable)headers
body:(NSData * _Nullable)body
shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRegionRequestCompleteHandler)complete;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,216 @@
//
// QNHttpRequest.m
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNLogUtil.h"
#import "QNAsyncRun.h"
#import "QNDnsPrefetch.h"
#import "QNUploadRequestState.h"
#import "QNHttpRegionRequest.h"
#import "QNConfiguration.h"
#import "QNUploadOption.h"
#import "QNUrlUtils.h"
#import "NSURLRequest+QNRequest.h"
#import "QNUploadRequestMetrics.h"
#import "QNResponseInfo.h"
@interface QNHttpRegionRequest()
@property(nonatomic, strong)QNConfiguration *config;
@property(nonatomic, strong)QNUploadOption *uploadOption;
@property(nonatomic, strong)QNUploadRequestInfo *requestInfo;
@property(nonatomic, strong)QNUploadRequestState *requestState;
@property(nonatomic, strong)QNUploadRegionRequestMetrics *requestMetrics;
@property(nonatomic, strong)QNHttpSingleRequest *singleRequest;
@property(nonatomic, strong)id <QNUploadServer> currentServer;
@property(nonatomic, strong)id <QNUploadRegion> region;
@end
@implementation QNHttpRegionRequest
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
token:(QNUpToken *)token
region:(id <QNUploadRegion>)region
requestInfo:(QNUploadRequestInfo *)requestInfo
requestState:(QNUploadRequestState *)requestState {
if (self = [super init]) {
_config = config;
_uploadOption = uploadOption;
_region = region;
_requestInfo = requestInfo;
_requestState = requestState;
_singleRequest = [[QNHttpSingleRequest alloc] initWithConfig:config
uploadOption:uploadOption
token:token
requestInfo:requestInfo
requestState:requestState];
}
return self;
}
- (void)get:(NSString *)action
headers:(NSDictionary *)headers
shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
complete:(QNRegionRequestCompleteHandler)complete{
self.requestMetrics = [[QNUploadRegionRequestMetrics alloc] initWithRegion:self.region];
[self.requestMetrics start];
[self performRequest:[self getNextServer:nil]
action:action
headers:headers
method:@"GET"
body:nil
shouldRetry:shouldRetry
progress:nil
complete:complete];
}
- (void)post:(NSString *)action
headers:(NSDictionary *)headers
body:(NSData *)body
shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRegionRequestCompleteHandler)complete{
self.requestMetrics = [[QNUploadRegionRequestMetrics alloc] initWithRegion:self.region];
[self.requestMetrics start];
[self performRequest:[self getNextServer:nil]
action:action
headers:headers
method:@"POST"
body:body
shouldRetry:shouldRetry
progress:progress
complete:complete];
}
- (void)put:(NSString *)action
headers:(NSDictionary *)headers
body:(NSData *)body
shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRegionRequestCompleteHandler)complete{
self.requestMetrics = [[QNUploadRegionRequestMetrics alloc] initWithRegion:self.region];
[self.requestMetrics start];
[self performRequest:[self getNextServer:nil]
action:action
headers:headers
method:@"PUT"
body:body
shouldRetry:shouldRetry
progress:progress
complete:complete];
}
- (void)performRequest:(id <QNUploadServer>)server
action:(NSString *)action
headers:(NSDictionary *)headers
method:(NSString *)method
body:(NSData *)body
shouldRetry:(BOOL(^)(QNResponseInfo *responseInfo, NSDictionary *response))shouldRetry
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRegionRequestCompleteHandler)complete{
if (!server.host || server.host.length == 0) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"server error"];
[self complete:responseInfo response:nil complete:complete];
return;
}
NSString *serverHost = server.host;
NSString *serverIP = server.ip;
if (self.config.converter) {
serverHost = self.config.converter(serverHost);
serverIP = nil;
}
self.currentServer = server;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *urlString = [NSString stringWithFormat:@"%@%@", [QNUrlUtils setHostScheme:serverHost useHttps:self.config.useHttps], action ?: @""];
request.URL = [NSURL URLWithString:urlString];
request.HTTPMethod = method;
[request setAllHTTPHeaderFields:headers];
[request setTimeoutInterval:self.config.timeoutInterval];
request.HTTPBody = body;
QNLogInfo(@"key:%@ url:%@", self.requestInfo.key, request.URL);
QNLogInfo(@"key:%@ headers:%@", self.requestInfo.key, headers);
kQNWeakSelf;
[self.singleRequest request:request
server:server
shouldRetry:shouldRetry
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, NSArray<QNUploadSingleRequestMetrics *> * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
[self.requestMetrics addMetricsList:metrics];
BOOL hijacked = metrics.lastObject.isMaybeHijacked || metrics.lastObject.isForsureHijacked;
BOOL isSafeDnsSource = kQNIsDnsSourceCustom(metrics.lastObject.syncDnsSource) || kQNIsDnsSourceDoh(metrics.lastObject.syncDnsSource) || kQNIsDnsSourceDnsPod(metrics.lastObject.syncDnsSource);
BOOL hijackedAndNeedRetry = hijacked && isSafeDnsSource;
if (hijackedAndNeedRetry) {
[self.region updateIpListFormHost:server.host];
}
if ((shouldRetry(responseInfo, response)
&& self.config.allowBackupHost
&& responseInfo.couldRegionRetry) || hijackedAndNeedRetry) {
id <QNUploadServer> newServer = [self getNextServer:responseInfo];
if (newServer) {
QNAsyncRunAfter(self.config.retryInterval, kQNBackgroundQueue, ^{
[self performRequest:newServer
action:action
headers:headers
method:method
body:body
shouldRetry:shouldRetry
progress:progress
complete:complete];
});
} else if (complete) {
[self complete:responseInfo response:response complete:complete];
}
} else if (complete) {
[self complete:responseInfo response:response complete:complete];
}
}];
}
- (void)complete:(QNResponseInfo *)responseInfo
response:(NSDictionary *)response
complete:(QNRegionRequestCompleteHandler)completionHandler {
[self.requestMetrics end];
if (completionHandler) {
completionHandler(responseInfo, self.requestMetrics, response);
}
self.singleRequest = nil;
}
//MARK: --
- (id <QNUploadServer>)getNextServer:(QNResponseInfo *)responseInfo{
if (responseInfo.isTlsError) {
self.requestState.isUseOldServer = YES;
}
return [self.region getNextServer:[self.requestState copy] responseInfo:responseInfo freezeServer:self.currentServer];
}
@end

View File

@@ -0,0 +1,42 @@
//
// QNHttpRequest+SingleRequestRetry.h
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNUploadRequestInfo.h"
#import "QNIUploadServer.h"
NS_ASSUME_NONNULL_BEGIN
@class QNUploadRequestState, QNResponseInfo, QNConfiguration, QNUploadOption, QNUpToken, QNUploadSingleRequestMetrics;
typedef void(^QNSingleRequestCompleteHandler)(QNResponseInfo * _Nullable responseInfo, NSArray <QNUploadSingleRequestMetrics *> * _Nullable metrics, NSDictionary * _Nullable response);
@interface QNHttpSingleRequest : NSObject
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
token:(QNUpToken *)token
requestInfo:(QNUploadRequestInfo *)requestInfo
requestState:(QNUploadRequestState *)requestState;
/// 网络请求
/// @param request 请求内容
/// @param server server信息目前仅用于日志统计
/// @param shouldRetry 判断是否需要重试的block
/// @param progress 上传进度回调
/// @param complete 上传完成回调
- (void)request:(NSURLRequest *)request
server:(id <QNUploadServer>)server
shouldRetry:(BOOL(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))shouldRetry
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNSingleRequestCompleteHandler)complete;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,323 @@
//
// 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

View File

@@ -0,0 +1,28 @@
//
// QNIUploadServer.h
// QiniuSDK
//
// Created by yangsen on 2020/7/3.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import<Foundation/Foundation.h>
@protocol QNUploadServer <NSObject>
@property(nonatomic, copy, nullable, readonly)NSString *httpVersion;
@property(nonatomic, copy, nullable, readonly)NSString *serverId;
@property(nonatomic, copy, nullable, readonly)NSString *ip;
@property(nonatomic, copy, nullable, readonly)NSString *host;
@property(nonatomic, copy, nullable, readonly)NSString *source;
@property(nonatomic,strong, nullable, readonly)NSNumber *ipPrefetchedTime;
@end
#define kQNHttpVersion1 @"http_version_1"
#define kQNHttpVersion2 @"http_version_2"
#define kQNHttpVersion3 @"http_version_3"
BOOL kQNIsHttp3(NSString * _Nullable httpVersion);
BOOL kQNIsHttp2(NSString * _Nullable httpVersion);

View File

@@ -0,0 +1,17 @@
//
// QNIUploadServer.m
// QiniuSDK
//
// Created by yangsen on 2021/2/4.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNIUploadServer.h"
BOOL kQNIsHttp3(NSString * _Nullable httpVersion) {
return [httpVersion isEqualToString:kQNHttpVersion3];
}
BOOL kQNIsHttp2(NSString * _Nullable httpVersion) {
return [httpVersion isEqualToString:kQNHttpVersion2];
}

View File

@@ -0,0 +1,30 @@
//
// QNRequestClient.h
// QiniuSDK
//
// Created by yangsen on 2020/4/29.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUploadRequestMetrics.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^QNRequestClientCompleteHandler)(NSURLResponse * _Nullable, QNUploadSingleRequestMetrics * _Nullable, NSData * _Nullable, NSError * _Nullable);
@protocol QNRequestClient <NSObject>
// client 标识
@property(nonatomic, copy, readonly)NSString *clientId;
- (void)request:(NSURLRequest *)request
server:(_Nullable id <QNUploadServer>)server
connectionProxy:(NSDictionary * _Nullable)connectionProxy
progress:(void(^ _Nullable)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(_Nullable QNRequestClientCompleteHandler)complete;
- (void)cancel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,102 @@
//
// QNRequestTransaction.h
// QiniuSDK
//
// Created by yangsen on 2020/4/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNUploadRegionInfo.h"
NS_ASSUME_NONNULL_BEGIN
@class QNUpToken, QNConfiguration, QNUploadOption, QNResponseInfo, QNUploadRegionRequestMetrics;
typedef void(^QNRequestTransactionCompleteHandler)(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response);
// 单个对象只能执行一个事务,多个事务需要创建多个事务对象完成
@interface QNRequestTransaction : NSObject
//MARK:-- 构造方法
- (instancetype)initWithHosts:(NSArray <NSString *> *)hosts
regionId:(NSString * _Nullable)regionId
token:(QNUpToken *)token;
//MARK:-- upload事务构造方法 选择
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
targetRegion:(id <QNUploadRegion>)targetRegion
currentRegion:(id <QNUploadRegion>)currentRegion
key:(NSString * _Nullable)key
token:(QNUpToken *)token;
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
hosts:(NSArray <NSString *> *)hosts
regionId:(NSString * _Nullable)regionId
key:(NSString * _Nullable)key
token:(QNUpToken *)token;
- (void)queryUploadHosts:(QNRequestTransactionCompleteHandler)complete;
- (void)uploadFormData:(NSData *)data
fileName:(NSString *)fileName
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete;
- (void)makeBlock:(long long)blockOffset
blockSize:(long long)blockSize
firstChunkData:(NSData *)firstChunkData
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete;
- (void)uploadChunk:(NSString *)blockContext
blockOffset:(long long)blockOffset
chunkData:(NSData *)chunkData
chunkOffset:(long long)chunkOffset
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete;
- (void)makeFile:(long long)fileSize
fileName:(NSString *)fileName
blockContexts:(NSArray <NSString *> *)blockContexts
complete:(QNRequestTransactionCompleteHandler)complete;
- (void)initPart:(QNRequestTransactionCompleteHandler)complete;
- (void)uploadPart:(NSString *)uploadId
partIndex:(NSInteger)partIndex
partData:(NSData *)partData
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete;
/**
* partInfoArray
* |_ NSDictionary : { "etag": "<Etag>", "partNumber": <PartNumber> }
*/
- (void)completeParts:(NSString *)fileName
uploadId:(NSString *)uploadId
partInfoArray:(NSArray <NSDictionary *> *)partInfoArray
complete:(QNRequestTransactionCompleteHandler)complete;
/**
* 上传日志
*/
- (void)reportLog:(NSData *)logData
logClientId:(NSString *)logClientId
complete:(QNRequestTransactionCompleteHandler)complete;
/**
* 获取服务端配置
*/
- (void)serverConfig:(QNRequestTransactionCompleteHandler)complete;
/**
* 获取服务端针对某个用户的配置
*/
- (void)serverUserConfig:(QNRequestTransactionCompleteHandler)complete;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,545 @@
//
// QNRequestTransaction.m
// QiniuSDK
//
// Created by yangsen on 2020/4/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNRequestTransaction.h"
#import "QNDefine.h"
#import "QNUtils.h"
#import "QNCrc32.h"
#import "NSData+QNMD5.h"
#import "QNUrlSafeBase64.h"
#import "QNUpToken.h"
#import "QNConfiguration.h"
#import "QNUploadOption.h"
#import "QNZoneInfo.h"
#import "QNUserAgent.h"
#import "QNResponseInfo.h"
#import "QNUploadRequestState.h"
#import "QNUploadRequestMetrics.h"
#import "QNUploadDomainRegion.h"
#import "QNHttpRegionRequest.h"
@interface QNRequestTransaction()
@property(nonatomic, strong)QNConfiguration *config;
@property(nonatomic, strong)QNUploadOption *uploadOption;
@property(nonatomic, copy)NSString *key;
@property(nonatomic, strong)QNUpToken *token;
@property(nonatomic, strong)QNUploadRequestInfo *requestInfo;
@property(nonatomic, strong)QNUploadRequestState *requestState;
@property(nonatomic, strong)QNHttpRegionRequest *regionRequest;
@end
@implementation QNRequestTransaction
- (instancetype)initWithHosts:(NSArray <NSString *> *)hosts
regionId:(NSString * _Nullable)regionId
token:(QNUpToken *)token{
return [self initWithConfig:[QNConfiguration defaultConfiguration]
uploadOption:[QNUploadOption defaultOptions]
hosts:hosts
regionId:regionId
key:nil
token:token];
}
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
hosts:(NSArray <NSString *> *)hosts
regionId:(NSString * _Nullable)regionId
key:(NSString * _Nullable)key
token:(nonnull QNUpToken *)token{
QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] init];
[region setupRegionData:[QNZoneInfo zoneInfoWithMainHosts:hosts regionId:regionId]];
return [self initWithConfig:config
uploadOption:uploadOption
targetRegion:region
currentRegion:region
key:key
token:token];
}
- (instancetype)initWithConfig:(QNConfiguration *)config
uploadOption:(QNUploadOption *)uploadOption
targetRegion:(id <QNUploadRegion>)targetRegion
currentRegion:(id <QNUploadRegion>)currentRegion
key:(NSString *)key
token:(QNUpToken *)token{
if (self = [super init]) {
_config = config;
_uploadOption = uploadOption;
_requestState = [[QNUploadRequestState alloc] init];
_key = key;
_token = token;
_requestInfo = [[QNUploadRequestInfo alloc] init];
_requestInfo.targetRegionId = targetRegion.zoneInfo.regionId;
_requestInfo.currentRegionId = currentRegion.zoneInfo.regionId;
_requestInfo.bucket = token.bucket;
_requestInfo.key = key;
_regionRequest = [[QNHttpRegionRequest alloc] initWithConfig:config
uploadOption:uploadOption
token:token
region:currentRegion
requestInfo:_requestInfo
requestState:_requestState];
}
return self;
}
//MARK: -- uc query
- (void)queryUploadHosts:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeUCQuery;
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)!responseInfo.isOK;
};
NSDictionary *header = @{@"User-Agent" : [kQNUserAgent getUserAgent:self.token.token]};
NSString *action = [NSString stringWithFormat:@"/v4/query?ak=%@&bucket=%@&sdk_name=%@&sdk_version=%@", self.token.access, self.token.bucket, [QNUtils sdkLanguage], [QNUtils sdkVersion]];
[self.regionRequest get:action
headers:header
shouldRetry:shouldRetry
complete:complete];
}
//MARK: -- upload form
- (void)uploadFormData:(NSData *)data
fileName:(NSString *)fileName
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeForm;
NSMutableDictionary *param = [NSMutableDictionary dictionary];
if (self.uploadOption.params) {
[param addEntriesFromDictionary:self.uploadOption.params];
}
if (self.uploadOption.metaDataParam) {
[param addEntriesFromDictionary:self.uploadOption.metaDataParam];
}
if (self.key && self.key.length > 0) {
param[@"key"] = self.key;
}
param[@"token"] = self.token.token ?: @"";
if (self.uploadOption.checkCrc) {
param[@"crc32"] = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:data]];
}
NSString *boundary = @"werghnvt54wef654rjuhgb56trtg34tweuyrgf";
NSString *disposition = @"Content-Disposition: form-data";
NSMutableData *body = [NSMutableData data];
@try {
for (NSString *paramsKey in param) {
NSString *pair = [NSString stringWithFormat:@"--%@\r\n%@; name=\"%@\"\r\n\r\n", boundary, disposition, paramsKey];
[body appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
id value = [param objectForKey:paramsKey];
if ([value isKindOfClass:[NSString class]]) {
[body appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
} else if ([value isKindOfClass:[NSData class]]) {
[body appendData:value];
}
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
fileName = [QNUtils formEscape:fileName];
NSString *filePair = [NSString stringWithFormat:@"--%@\r\n%@; name=\"%@\"; filename=\"%@\"\nContent-Type:%@\r\n\r\n", boundary, disposition, @"file", fileName, self.uploadOption.mimeType];
[body appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:data];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
} @catch (NSException *exception) {
if (complete) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithLocalIOError:[NSString stringWithFormat:@"%@", exception]];
QNUploadRegionRequestMetrics *metrics = [QNUploadRegionRequestMetrics emptyMetrics];
complete(info, metrics, nil);
}
return;
}
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Content-Type"] = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
header[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)body.length];
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)!responseInfo.isOK;
};
[self.regionRequest post:nil
headers:header
body:body
shouldRetry:shouldRetry
progress:progress
complete:complete];
}
//MARK: --
- (void)makeBlock:(long long)blockOffset
blockSize:(long long)blockSize
firstChunkData:(NSData *)firstChunkData
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeMkblk;
self.requestInfo.fileOffset = @(blockOffset);
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/octet-stream";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *action = [NSString stringWithFormat:@"/mkblk/%u", (unsigned int)blockSize];
NSString *chunkCrc = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:firstChunkData]];
kQNWeakSelf;
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
kQNStrongSelf;
NSString *ctx = response[@"ctx"];
NSString *crcServer = [NSString stringWithFormat:@"%@", response[@"crc32"]];
return (BOOL)(responseInfo.isOK == false || (responseInfo.isOK && (!ctx || (self.uploadOption.checkCrc && ![chunkCrc isEqualToString:crcServer]))));
};
[self.regionRequest post:action
headers:header
body:firstChunkData
shouldRetry:shouldRetry
progress:progress
complete:complete];
}
- (void)uploadChunk:(NSString *)blockContext
blockOffset:(long long)blockOffset
chunkData:(NSData *)chunkData
chunkOffset:(long long)chunkOffset
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeBput;
self.requestInfo.fileOffset = @(blockOffset + chunkOffset);
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/octet-stream";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *action = [NSString stringWithFormat:@"/bput/%@/%lld", blockContext, chunkOffset];
NSString *chunkCrc = [NSString stringWithFormat:@"%u", (unsigned int)[QNCrc32 data:chunkData]];
kQNWeakSelf;
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
kQNStrongSelf;
NSString *ctx = response[@"ctx"];
NSString *crcServer = [NSString stringWithFormat:@"%@", response[@"crc32"]];
return (BOOL)(responseInfo.isOK == false || (responseInfo.isOK && (!ctx || (self.uploadOption.checkCrc && ![chunkCrc isEqualToString:crcServer]))));
};
[self.regionRequest post:action
headers:header
body:chunkData
shouldRetry:shouldRetry
progress:progress
complete:complete];
}
- (void)makeFile:(long long)fileSize
fileName:(NSString *)fileName
blockContexts:(NSArray <NSString *> *)blockContexts
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeMkfile;
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/octet-stream";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *mimeType = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.uploadOption.mimeType]];
__block NSString *action = [[NSString alloc] initWithFormat:@"/mkfile/%lld%@", fileSize, mimeType];
if (self.key != nil) {
NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
action = [NSString stringWithFormat:@"%@%@", action, keyStr];
}
[self.uploadOption.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
action = [NSString stringWithFormat:@"%@/%@/%@", action, key, [QNUrlSafeBase64 encodeString:obj]];
}];
[self.uploadOption.metaDataParam enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
action = [NSString stringWithFormat:@"%@/%@/%@", action, key, [QNUrlSafeBase64 encodeString:obj]];
}];
//
NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:fileName]];
action = [NSString stringWithFormat:@"%@%@", action, fname];
NSMutableData *body = [NSMutableData data];
NSString *bodyString = [blockContexts componentsJoinedByString:@","];
[body appendData:[bodyString dataUsingEncoding:NSUTF8StringEncoding]];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:body
shouldRetry:shouldRetry
progress:nil
complete:complete];
}
- (void)initPart:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeInitParts;
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/octet-stream";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];;
NSString *action = [[NSString alloc] initWithFormat:@"%@%@/uploads", buckets, objects];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:nil
shouldRetry:shouldRetry
progress:nil
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (void)uploadPart:(NSString *)uploadId
partIndex:(NSInteger)partIndex
partData:(NSData *)partData
progress:(void(^)(long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeUploadPart;
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/octet-stream";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
if (self.uploadOption.checkCrc) {
NSString *md5 = [[partData qn_md5] lowercaseString];
if (md5) {
header[@"Content-MD5"] = md5;
}
}
NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];;
NSString *uploads = [[NSString alloc] initWithFormat:@"/uploads/%@", uploadId];
NSString *partNumber = [[NSString alloc] initWithFormat:@"/%ld", (long)partIndex];
NSString *action = [[NSString alloc] initWithFormat:@"%@%@%@%@", buckets, objects, uploads, partNumber];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
NSString *etag = [NSString stringWithFormat:@"%@", response[@"etag"]];
NSString *serverMD5 = [NSString stringWithFormat:@"%@", response[@"md5"]];
return (BOOL)(!responseInfo.isOK || !etag || !serverMD5);
};
[self.regionRequest put:action
headers:header
body:partData
shouldRetry:shouldRetry
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (void)completeParts:(NSString *)fileName
uploadId:(NSString *)uploadId
partInfoArray:(NSArray <NSDictionary *> *)partInfoArray
complete:(QNRequestTransactionCompleteHandler)complete{
self.requestInfo.requestType = QNUploadRequestTypeCompletePart;
if (!partInfoArray || partInfoArray.count == 0) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithInvalidArgument:@"partInfoArray"];
if (complete) {
complete(responseInfo, nil, responseInfo.responseDictionary);
}
return;
}
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/json";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *buckets = [[NSString alloc] initWithFormat:@"/buckets/%@", self.token.bucket];
NSString *objects = [[NSString alloc] initWithFormat:@"/objects/%@", [self resumeV2EncodeKey:self.key]];
NSString *uploads = [[NSString alloc] initWithFormat:@"/uploads/%@", uploadId];
NSString *action = [[NSString alloc] initWithFormat:@"%@%@%@", buckets, objects, uploads];
NSMutableDictionary *bodyDictionary = [NSMutableDictionary dictionary];
if (partInfoArray) {
bodyDictionary[@"parts"] = partInfoArray;
}
if (fileName) {
bodyDictionary[@"fname"] = fileName;
}
if (self.uploadOption.mimeType) {
bodyDictionary[@"mimeType"] = self.uploadOption.mimeType;
}
if (self.uploadOption.params) {
bodyDictionary[@"customVars"] = self.uploadOption.params;
}
if (self.uploadOption.metaDataParam) {
bodyDictionary[@"metaData"] = self.uploadOption.metaDataParam;
}
NSError *error = nil;
NSData *body = [NSJSONSerialization dataWithJSONObject:bodyDictionary
options:NSJSONWritingPrettyPrinted
error:&error];
if (error) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:error.description];
if (complete) {
complete(responseInfo, nil, responseInfo.responseDictionary);
}
return;
}
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:body
shouldRetry:shouldRetry
progress:nil
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (void)reportLog:(NSData *)logData
logClientId:(NSString *)logClientId
complete:(QNRequestTransactionCompleteHandler)complete {
self.requestInfo.requestType = QNUploadRequestTypeUpLog;
NSString *token = [NSString stringWithFormat:@"UpToken %@", self.token.token];
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"Authorization"] = token;
header[@"Content-Type"] = @"application/json";
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *action = @"/log/4?compressed=gzip";
if (logClientId) {
header[@"X-Log-Client-Id"] = logClientId;
}
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:logData
shouldRetry:shouldRetry
progress:nil
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (void)serverConfig:(QNRequestTransactionCompleteHandler)complete {
self.requestInfo.requestType = QNUploadRequestTypeServerConfig;
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *action = [NSString stringWithFormat:@"/v1/sdk/config?sdk_name=%@&sdk_version=%@", [QNUtils sdkLanguage], [QNUtils sdkVersion]];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:nil
shouldRetry:shouldRetry
progress:nil
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (void)serverUserConfig:(QNRequestTransactionCompleteHandler)complete {
self.requestInfo.requestType = QNUploadRequestTypeServerUserConfig;
NSMutableDictionary *header = [NSMutableDictionary dictionary];
header[@"User-Agent"] = [kQNUserAgent getUserAgent:self.token.token];
NSString *action = [NSString stringWithFormat:@"/v1/sdk/config/user?ak=%@&sdk_name=%@&sdk_version=%@", self.token.access, [QNUtils sdkLanguage], [QNUtils sdkVersion]];
BOOL (^shouldRetry)(QNResponseInfo *, NSDictionary *) = ^(QNResponseInfo * responseInfo, NSDictionary * response){
return (BOOL)(!responseInfo.isOK);
};
[self.regionRequest post:action
headers:header
body:nil
shouldRetry:shouldRetry
progress:nil
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
complete(responseInfo, metrics, response);
}];
}
- (NSString *)resumeV2EncodeKey:(NSString *)key{
NSString *encodeKey = nil;
if (!self.key) {
encodeKey = @"~";
} else if (self.key.length == 0) {
encodeKey = @"";
} else {
encodeKey = [QNUrlSafeBase64 encodeString:self.key];
}
return encodeKey;
}
@end

View File

@@ -0,0 +1,30 @@
//
// QNUploadRegion.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/4/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNIUploadServer.h"
NS_ASSUME_NONNULL_BEGIN
@class QNZoneInfo, QNUploadRequestState, QNResponseInfo;
@protocol QNUploadRegion <NSObject>
@property(nonatomic, assign, readonly)BOOL isValid;
@property(nonatomic, strong, nullable, readonly)QNZoneInfo *zoneInfo;
- (void)setupRegionData:(QNZoneInfo * _Nullable)zoneInfo;
- (id<QNUploadServer> _Nullable)getNextServer:(QNUploadRequestState *)requestState
responseInfo:(QNResponseInfo *)responseInfo
freezeServer:(id <QNUploadServer> _Nullable)freezeServer;
- (void)updateIpListFormHost:(NSString *)host;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,45 @@
//
// QNUploadRequestInfo.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/13.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadRequestInfo : NSObject
/// 当前请求的类型
@property(nonatomic, copy, nullable)NSString *requestType;
/// 上传的bucket
@property(nonatomic, copy, nullable)NSString *bucket;
/// 上传的key
@property(nonatomic, copy, nullable)NSString *key;
/// 上传数据的偏移量
@property(nonatomic, strong, nullable)NSNumber *fileOffset;
/// 上传的目标region
@property(nonatomic, copy, nullable)NSString *targetRegionId;
/// 当前上传的region
@property(nonatomic, copy, nullable)NSString *currentRegionId;
- (BOOL)shouldReportRequestLog;
@end
extern NSString *const QNUploadRequestTypeUCQuery;
extern NSString *const QNUploadRequestTypeForm;
extern NSString *const QNUploadRequestTypeMkblk;
extern NSString *const QNUploadRequestTypeBput;
extern NSString *const QNUploadRequestTypeMkfile;
extern NSString *const QNUploadRequestTypeInitParts;
extern NSString *const QNUploadRequestTypeUploadPart;
extern NSString *const QNUploadRequestTypeCompletePart;
extern NSString *const QNUploadRequestTypeServerConfig;
extern NSString *const QNUploadRequestTypeServerUserConfig;
extern NSString *const QNUploadRequestTypeUpLog;
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,29 @@
//
// QNUploadRequestInfo.m
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/13.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNUploadRequestInfo.h"
@implementation QNUploadRequestInfo
- (BOOL)shouldReportRequestLog{
return ![self.requestType isEqualToString:QNUploadRequestTypeUpLog];
}
@end
NSString * const QNUploadRequestTypeUCQuery = @"uc_query";
NSString * const QNUploadRequestTypeForm = @"form";
NSString * const QNUploadRequestTypeMkblk = @"mkblk";
NSString * const QNUploadRequestTypeBput = @"bput";
NSString * const QNUploadRequestTypeMkfile = @"mkfile";
NSString * const QNUploadRequestTypeInitParts = @"init_parts";
NSString * const QNUploadRequestTypeUploadPart = @"upload_part";
NSString * const QNUploadRequestTypeCompletePart = @"complete_part";
NSString * const QNUploadRequestTypeServerConfig = @"server_config";
NSString * const QNUploadRequestTypeServerUserConfig = @"server_user_config";
NSString * const QNUploadRequestTypeUpLog = @"uplog";

View File

@@ -0,0 +1,21 @@
//
// QNUploadServerDomainResolver.h
// AppTest
//
// Created by yangsen on 2020/4/23.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNUploadRegionInfo.h"
NS_ASSUME_NONNULL_BEGIN
@class QNConfiguration;
@interface QNUploadDomainRegion : NSObject <QNUploadRegion>
- (instancetype)initWithConfig:(QNConfiguration *)config;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,444 @@
//
// QNUploadServerDomainResolver.m
// AppTest
//
// Created by yangsen on 2020/4/23.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNUploadRequestState.h"
#import "QNUploadDomainRegion.h"
#import "QNResponseInfo.h"
#import "QNUploadServer.h"
#import "QNZoneInfo.h"
#import "QNUploadServerFreezeUtil.h"
#import "QNUploadServerFreezeManager.h"
#import "QNDnsPrefetch.h"
#import "QNLogUtil.h"
#import "QNUtils.h"
#import "QNDefine.h"
#import "QNUploadServerNetworkStatus.h"
@interface QNUploadIpGroup : NSObject
@property(nonatomic, assign)int ipIndex;
@property(nonatomic, copy, readonly)NSString *groupType;
@property(nonatomic, strong, readonly)NSArray <id <QNIDnsNetworkAddress> > *ipList;
@end
@implementation QNUploadIpGroup
- (instancetype)initWithGroupType:(NSString *)groupType
ipList:(NSArray <id <QNIDnsNetworkAddress> > *)ipList{
if (self = [super init]) {
_groupType = groupType;
_ipList = ipList;
_ipIndex = -1;
}
return self;
}
- (id <QNIDnsNetworkAddress>)getServerIP{
if (!self.ipList || self.ipList.count == 0) {
return nil;
} else {
if (_ipIndex < 0 || _ipIndex > (self.ipList.count - 1)) {
_ipIndex = arc4random()%self.ipList.count;
}
return self.ipList[_ipIndex];
}
}
@end
@interface QNUploadServerDomain: NSObject
@property(nonatomic, copy)NSString *host;
@property(nonatomic, strong)NSArray <QNUploadIpGroup *> *ipGroupList;
@end
@implementation QNUploadServerDomain
+ (QNUploadServerDomain *)domain:(NSString *)host{
QNUploadServerDomain *domain = [[QNUploadServerDomain alloc] init];
domain.host = host;
return domain;
}
- (QNUploadServer *)getServerWithCondition:(BOOL(^)(NSString *host, QNUploadServer *server, QNUploadServer *filterServer))condition {
@synchronized (self) {
if (!self.ipGroupList || self.ipGroupList.count == 0) {
[self createIpGroupList];
}
}
QNUploadServer *server = nil;
// HostIP:
if (self.ipGroupList && self.ipGroupList.count > 0) {
for (QNUploadIpGroup *ipGroup in self.ipGroupList) {
id <QNIDnsNetworkAddress> inetAddress = [ipGroup getServerIP];
QNUploadServer *filterServer = [QNUploadServer server:self.host
ip:inetAddress.ipValue
source:inetAddress.sourceValue
ipPrefetchedTime:inetAddress.timestampValue];
if (condition == nil || condition(self.host, server, filterServer)) {
server = filterServer;
}
if (condition == nil) {
break;
}
}
return server;
}
// HostIP:
QNUploadServer *hostServer = [QNUploadServer server:self.host
ip:nil
source:nil
ipPrefetchedTime:nil];
if (condition == nil || condition(self.host, nil, hostServer)) {
//
server = hostServer;
}
return server;
}
- (QNUploadServer *)getOneServer{
if (!self.host || self.host.length == 0) {
return nil;
}
if (self.ipGroupList && self.ipGroupList.count > 0) {
NSInteger index = arc4random()%self.ipGroupList.count;
QNUploadIpGroup *ipGroup = self.ipGroupList[index];
id <QNIDnsNetworkAddress> inetAddress = [ipGroup getServerIP];
QNUploadServer *server = [QNUploadServer server:self.host ip:inetAddress.ipValue source:inetAddress.sourceValue ipPrefetchedTime:inetAddress.timestampValue];;
return server;
} else {
return [QNUploadServer server:self.host ip:nil source:nil ipPrefetchedTime:nil];
}
}
- (void)clearIpGroupList {
@synchronized (self) {
self.ipGroupList = nil;
}
}
- (void)createIpGroupList {
@synchronized (self) {
if (self.ipGroupList && self.ipGroupList.count > 0) {
return;
}
// get address List of host
NSArray *inetAddresses = [kQNDnsPrefetch getInetAddressByHost:self.host];
if (!inetAddresses || inetAddresses.count == 0) {
return;
}
// address List to ipList of group & check ip network
NSMutableDictionary *ipGroupInfos = [NSMutableDictionary dictionary];
for (id <QNIDnsNetworkAddress> inetAddress in inetAddresses) {
NSString *ipValue = inetAddress.ipValue;
NSString *groupType = [QNUtils getIpType:ipValue host:self.host];
if (groupType) {
NSMutableArray *ipList = ipGroupInfos[groupType] ?: [NSMutableArray array];
[ipList addObject:inetAddress];
ipGroupInfos[groupType] = ipList;
}
}
// ipList of group to ipGroup List
NSMutableArray *ipGroupList = [NSMutableArray array];
for (NSString *groupType in ipGroupInfos.allKeys) {
NSArray *ipList = ipGroupInfos[groupType];
QNUploadIpGroup *ipGroup = [[QNUploadIpGroup alloc] initWithGroupType:groupType ipList:ipList];
[ipGroupList addObject:ipGroup];
}
self.ipGroupList = ipGroupList;
}
}
@end
@interface QNUploadDomainRegion()
// http3
@property(nonatomic, assign)BOOL http3Enabled;
// HostPS Host, Region -9
@property(atomic , assign)BOOL hasFreezeHost;
@property(atomic , assign)BOOL isAllFrozen;
@property(atomic , assign)BOOL enableAccelerateUpload;
// http2
@property(nonatomic, strong)QNUploadServerFreezeManager *partialHttp2Freezer;
@property(nonatomic, strong)QNUploadServerFreezeManager *partialHttp3Freezer;
@property(nonatomic, strong)NSArray <NSString *> *accDomainHostList;
@property(nonatomic, strong)NSDictionary <NSString *, QNUploadServerDomain *> *accDomainDictionary;
@property(nonatomic, strong)NSArray <NSString *> *domainHostList;
@property(nonatomic, strong)NSDictionary <NSString *, QNUploadServerDomain *> *domainDictionary;
@property(nonatomic, strong)NSArray <NSString *> *oldDomainHostList;
@property(nonatomic, strong)NSDictionary <NSString *, QNUploadServerDomain *> *oldDomainDictionary;
@property(nonatomic, strong, nullable)QNZoneInfo *zoneInfo;
@end
@implementation QNUploadDomainRegion
- (instancetype)initWithConfig:(QNConfiguration *)config {
if (self = [super init]) {
if (config) {
self.enableAccelerateUpload = config.accelerateUploading;
}
}
return self;
}
- (BOOL)isValid{
return !self.isAllFrozen &&
((self.enableAccelerateUpload && self.accDomainHostList > 0) ||
self.domainHostList.count > 0 || self.oldDomainHostList.count > 0);
}
- (void)setupRegionData:(QNZoneInfo *)zoneInfo{
_zoneInfo = zoneInfo;
self.isAllFrozen = NO;
self.hasFreezeHost = NO;
self.http3Enabled = zoneInfo.http3Enabled;
//
self.http3Enabled = false;
NSMutableArray *serverGroups = [NSMutableArray array];
NSMutableArray *accDomainHostList = [NSMutableArray array];
if (zoneInfo.acc_domains) {
[serverGroups addObjectsFromArray:zoneInfo.acc_domains];
[accDomainHostList addObjectsFromArray:zoneInfo.acc_domains];
}
self.accDomainHostList = accDomainHostList;
self.accDomainDictionary = [self createDomainDictionary:serverGroups];
[serverGroups removeAllObjects];
NSMutableArray *domainHostList = [NSMutableArray array];
if (zoneInfo.domains) {
[serverGroups addObjectsFromArray:zoneInfo.domains];
[domainHostList addObjectsFromArray:zoneInfo.domains];
}
self.domainHostList = domainHostList;
self.domainDictionary = [self createDomainDictionary:serverGroups];
[serverGroups removeAllObjects];
NSMutableArray *oldDomainHostList = [NSMutableArray array];
if (zoneInfo.old_domains) {
[serverGroups addObjectsFromArray:zoneInfo.old_domains];
[oldDomainHostList addObjectsFromArray:zoneInfo.old_domains];
}
self.oldDomainHostList = oldDomainHostList;
self.oldDomainDictionary = [self createDomainDictionary:serverGroups];
QNLogInfo(@"region :%@",domainHostList);
QNLogInfo(@"region old:%@",oldDomainHostList);
}
- (NSDictionary *)createDomainDictionary:(NSArray <NSString *> *)hosts{
NSMutableDictionary *domainDictionary = [NSMutableDictionary dictionary];
for (NSString *host in hosts) {
QNUploadServerDomain *domain = [QNUploadServerDomain domain:host];
[domainDictionary setObject:domain forKey:host];
}
return [domainDictionary copy];
}
- (void)updateIpListFormHost:(NSString *)host {
if (host == nil) {
return;
}
[self.domainDictionary[host] clearIpGroupList];
[self.oldDomainDictionary[host] clearIpGroupList];
}
- (id<QNUploadServer> _Nullable)getNextServer:(QNUploadRequestState *)requestState
responseInfo:(QNResponseInfo *)responseInfo
freezeServer:(id <QNUploadServer> _Nullable)freezeServer{
if (self.isAllFrozen) {
return nil;
}
[self freezeServerIfNeed:responseInfo freezeServer:freezeServer];
BOOL accelerate = YES;
@synchronized (self) {
if (self.enableAccelerateUpload && responseInfo.isTransferAccelerationConfigureError) {
self.enableAccelerateUpload = NO;
}
accelerate = self.enableAccelerateUpload;
}
NSMutableArray *hostList = [NSMutableArray array];
NSMutableDictionary *domainInfo = [NSMutableDictionary dictionary];
if (requestState.isUseOldServer) {
if (self.oldDomainHostList.count > 0 && self.oldDomainDictionary.count > 0) {
[hostList addObjectsFromArray:self.oldDomainHostList];
[domainInfo addEntriesFromDictionary:self.oldDomainDictionary];
}
} else {
// 使 acc
if (accelerate &&
self.accDomainHostList.count > 0 &&
self.accDomainDictionary.count > 0) {
[hostList addObjectsFromArray:self.accDomainHostList];
[domainInfo addEntriesFromDictionary:self.accDomainDictionary];
}
if (self.domainHostList.count > 0 &&
self.domainDictionary.count > 0){
[hostList addObjectsFromArray:self.domainHostList];
[domainInfo addEntriesFromDictionary:self.domainDictionary];
}
}
if (hostList.count == 0 || domainInfo.count == 0) {
return nil;
}
QNUploadServer *server = nil;
// 1. 使http3
if (self.http3Enabled) {
for (NSString *host in hostList) {
QNUploadServer *domainServer = [domainInfo[host] getServerWithCondition:^BOOL(NSString *host, QNUploadServer *serverP, QNUploadServer *filterServer) {
// 1.1
NSString *frozenType = QNUploadFrozenType(host, filterServer.ip);
BOOL isFrozen = [QNUploadServerFreezeUtil isType:frozenType
frozenByFreezeManagers:@[self.partialHttp3Freezer, kQNUploadGlobalHttp3Freezer]];
if (isFrozen) {
return NO;
}
// 1.2
return [QNUploadServerNetworkStatus isServerNetworkBetter:filterServer thanServerB:serverP];
}];
server = [QNUploadServerNetworkStatus getBetterNetworkServer:server serverB:domainServer];
if (server) {
break;
}
}
if (server) {
server.httpVersion = kQNHttpVersion3;
return server;
}
}
// 2. http2
for (NSString *host in hostList) {
kQNWeakSelf;
QNUploadServer *domainServer = [domainInfo[host] getServerWithCondition:^BOOL(NSString *host, QNUploadServer *serverP, QNUploadServer *filterServer) {
kQNStrongSelf;
// 2.1
NSString *frozenType = QNUploadFrozenType(host, filterServer.ip);
BOOL isFrozen = [QNUploadServerFreezeUtil isType:frozenType
frozenByFreezeManagers:@[self.partialHttp2Freezer, kQNUploadGlobalHttp2Freezer]];
if (isFrozen) {
return NO;
}
// 2.2
return [QNUploadServerNetworkStatus isServerNetworkBetter:filterServer thanServerB:serverP];
}];
server = [QNUploadServerNetworkStatus getBetterNetworkServer:server serverB:domainServer];
if (server) {
break;
}
}
// 3. server Host
if (server == nil && !self.hasFreezeHost && hostList.count > 0) {
NSInteger index = arc4random()%hostList.count;
NSString *host = hostList[index];
server = [domainInfo[host] getOneServer];
[self unfreezeServer:server];
}
if (server == nil) {
self.isAllFrozen = YES;
}
server.httpVersion = kQNHttpVersion2;
QNLogInfo(@"get server host:%@ ip:%@", server.host, server.ip);
return server;
}
- (void)freezeServerIfNeed:(QNResponseInfo *)responseInfo
freezeServer:(QNUploadServer *)freezeServer {
if (freezeServer == nil || freezeServer.serverId == nil || responseInfo == nil) {
return;
}
NSString *frozenType = QNUploadFrozenType(freezeServer.host, freezeServer.ip);
// 1. http3
if (kQNIsHttp3(freezeServer.httpVersion)) {
if (responseInfo.isNotQiniu) {
self.hasFreezeHost = YES;
[self.partialHttp3Freezer freezeType:frozenType frozenTime:kQNGlobalConfiguration.partialHostFrozenTime];
}
if (!responseInfo.canConnectToHost || responseInfo.isHostUnavailable) {
self.hasFreezeHost = YES;
[kQNUploadGlobalHttp3Freezer freezeType:frozenType frozenTime:kQNUploadHttp3FrozenTime];
}
return;
}
// 2. http2
// 2.1 Host || Host
if (responseInfo.isNotQiniu || !responseInfo.canConnectToHost || responseInfo.isHostUnavailable) {
QNLogInfo(@"partial freeze server host:%@ ip:%@", freezeServer.host, freezeServer.ip);
self.hasFreezeHost = YES;
[self.partialHttp2Freezer freezeType:frozenType frozenTime:kQNGlobalConfiguration.partialHostFrozenTime];
}
// 2.2 Host
if (responseInfo.isHostUnavailable) {
QNLogInfo(@"global freeze server host:%@ ip:%@", freezeServer.host, freezeServer.ip);
self.hasFreezeHost = YES;
[kQNUploadGlobalHttp2Freezer freezeType:frozenType frozenTime:kQNGlobalConfiguration.globalHostFrozenTime];
}
}
///
- (void)unfreezeServer:(QNUploadServer *)freezeServer {
if (freezeServer == nil) {
return;
}
NSString *frozenType = QNUploadFrozenType(freezeServer.host, freezeServer.ip);
[self.partialHttp2Freezer unfreezeType:frozenType];
}
- (QNUploadServerFreezeManager *)partialHttp2Freezer{
if (!_partialHttp2Freezer) {
_partialHttp2Freezer = [[QNUploadServerFreezeManager alloc] init];
}
return _partialHttp2Freezer;
}
- (QNUploadServerFreezeManager *)partialHttp3Freezer{
if (!_partialHttp3Freezer) {
_partialHttp3Freezer = [[QNUploadServerFreezeManager alloc] init];
}
return _partialHttp3Freezer;
}
@end

View File

@@ -0,0 +1,29 @@
//
// QNUploadServer.h
// AppTest
//
// Created by yangsen on 2020/4/23.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNUploadRegionInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadServer : NSObject <QNUploadServer>
@property(nonatomic, copy)NSString *httpVersion;
/// 上传server构造方法
/// @param host host
/// @param ip host对应的IP
/// @param source ip查询来源@"system"@"httpdns" @"none" @"customized" 自定义请使用@"customized"
/// @param ipPrefetchedTime 根据host获取IP的时间戳
+ (instancetype)server:(NSString * _Nullable)host
ip:(NSString * _Nullable)ip
source:(NSString * _Nullable)source
ipPrefetchedTime:(NSNumber * _Nullable)ipPrefetchedTime;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,39 @@
//
// QNUploadServer.m
// AppTest
//
// Created by yangsen on 2020/4/23.
// Copyright © 2020 com.qiniu. All rights reserved.
//
#import "QNUploadServer.h"
@interface QNUploadServer()
@property(nonatomic, copy)NSString *ip;
@property(nonatomic, copy)NSString *host;
@property(nonatomic, copy)NSString *source;
@property(nonatomic,strong)NSNumber *ipPrefetchedTime;
@end
@implementation QNUploadServer
@synthesize httpVersion;
+ (instancetype)server:(NSString *)host
ip:(NSString *)ip
source:(NSString *)source
ipPrefetchedTime:(NSNumber *)ipPrefetchedTime{
QNUploadServer *server = [[QNUploadServer alloc] init];
server.ip = ip;
server.host = host;
server.source = source ?: @"none";
server.httpVersion = kQNHttpVersion2;
server.ipPrefetchedTime = ipPrefetchedTime;
return server;
}
- (NSString *)serverId {
return [self.host copy];
}
@end

View File

@@ -0,0 +1,30 @@
//
// QNUploadServerFreezeManager.h
// QiniuSDK
//
// Created by yangsen on 2020/6/2.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadServerFreezeManager : NSObject
/// 查询host是否被冻结
/// @param type 冻结Key
- (BOOL)isTypeFrozen:(NSString * _Nullable)type;
/// 冻结host
/// @param type 冻结Key
/// @param frozenTime 冻结时间
- (void)freezeType:(NSString * _Nullable)type frozenTime:(NSInteger)frozenTime;
/// 解冻host
/// @param type 冻结Key
- (void)unfreezeType:(NSString * _Nullable)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,97 @@
//
// QNUploadServerFreezeManager.m
// QiniuSDK
//
// Created by yangsen on 2020/6/2.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNUploadServerFreezeManager.h"
@interface QNUploadServerFreezeItem : NSObject
@property(nonatomic, copy)NSString *type;
@property(nonatomic, strong)NSDate *freezeDate;
@end
@implementation QNUploadServerFreezeItem
+ (instancetype)item:(NSString *)type{
QNUploadServerFreezeItem *item = [[QNUploadServerFreezeItem alloc] init];
item.type = type;
return item;
}
- (BOOL)isFrozenByDate:(NSDate *)date{
BOOL isFrozen = YES;
@synchronized (self) {
if (!self.freezeDate || [self.freezeDate timeIntervalSinceDate:date] < 0){
isFrozen = NO;
}
}
return isFrozen;
}
- (void)freeze:(NSInteger)frozenTime{
@synchronized (self) {
self.freezeDate = [NSDate dateWithTimeIntervalSinceNow:frozenTime];
}
}
@end
@interface QNUploadServerFreezeManager()
@property(nonatomic, strong)NSMutableDictionary *freezeInfo;
@end
@implementation QNUploadServerFreezeManager
- (instancetype)init{
if (self = [super init]) {
_freezeInfo = [NSMutableDictionary dictionary];
}
return self;
}
- (BOOL)isTypeFrozen:(NSString * _Nullable)type {
if (!type || type.length == 0) {
return true;
}
BOOL isFrozen = true;
QNUploadServerFreezeItem *item = nil;
@synchronized (self) {
item = self.freezeInfo[type];
}
if (!item || ![item isFrozenByDate:[NSDate date]]) {
isFrozen = false;
}
return isFrozen;
}
- (void)freezeType:(NSString * _Nullable)type frozenTime:(NSInteger)frozenTime {
if (!type || type.length == 0) {
return;
}
QNUploadServerFreezeItem *item = nil;
@synchronized (self) {
item = self.freezeInfo[type];
if (!item) {
item = [QNUploadServerFreezeItem item:type];
self.freezeInfo[type] = item;
}
}
[item freeze:frozenTime];
}
- (void)unfreezeType:(NSString * _Nullable)type {
if (!type || type.length == 0) {
return;
}
@synchronized (self) {
[self.freezeInfo removeObjectForKey:type];
}
}
@end

View File

@@ -0,0 +1,31 @@
//
// QNUploadServerFreezeUtil.h
// QiniuSDK
//
// Created by yangsen on 2021/2/4.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadServerFreezeManager.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define kQNUploadHttp3FrozenTime (3600 * 24)
#define QNUploadFrozenType(HOST, IP) ([QNUploadServerFreezeUtil getFrozenType:HOST ip:IP])
#define kQNUploadGlobalHttp3Freezer [QNUploadServerFreezeUtil sharedHttp3Freezer]
#define kQNUploadGlobalHttp2Freezer [QNUploadServerFreezeUtil sharedHttp2Freezer]
@interface QNUploadServerFreezeUtil : NSObject
+ (QNUploadServerFreezeManager *)sharedHttp2Freezer;
+ (QNUploadServerFreezeManager *)sharedHttp3Freezer;
+ (BOOL)isType:(NSString *)type frozenByFreezeManagers:(NSArray <QNUploadServerFreezeManager *> *)freezeManagerList;
+ (NSString *)getFrozenType:(NSString *)host ip:(NSString *)ip;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,55 @@
//
// QNUploadServerFreezeUtil.m
// QiniuSDK
//
// Created by yangsen on 2021/2/4.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUtils.h"
#import "QNUploadServerFreezeUtil.h"
@implementation QNUploadServerFreezeUtil
+ (QNUploadServerFreezeManager *)sharedHttp2Freezer {
static QNUploadServerFreezeManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[QNUploadServerFreezeManager alloc] init];
});
return manager;
}
+ (QNUploadServerFreezeManager *)sharedHttp3Freezer {
static QNUploadServerFreezeManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[QNUploadServerFreezeManager alloc] init];
});
return manager;
}
+ (BOOL)isType:(NSString *)type frozenByFreezeManagers:(NSArray <QNUploadServerFreezeManager *> *)freezeManagerList{
if (!type || type.length == 0) {
return YES;
}
if (!freezeManagerList || freezeManagerList.count == 0) {
return NO;
}
BOOL isFrozen = NO;
for (QNUploadServerFreezeManager *freezeManager in freezeManagerList) {
isFrozen = [freezeManager isTypeFrozen:type];
if (isFrozen) {
break;
}
}
return isFrozen;
}
+ (NSString *)getFrozenType:(NSString *)host ip:(NSString *)ip {
NSString *ipType = [QNUtils getIpType:ip host:host];
return [NSString stringWithFormat:@"%@-%@", host, ipType];
}
@end

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
</array>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

26
Pods/Qiniu/QiniuSDK/QiniuSDK.h generated Normal file
View File

@@ -0,0 +1,26 @@
//
// QiniuSDK.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNErrorCode.h"
#import "QNDns.h"
#import "QNZone.h"
#import "QNFixedZone.h"
#import "QNAutoZone.h"
#import "QNLogUtil.h"
#import "QNConfiguration.h"
#import "QNServerConfigMonitor.h"
#import "QNRecorderDelegate.h"
#import "QNFileRecorder.h"
#import "QNResponseInfo.h"
#import "QNUploadManager.h"
#import "QNUploadOption.h"
#import "QNUrlSafeBase64.h"
#import "QNReportConfig.h"
#import "QNPipeline.h"

55
Pods/Qiniu/QiniuSDK/Recorder/QNFileRecorder.h generated Executable file
View File

@@ -0,0 +1,55 @@
//
// QNFileRecorder.h
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import "QNRecorderDelegate.h"
#import <Foundation/Foundation.h>
/**
* 将上传记录保存到文件系统中
*/
@interface QNFileRecorder : NSObject <QNRecorderDelegate>
/**
* 用指定保存的目录进行初始化
*
* @param directory 目录
* @param error 输出的错误信息
*
* @return 实例
*/
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
error:(NSError *__autoreleasing *)error;
/**
* 用指定保存的目录以及是否进行base64编码进行初始化
*
* @param directory 目录
* @param encode 为避免因为特殊字符或含有/无法保存持久化记录故用此参数指定是否要base64编码
* @param error 输出错误信息
*
* @return 实例
*/
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
encodeKey:(BOOL)encode
error:(NSError *__autoreleasing *)error;
/**
* 从外部手动删除记录,如无特殊需求,不建议使用
*
* @param key 持久化记录key
* @param dir 目录
* @param encode 是否encode
*/
+ (void)removeKey:(NSString *)key
directory:(NSString *)dir
encodeKey:(BOOL)encode;
// 删除所有修改时间在 date 之前的缓存
- (NSError *)deleteAll;
@end

View File

@@ -0,0 +1,127 @@
//
// QNFileRecorder.m
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import "QNFileRecorder.h"
#import "QNUrlSafeBase64.h"
@interface QNFileRecorder ()
@property (copy, readonly) NSString *directory;
@property BOOL encode;
@end
@implementation QNFileRecorder
- (NSString *)pathOfKey:(NSString *)key {
return [QNFileRecorder pathJoin:key path:_directory];
}
+ (NSString *)pathJoin:(NSString *)key
path:(NSString *)path {
return [[NSString alloc] initWithFormat:@"%@/%@", path, key];
}
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
error:(NSError *__autoreleasing *)perror {
return [QNFileRecorder fileRecorderWithFolder:directory encodeKey:false error:perror];
}
+ (instancetype)fileRecorderWithFolder:(NSString *)directory
encodeKey:(BOOL)encode
error:(NSError *__autoreleasing *)perror {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error];
if (error != nil) {
if (perror) {
*perror = error;
}
return nil;
}
return [[QNFileRecorder alloc] initWithFolder:directory encodeKey:encode];
}
- (instancetype)initWithFolder:(NSString *)directory encodeKey:(BOOL)encode {
if (self = [super init]) {
_directory = directory;
_encode = encode;
}
return self;
}
- (NSError *)set:(NSString *)key
data:(NSData *)value {
NSError *error;
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
[value writeToFile:[self pathOfKey:key] options:NSDataWritingAtomic error:&error];
return error;
}
- (NSData *)get:(NSString *)key {
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
return [NSData dataWithContentsOfFile:[self pathOfKey:key]];
}
- (NSError *)del:(NSString *)key {
NSError *error;
if (_encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
[[NSFileManager defaultManager] removeItemAtPath:[self pathOfKey:key] error:&error];
return error;
}
- (NSError *)deleteAll {
NSString *filePath = self.directory;
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir = NO;
BOOL existed = [fileManager fileExistsAtPath:filePath isDirectory:&isDir];
if ( !(isDir == YES && existed == YES) ) {
return nil;
}
//
NSError *error;
[fileManager removeItemAtPath:filePath error:&error];
if (error != nil) {
return error;
}
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error];
return error;
}
- (NSString *)getFileName{
return nil;
}
+ (void)removeKey:(NSString *)key
directory:(NSString *)dir
encodeKey:(BOOL)encode {
if (encode) {
key = [QNUrlSafeBase64 encodeString:key];
}
NSError *error;
NSString *path = [QNFileRecorder pathJoin:key path:dir];
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
if (error) {
NSLog(@"%s,%@", __func__, error);
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, dir: %@>", NSStringFromClass([self class]), self, _directory];
}
@end

View File

@@ -0,0 +1,60 @@
//
// QNRecorderDelegate.h
// QiniuSDK
//
// Created by bailong on 14/10/5.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 为持久化上传记录根据上传的key以及文件名 生成持久化的记录key
*
* @param uploadKey 上传的key
* @param filePath 文件名
*
* @return 根据uploadKey, filepath 算出的记录key
*/
typedef NSString * (^QNRecorderKeyGenerator)(NSString *uploadKey, NSString *filePath);
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@protocol QNRecorderDelegate <NSObject>
/**
* 保存记录
*
* @param key 持久化记录的key
* @param value 持久化记录上传状态信息
*
* @return 错误信息成功为nil
*/
- (NSError *)set:(NSString *)key
data:(NSData *)value;
/**
* 取出保存的持久化上传状态信息
*
* @param key 持久化记录key
*
* @return 上传中间状态信息
*/
- (NSData *)get:(NSString *)key;
/**
* 删除持久化记录,一般在上传成功后自动调用
*
* @param key 持久化记录key
*
* @return 错误信息成功为nil
*/
- (NSError *)del:(NSString *)key;
/**
* 缓存文件名称
*/
- (NSString *)getFileName;
@end

126
Pods/Qiniu/QiniuSDK/Storage/QNBaseUpload.h generated Normal file
View File

@@ -0,0 +1,126 @@
//
// QNBaseUpload.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2020/4/19.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNRecorderDelegate.h"
#import "QNUpToken.h"
#import "QNUrlSafeBase64.h"
#import "QNAsyncRun.h"
#import "QNUploadManager.h"
#import "QNUploadOption.h"
#import "QNZone.h"
#import "QNUploadSource.h"
#import "QNUploadRequestMetrics.h"
extern NSString *const QNUploadUpTypeForm;
extern NSString *const QNUploadUpTypeResumableV1;
extern NSString *const QNUploadUpTypeResumableV2;
typedef void (^QNUpTaskCompletionHandler)(QNResponseInfo *info, NSString *key, QNUploadTaskMetrics *metrics, NSDictionary *resp);
@interface QNBaseUpload : NSObject
@property (nonatomic, copy, readonly) NSString *upType;
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, copy, readonly) NSString *fileName;
@property (nonatomic, strong, readonly) NSData *data;
@property (nonatomic, strong, readonly) id <QNUploadSource> uploadSource;
@property (nonatomic, strong, readonly) QNUpToken *token;
@property (nonatomic, strong, readonly) QNUploadOption *option;
@property (nonatomic, strong, readonly) QNConfiguration *config;
@property (nonatomic, strong, readonly) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy, readonly) NSString *recorderKey;
@property (nonatomic, strong, readonly) QNUpTaskCompletionHandler completionHandler;
@property (nonatomic, strong, readonly) QNUploadRegionRequestMetrics *currentRegionRequestMetrics;
@property (nonatomic, strong, readonly) QNUploadTaskMetrics *metrics;
//MARK:-- 构造函数
/// file构造函数
/// @param uploadSource 文件源
/// @param key 上传key
/// @param token 上传token
/// @param option 上传option
/// @param config 上传config
/// @param recorder 断点续传记录信息
/// @param recorderKey 断电上传信息保存的key值需确保唯一性
/// @param completionHandler 上传完成回调
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler;
/// data 构造函数
/// @param data 上传data流
/// @param key 上传key
/// @param fileName 上传fileName
/// @param token 上传token
/// @param option 上传option
/// @param config 上传config
/// @param completionHandler 上传完成回调
- (instancetype)initWithData:(NSData *)data
key:(NSString *)key
fileName:(NSString *)fileName
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
completionHandler:(QNUpTaskCompletionHandler)completionHandler;
/// 初始化数据
- (void)initData;
//MARK: -- 上传
/// 开始上传流程
- (void)run;
/// 准备上传
- (int)prepareToUpload;
/// 重新加载上传数据
- (BOOL)reloadUploadInfo;
/// 开始上传
- (void)startToUpload;
/// 切换区域
- (BOOL)switchRegionAndUpload;
// 根据错误信息进行切换region并上传return:是否切换region并上传
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo;
/// 上传结束调用回调方法,在上传结束时调用,该方法内部会调用回调,已通知上层上传结束
/// @param info 上传返回信息
/// @param response 上传字典信息
- (void)complete:(QNResponseInfo *)info
response:(NSDictionary *)response;
//MARK: -- 机房管理
/// 在区域列表头部插入一个区域
- (void)insertRegionAtFirst:(id <QNUploadRegion>)region;
/// 切换区域
- (BOOL)switchRegion;
/// 获取目标区域
- (id <QNUploadRegion>)getTargetRegion;
/// 获取当前区域
- (id <QNUploadRegion>)getCurrentRegion;
//MARK: -- upLog
// 一个上传流程可能会发起多个上传操作上传多个分片每个上传操作均是以一个Region的host做重试操作
- (void)addRegionRequestMetricsOfOneFlow:(QNUploadRegionRequestMetrics *)metrics;
@end

295
Pods/Qiniu/QiniuSDK/Storage/QNBaseUpload.m generated Normal file
View File

@@ -0,0 +1,295 @@
//
// QNBaseUpload.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2020/4/19.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNAutoZone.h"
#import "QNZoneInfo.h"
#import "QNResponseInfo.h"
#import "QNDefine.h"
#import "QNBaseUpload.h"
#import "QNUploadDomainRegion.h"
NSString *const QNUploadUpTypeForm = @"form";
NSString *const QNUploadUpTypeResumableV1 = @"resumable_v1";
NSString *const QNUploadUpTypeResumableV2 = @"resumable_v2";
@interface QNBaseUpload ()
@property (nonatomic, strong) QNBaseUpload *strongSelf;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) id <QNUploadSource> uploadSource;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy) NSString *recorderKey;
@property (nonatomic, strong) QNUpTaskCompletionHandler completionHandler;
@property (nonatomic, assign)NSInteger currentRegionIndex;
@property (nonatomic, strong)NSMutableArray <id <QNUploadRegion> > *regions;
@property (nonatomic, strong)QNUploadRegionRequestMetrics *currentRegionRequestMetrics;
@property (nonatomic, strong) QNUploadTaskMetrics *metrics;
@end
@implementation QNBaseUpload
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
return [self initWithSource:uploadSource data:nil fileName:[uploadSource getFileName] key:key token:token option:option configuration:config recorder:recorder recorderKey:recorderKey completionHandler:completionHandler];
}
- (instancetype)initWithData:(NSData *)data
key:(NSString *)key
fileName:(NSString *)fileName
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
return [self initWithSource:nil data:data fileName:fileName key:key token:token option:option configuration:config recorder:nil recorderKey:nil completionHandler:completionHandler];
}
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
data:(NSData *)data
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
if (self = [super init]) {
_uploadSource = uploadSource;
_data = data;
_fileName = fileName ?: @"?";
_key = key;
_token = token;
_config = config;
_option = option ?: [QNUploadOption defaultOptions];
_recorder = recorder;
_recorderKey = recorderKey;
_completionHandler = completionHandler;
[self initData];
}
return self;
}
- (instancetype)init{
if (self = [super init]) {
[self initData];
}
return self;
}
- (void)initData{
_strongSelf = self;
_currentRegionIndex = 0;
}
- (void)run {
[self.metrics start];
kQNWeakSelf;
[_config.zone query:self.config token:self.token on:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, QNZonesInfo * _Nullable zonesInfo) {
kQNStrongSelf;
self.metrics.ucQueryMetrics = metrics;
if (responseInfo != nil && responseInfo.isOK && zonesInfo) {
if (![self setupRegions:zonesInfo]) {
responseInfo = [QNResponseInfo responseInfoWithInvalidArgument:[NSString stringWithFormat:@"setup regions host fail, origin response:%@", responseInfo]];
[self complete:responseInfo response:responseInfo.responseDictionary];
return;
}
int prepareCode = [self prepareToUpload];
if (prepareCode == 0) {
[self startToUpload];
} else {
QNResponseInfo *responseInfoP = [QNResponseInfo errorResponseInfo:prepareCode errorDesc:nil];
[self complete:responseInfoP response:responseInfoP.responseDictionary];
}
} else {
if (responseInfo == nil) {
// responseInfo
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"can't get regions"];
}
[self complete:responseInfo response:responseInfo.responseDictionary];
}
}];
}
- (BOOL)reloadUploadInfo {
return YES;
}
- (int)prepareToUpload{
return 0;
}
- (void)startToUpload{
self.currentRegionRequestMetrics = [[QNUploadRegionRequestMetrics alloc] initWithRegion:[self getCurrentRegion]];
[self.currentRegionRequestMetrics start];
}
//
- (BOOL)switchRegionAndUpload{
if (self.currentRegionRequestMetrics) {
[self.currentRegionRequestMetrics end];
[self.metrics addMetrics:self.currentRegionRequestMetrics];
self.currentRegionRequestMetrics = nil;
}
BOOL isSwitched = [self switchRegion];
if (isSwitched) {
[self startToUpload];
}
return isSwitched;
}
// regionreturn:region
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo {
if (errorResponseInfo.statusCode == 400 && [errorResponseInfo.message containsString:@"incorrect region"]) {
[QNAutoZone clearCache];
}
if (!errorResponseInfo || errorResponseInfo.isOK || // ||
![errorResponseInfo couldRetry] || ![self.config allowBackupHost]) { //
return false;
}
if (self.currentRegionRequestMetrics) {
[self.currentRegionRequestMetrics end];
[self.metrics addMetrics:self.currentRegionRequestMetrics];
self.currentRegionRequestMetrics = nil;
}
// & Resource index
if (![self reloadUploadInfo]) {
return false;
}
// context
if (!errorResponseInfo.isCtxExpiedError && ![self switchRegion]) {
// context region
return false;
}
[self startToUpload];
return true;
}
- (void)complete:(QNResponseInfo *)info
response:(NSDictionary *)response{
[self.metrics end];
[self.currentRegionRequestMetrics end];
if (self.currentRegionRequestMetrics) {
[self.metrics addMetrics:self.currentRegionRequestMetrics];
}
if (self.completionHandler) {
self.completionHandler(info, _key, _metrics, response);
}
self.strongSelf = nil;
}
//MARK:-- region
- (BOOL)setupRegions:(QNZonesInfo *)zonesInfo{
if (zonesInfo == nil || zonesInfo.zonesInfo == nil || zonesInfo.zonesInfo.count == 0) {
return NO;
}
NSMutableArray *defaultRegions = [NSMutableArray array];
NSArray *zoneInfos = zonesInfo.zonesInfo;
for (QNZoneInfo *zoneInfo in zoneInfos) {
QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] initWithConfig:self.config];
[region setupRegionData:zoneInfo];
if (region.isValid) {
[defaultRegions addObject:region];
}
}
self.regions = defaultRegions;
self.metrics.regions = defaultRegions;
return defaultRegions.count > 0;
}
- (void)insertRegionAtFirst:(id <QNUploadRegion>)region{
BOOL hasRegion = NO;
for (id <QNUploadRegion> regionP in self.regions) {
if ([regionP.zoneInfo.regionId isEqualToString:region.zoneInfo.regionId]) {
hasRegion = YES;
break;
}
}
if (!hasRegion) {
[self.regions insertObject:region atIndex:0];
}
}
- (BOOL)switchRegion{
BOOL ret = NO;
@synchronized (self) {
NSInteger regionIndex = _currentRegionIndex + 1;
if (regionIndex < self.regions.count) {
_currentRegionIndex = regionIndex;
ret = YES;
}
}
return ret;
}
- (id <QNUploadRegion>)getTargetRegion{
return self.regions.firstObject;
}
- (id <QNUploadRegion>)getCurrentRegion{
id <QNUploadRegion> region = nil;
@synchronized (self) {
if (self.currentRegionIndex < self.regions.count) {
region = self.regions[self.currentRegionIndex];
}
}
return region;
}
- (void)addRegionRequestMetricsOfOneFlow:(QNUploadRegionRequestMetrics *)metrics{
if (metrics == nil) {
return;
}
@synchronized (self) {
if (self.currentRegionRequestMetrics == nil) {
self.currentRegionRequestMetrics = metrics;
return;
}
}
[self.currentRegionRequestMetrics addMetrics:metrics];
}
- (QNUploadTaskMetrics *)metrics {
if (_metrics == nil) {
_metrics = [QNUploadTaskMetrics taskMetrics:self.upType];
}
return _metrics;
}
@end

View File

@@ -0,0 +1,14 @@
//
// QNConcurrentResumeUpload.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
/// 并发分片上传
#import "QNPartsUpload.h"
@interface QNConcurrentResumeUpload : QNPartsUpload
@end

View File

@@ -0,0 +1,43 @@
//
// QNConcurrentResumeUpload.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNConcurrentResumeUpload.h"
@interface QNConcurrentResumeUpload()
@property(nonatomic, strong) dispatch_group_t uploadGroup;
@property(nonatomic, strong) dispatch_queue_t uploadQueue;
@end
@implementation QNConcurrentResumeUpload
- (int)prepareToUpload{
self.uploadGroup = dispatch_group_create();
self.uploadQueue = dispatch_queue_create("com.qiniu.concurrentUpload", DISPATCH_QUEUE_SERIAL);
return [super prepareToUpload];
}
- (void)uploadRestData:(dispatch_block_t)completeHandler {
QNLogInfo(@"key:%@ 并发分片", self.key);
for (int i = 0; i < self.config.concurrentTaskCount; i++) {
dispatch_group_enter(self.uploadGroup);
dispatch_group_async(self.uploadGroup, self.uploadQueue, ^{
[super performUploadRestData:^{
dispatch_group_leave(self.uploadGroup);
}];
});
}
dispatch_group_notify(self.uploadGroup, self.uploadQueue, ^{
completeHandler();
});
}
@end

View File

@@ -0,0 +1,346 @@
//
// QNConfiguration.h
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
#import "QNDns.h"
/**
* 断点上传时的分块大小
*/
extern const UInt32 kQNBlockSize;
/**
* DNS默认缓存时间
*/
extern const UInt32 kQNDefaultDnsCacheTime;
/**
* 转换为用户需要的url
*
* @param url 上传url
*
* @return 根据上传url算出代理url
*/
typedef NSString * (^QNUrlConvert)(NSString *url);
typedef NS_ENUM(NSInteger, QNResumeUploadVersion){
QNResumeUploadVersionV1, // 分片v1
QNResumeUploadVersionV2 // 分片v2
};
@class QNConfigurationBuilder;
@class QNZone;
@class QNReportConfig;
/**
* Builder block
*
* @param builder builder实例
*/
typedef void (^QNConfigurationBuilderBlock)(QNConfigurationBuilder *builder);
@interface QNConfiguration : NSObject
/**
* 存储区域
*/
@property (copy, nonatomic, readonly) QNZone *zone;
/**
* 断点上传时的分片大小
*/
@property (readonly) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传否则使用form上传
*/
@property (readonly) UInt32 putThreshold;
/**
* 上传失败时每个上传域名的重试次数默认重试1次
*/
@property (readonly) UInt32 retryMax;
/**
* 重试前等待时长默认0.5s
*/
@property (readonly) NSTimeInterval retryInterval;
/**
* 单个请求超时时间 单位 秒
* 注:每个文件上传肯能存在多个操作,当每个操作失败时,可能存在多个请求重试。
*/
@property (readonly) UInt32 timeoutInterval;
/**
* 是否使用 https默认为 YES
*/
@property (nonatomic, assign, readonly) BOOL useHttps;
/**
* 单个文件是否开启并发分片上传默认为NO
* 单个文件大小大于4M时会采用分片上传每个分片会已单独的请求进行上传操作多个上传操作可以使用并发
* 也可以采用串行,采用并发时,可以设置并发的个数(对concurrentTaskCount进行设置)。
*/
@property (nonatomic, assign, readonly) BOOL useConcurrentResumeUpload;
/**
* 分片上传版本
*/
@property (nonatomic, assign, readonly) QNResumeUploadVersion resumeUploadVersion;
/**
* 并发分片上传的并发任务个数在concurrentResumeUpload为YES时有效默认为3个
*/
@property (nonatomic, assign, readonly) UInt32 concurrentTaskCount;
/**
* 重试时是否允许使用备用上传域名默认为YES
*/
@property (nonatomic, assign) BOOL allowBackupHost;
/**
* 是否允许使用加速域名,默认为 false
*/
@property (nonatomic, assign, readonly) BOOL accelerateUploading;
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@property (nonatomic, readonly) id<QNRecorderDelegate> recorder;
/**
* 为持久化上传记录根据上传的key以及文件名 生成持久化的记录key
*/
@property (nonatomic, readonly) QNRecorderKeyGenerator recorderKeyGen;
/**
* 上传请求代理配置信息
*/
@property (nonatomic, readonly) NSDictionary *proxy;
/**
* 上传URL转换使url转换为用户需要的url
*/
@property (nonatomic, readonly) QNUrlConvert converter;
/**
* 默认配置
*/
+ (instancetype)defaultConfiguration;
/**
* 使用 QNConfigurationBuilder 进行配置
* @param block 配置block
*/
+ (instancetype)build:(QNConfigurationBuilderBlock)block;
@end
#define kQNGlobalConfiguration [QNGlobalConfiguration shared]
@interface QNGlobalConfiguration : NSObject
/**
* 是否开启dns预解析 默认开启
*/
@property(nonatomic, assign)BOOL isDnsOpen;
/**
* dns 预取失败后 会进行重新预取 dnsRepreHostNum为最多尝试次数
*/
@property(nonatomic, assign)UInt32 dnsRepreHostNum;
/**
* dns 预取超时,单位:秒 默认2
*/
@property(nonatomic, assign)int dnsResolveTimeout;
/**
* dns 预取, ip 默认有效时间 单位:秒 默认120
* 只有在 dns 预取未返回 ttl 时使用
*/
@property(nonatomic, assign)UInt32 dnsCacheTime;
/**
* dns预取缓存最大有效时间 单位:秒 默认 1800
* 当 dns 缓存 ip 过期并未刷新时,只要在 dnsCacheMaxTTL 时间内仍有效。
*/
@property(nonatomic, assign)UInt32 dnsCacheMaxTTL;
/**
* 自定义DNS解析客户端host
*/
@property(nonatomic, strong) id <QNDnsDelegate> dns;
/**
* dns解析结果本地缓存路径
*/
@property(nonatomic, copy, readonly)NSString *dnsCacheDir;
/**
* 是否使用 udp 方式进行 Dns 预取,默认开启
*/
@property(nonatomic, assign)BOOL udpDnsEnable;
/**
* 使用 udp 进行 Dns 预取时的 server ipv4 数组;当对某个 Host 使用 udp 进行 Dns 预取时,会使用 udpDnsIps 进行并发预取
* 当 udpDnsEnable 开启时,使用 udp 进行 Dns 预取方式才会生效
* 默认
*/
@property(nonatomic, copy) NSArray <NSString *> *udpDnsIpv4Servers;
/**
* 使用 udp 进行 Dns 预取时的 server ipv6 数组;当对某个 Host 使用 udp 进行 Dns 预取时,会使用 udpDnsIps 进行并发预取
* 当 udpDnsEnable 开启时,使用 udp 进行 Dns 预取方式才会生效
* 默认nil
*/
@property(nonatomic, copy) NSArray <NSString *> *udpDnsIpv6Servers;
/**
* 是否使用 doh 预取,默认开启
*/
@property(nonatomic, assign)BOOL dohEnable;
/**
* 使用 doh 预取时的 server 数组;当对某个 Host 使用 Doh 预取时,会使用 dohServers 进行并发预取
* 当 dohEnable 开启时doh 预取才会生效
* 注意:如果使用 ip需保证服务证书与 IP 绑定,避免 sni 问题
*/
@property(nonatomic, copy) NSArray <NSString *> *dohIpv4Servers;
/**
* 使用 doh 预取时的 server 数组;当对某个 Host 使用 Doh 预取时,会使用 dohServers 进行并发预取
* 当 dohEnable 开启时doh 预取才会生效
* 默认nil
* 注意:如果使用 ip需保证服务证书与 IP 绑定,避免 sni 问题
*/
@property(nonatomic, copy) NSArray <NSString *> *dohIpv6Servers;
/**
* Host全局冻结时间 单位:秒 默认60 推荐范围:[30 ~ 120]
* 当某个Host的上传失败后并且可能短时间无法恢复会冻结该Host
*/
@property(nonatomic, assign)UInt32 globalHostFrozenTime;
/**
* Host局部冻结时间只会影响当前上传操作 单位:秒 默认5*60 推荐范围:[60 ~ 10*60]
* 当某个Host的上传失败后并且短时间可能会恢复会局部冻结该Host
*/
@property(nonatomic, assign)UInt32 partialHostFrozenTime;
/**
* 网络连接状态检测使用的connectCheckURLStrings网络链接状态检测可能会影响重试机制启动网络连接状态检测有助于提高上传可用性。
* 当请求的 Response 为网络异常时,并发对 connectCheckURLStrings 中 URLString 进行 HEAD 请求,以此检测当前网络状态的链接状态,其中任意一个 URLString 链接成功则认为当前网络状态链接良好;
* 当 connectCheckURLStrings 为 nil 或者 空数组时则弃用检测功能。
*/
@property(nonatomic, copy)NSArray <NSString *> *connectCheckURLStrings;
/**
* 是否开启网络连接状态检测,默认:开启
*/
@property(nonatomic, assign)BOOL connectCheckEnable;
/**
* 网络连接状态检测HEAD请求超时默认2s
*/
@property(nonatomic, assign)NSTimeInterval connectCheckTimeout;
+ (instancetype)shared;
@end
@interface QNConfigurationBuilder : NSObject
/**
* 默认上传服务器地址
*/
@property (nonatomic, strong) QNZone *zone;
/**
* 断点上传时的分片大小
* 分片 v1 最小为 1024即 1K建议用户配置 >= 512K
* 分片 v2 最小为 1024 * 1024即 1M
*/
@property (assign) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传否则使用form上传
*/
@property (assign) UInt32 putThreshold;
/**
* 上传失败时每个上传域名的重试次数默认重试1次
*/
@property (assign) UInt32 retryMax;
/**
* 重试前等待时长默认0.5s
*/
@property (assign) NSTimeInterval retryInterval;
/**
* 超时时间 单位 秒
*/
@property (assign) UInt32 timeoutInterval;
/**
* 是否使用 https默认为 YES
*/
@property (nonatomic, assign) BOOL useHttps;
/**
* 重试时是否允许使用备用上传域名默认为YES
*/
@property (nonatomic, assign) BOOL allowBackupHost;
/**
* 是否允许使用加速域名,默认为 false
*/
@property (nonatomic, assign) BOOL accelerateUploading;
/**
* 是否开启并发分片上传默认为NO
*/
@property (nonatomic, assign) BOOL useConcurrentResumeUpload;
/**
* 分片上传版本
*/
@property (nonatomic, assign) QNResumeUploadVersion resumeUploadVersion;
/**
* 并发分片上传的并发任务个数在concurrentResumeUpload为YES时有效默认为3个
*/
@property (nonatomic, assign) UInt32 concurrentTaskCount;
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@property (nonatomic, strong) id<QNRecorderDelegate> recorder;
/**
* 为持久化上传记录根据上传的key以及文件名 生成持久化的记录key
*/
@property (nonatomic, strong) QNRecorderKeyGenerator recorderKeyGen;
/**
* 上传请求代理配置信息
*/
@property (nonatomic, strong) NSDictionary *proxy;
/**
* 上传URL转换使url转换为用户需要的url
*/
@property (nonatomic, strong) QNUrlConvert converter;
@end

View File

@@ -0,0 +1,293 @@
//
// QNConfiguration.m
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
#import "QNUpToken.h"
#import "QNReportConfig.h"
#import "QNAutoZone.h"
#import "QN_GTM_Base64.h"
const UInt32 kQNBlockSize = 4 * 1024 * 1024;
const UInt32 kQNDefaultDnsCacheTime = 2 * 60;
@implementation QNConfiguration
+ (instancetype)defaultConfiguration{
QNConfigurationBuilder *builder = [[QNConfigurationBuilder alloc] init];
return [[QNConfiguration alloc] initWithBuilder:builder];
}
+ (instancetype)build:(QNConfigurationBuilderBlock)block {
QNConfigurationBuilder *builder = [[QNConfigurationBuilder alloc] init];
block(builder);
return [[QNConfiguration alloc] initWithBuilder:builder];
}
- (instancetype)initWithBuilder:(QNConfigurationBuilder *)builder {
if (self = [super init]) {
_useConcurrentResumeUpload = builder.useConcurrentResumeUpload;
_resumeUploadVersion = builder.resumeUploadVersion;
_concurrentTaskCount = builder.concurrentTaskCount;
_chunkSize = builder.chunkSize;
if (builder.resumeUploadVersion == QNResumeUploadVersionV1) {
if (_chunkSize < 1024) {
_chunkSize = 1024;
}
} else if (builder.resumeUploadVersion == QNResumeUploadVersionV2) {
if (_chunkSize < 1024 * 1024) {
_chunkSize = 1024 * 1024;
}
}
_putThreshold = builder.putThreshold;
_retryMax = builder.retryMax;
_retryInterval = builder.retryInterval;
_timeoutInterval = builder.timeoutInterval;
_recorder = builder.recorder;
_recorderKeyGen = builder.recorderKeyGen;
_proxy = builder.proxy;
_converter = builder.converter;
_zone = builder.zone;
_useHttps = builder.useHttps;
_allowBackupHost = builder.allowBackupHost;
_accelerateUploading = builder.accelerateUploading;
}
return self;
}
@end
@interface QNGlobalConfiguration(){
NSArray *_defaultDohIpv4Servers;
NSArray *_defaultDohIpv6Servers;
NSArray *_defaultUdpDnsIpv4Servers;
NSArray *_defaultUdpDnsIpv6Servers;
NSArray *_defaultConnectCheckUrls;
}
@property(nonatomic, strong)NSArray *defaultDohIpv4Servers;
@property(nonatomic, strong)NSArray *defaultDohIpv6Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv4Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv6Servers;
@property(nonatomic, strong)NSArray *defaultConnectCheckUrls;
@end
@implementation QNGlobalConfiguration
+ (instancetype)shared{
static QNGlobalConfiguration *config = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
config = [[QNGlobalConfiguration alloc] init];
[config setupData];
});
return config;
}
- (void)setupData{
_isDnsOpen = YES;
_dnsResolveTimeout = 2;
_dnsCacheDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches/Dns"];
_dnsRepreHostNum = 2;
_dnsCacheTime = kQNDefaultDnsCacheTime;
_dnsCacheMaxTTL = 10*60;
_dohEnable = true;
_defaultDohIpv4Servers = [self parseBase64Array:@"WyJodHRwczovLzIyMy42LjYuNi9kbnMtcXVlcnkiLCAiaHR0cHM6Ly84LjguOC44L2Rucy1xdWVyeSJd"];
_udpDnsEnable = true;
_defaultUdpDnsIpv4Servers = [self parseBase64Array:@"WyIyMjMuNS41LjUiLCAiMTE0LjExNC4xMTQuMTE0IiwgIjEuMS4xLjEiLCAiOC44LjguOCJd"];
_globalHostFrozenTime = 60;
_partialHostFrozenTime = 5*60;
_connectCheckEnable = YES;
_connectCheckTimeout = 2;
_defaultConnectCheckUrls = [self parseBase64Array:@"WyJodHRwczovL3d3dy5xaW5pdS5jb20iLCAiaHR0cHM6Ly93d3cuYmFpZHUuY29tIiwgImh0dHBzOi8vd3d3Lmdvb2dsZS5jb20iXQ=="];
_connectCheckURLStrings = nil;
}
- (NSArray *)parseBase64Array:(NSString *)data {
NSData *jsonData = [QN_GTM_Base64 decodeData:[data dataUsingEncoding:NSUTF8StringEncoding]];
NSArray *ret = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
if (ret && [ret isKindOfClass:[NSArray class]]) {
return ret;
}
return nil;
}
- (BOOL)isDohEnable {
return _dohEnable && (self.dohIpv4Servers.count > 0 || self.dohIpv6Servers.count > 0) ;
}
- (NSArray<NSString *> *)dohIpv4Servers {
if (_dohIpv4Servers) {
return _dohIpv4Servers;
} else {
return self.defaultDohIpv4Servers;
}
}
- (NSArray<NSString *> *)dohIpv6Servers {
if (_dohIpv6Servers) {
return _dohIpv6Servers;
} else {
return self.defaultDohIpv6Servers;
}
}
- (NSArray<NSString *> *)udpDnsIpv4Servers {
if (_udpDnsIpv4Servers) {
return _udpDnsIpv4Servers;
} else {
return self.defaultUdpDnsIpv4Servers;
}
}
- (NSArray<NSString *> *)udpDnsIpv6Servers {
if (_udpDnsIpv6Servers) {
return _udpDnsIpv6Servers;
} else {
return self.defaultUdpDnsIpv6Servers;
}
}
- (BOOL)isUdpDnsEnable {
return _udpDnsEnable && (self.udpDnsIpv4Servers.count > 0 || self.udpDnsIpv6Servers.count > 0) ;
}
- (NSArray<NSString *> *)connectCheckURLStrings {
if (_connectCheckURLStrings) {
return _connectCheckURLStrings;
} else {
return self.defaultConnectCheckUrls;
}
}
- (NSArray *)defaultDohIpv4Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultDohIpv4Servers) {
arr = [_defaultDohIpv4Servers copy];
}
}
return arr;
}
- (void)setDefaultDohIpv4Servers:(NSArray *)defaultDohIpv4Servers {
@synchronized (self) {
_defaultDohIpv4Servers = defaultDohIpv4Servers;
}
}
- (NSArray *)defaultDohIpv6Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultDohIpv6Servers) {
arr = [_defaultDohIpv6Servers copy];
}
}
return arr;
}
- (void)setDefaultDohIpv6Servers:(NSArray *)defaultDohIpv6Servers {
@synchronized (self) {
_defaultDohIpv6Servers = defaultDohIpv6Servers;
}
}
- (NSArray *)defaultUdpDnsIpv4Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultUdpDnsIpv4Servers) {
arr = [_defaultUdpDnsIpv4Servers copy];
}
}
return arr;
}
- (void)setDefaultUdpDnsIpv4Servers:(NSArray *)defaultUdpDnsIpv4Servers {
@synchronized (self) {
_defaultUdpDnsIpv4Servers = defaultUdpDnsIpv4Servers;
}
}
- (NSArray *)defaultUdpDnsIpv6Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultUdpDnsIpv6Servers) {
arr = [_defaultUdpDnsIpv6Servers copy];
}
}
return arr;
}
- (void)setDefaultUdpDnsIpv6Servers:(NSArray *)defaultUdpDnsIpv6Servers {
@synchronized (self) {
_defaultUdpDnsIpv6Servers = defaultUdpDnsIpv6Servers;
}
}
- (NSArray *)defaultConnectCheckUrls {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultConnectCheckUrls) {
arr = [_defaultConnectCheckUrls copy];
}
}
return arr;
}
- (void)setDefaultConnectCheckUrls:(NSArray *)defaultConnectCheckUrls {
@synchronized (self) {
_defaultConnectCheckUrls = defaultConnectCheckUrls;
}
}
@end
@implementation QNConfigurationBuilder
- (instancetype)init {
if (self = [super init]) {
_zone = [[QNAutoZone alloc] init];
_chunkSize = 2 * 1024 * 1024;
_putThreshold = 4 * 1024 * 1024;
_retryMax = 1;
_timeoutInterval = 90;
_retryInterval = 0.5;
_recorder = nil;
_recorderKeyGen = nil;
_proxy = nil;
_converter = nil;
_useHttps = YES;
_allowBackupHost = YES;
_accelerateUploading = NO;
_useConcurrentResumeUpload = NO;
_resumeUploadVersion = QNResumeUploadVersionV1;
_concurrentTaskCount = 3;
}
return self;
}
@end

13
Pods/Qiniu/QiniuSDK/Storage/QNFormUpload.h generated Executable file
View File

@@ -0,0 +1,13 @@
//
// QNFormUpload.h
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNBaseUpload.h"
@interface QNFormUpload : QNBaseUpload
@end

View File

@@ -0,0 +1,73 @@
//
// QNFormUpload.m
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNLogUtil.h"
#import "QNFormUpload.h"
#import "QNResponseInfo.h"
#import "QNUpProgress.h"
#import "QNRequestTransaction.h"
@interface QNFormUpload ()
@property(nonatomic, strong)QNUpProgress *progress;
@property(nonatomic, strong)QNRequestTransaction *uploadTransaction;
@end
@implementation QNFormUpload
- (void)startToUpload {
[super startToUpload];
QNLogInfo(@"key:%@ form上传", self.key);
self.uploadTransaction = [[QNRequestTransaction alloc] initWithConfig:self.config
uploadOption:self.option
targetRegion:[self getTargetRegion]
currentRegion:[self getCurrentRegion]
key:self.key
token:self.token];
kQNWeakSelf;
void(^progressHandler)(long long totalBytesWritten, long long totalBytesExpectedToWrite) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
[self.progress progress:self.key uploadBytes:totalBytesWritten totalBytes:totalBytesExpectedToWrite];
};
[self.uploadTransaction uploadFormData:self.data
fileName:self.fileName
progress:progressHandler
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
[self addRegionRequestMetricsOfOneFlow:metrics];
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
[self.progress notifyDone:self.key totalBytes:self.data.length];
[self complete:responseInfo response:response];
}];
}
- (QNUpProgress *)progress {
if (_progress == nil) {
_progress = [QNUpProgress progress:self.option.progressHandler byteProgress:self.option.byteProgressHandler];
}
return _progress;
}
- (NSString *)upType {
return QNUploadUpTypeForm;
}
@end

View File

@@ -0,0 +1,24 @@
//
// QNPartsUpload.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/7.
// Copyright © 2020 Qiniu. All rights reserved.
//
/// 分片上传,默认为串行
#import "QNBaseUpload.h"
#import "QNUploadInfo.h"
NS_ASSUME_NONNULL_BEGIN
@class QNRequestTransaction;
@interface QNPartsUpload : QNBaseUpload
/// 上传剩余的数据此方法整合上传流程上传操作为performUploadRestData默认串行上传
- (void)uploadRestData:(dispatch_block_t)completeHandler;
- (void)performUploadRestData:(dispatch_block_t)completeHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,324 @@
//
// QNPartsUpload.m
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/7.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNUtils.h"
#import "QNLogUtil.h"
#import "QNPartsUpload.h"
#import "QNZoneInfo.h"
#import "QNReportItem.h"
#import "QNRequestTransaction.h"
#import "QNPartsUploadPerformerV1.h"
#import "QNPartsUploadPerformerV2.h"
#define kQNRecordFileInfoKey @"recordFileInfo"
#define kQNRecordZoneInfoKey @"recordZoneInfo"
@interface QNPartsUpload()
@property(nonatomic, strong)QNPartsUploadPerformer *uploadPerformer;
@property( atomic, strong)QNResponseInfo *uploadDataErrorResponseInfo;
@property( atomic, strong)NSDictionary *uploadDataErrorResponse;
@end
@implementation QNPartsUpload
- (void)initData {
[super initData];
//
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
QNLogInfo(@"key:%@ 分片V1", self.key);
self.uploadPerformer = [[QNPartsUploadPerformerV1 alloc] initWithSource:self.uploadSource
fileName:self.fileName
key:self.key
token:self.token
option:self.option
configuration:self.config
recorderKey:self.recorderKey];
} else {
QNLogInfo(@"key:%@ 分片V2", self.key);
self.uploadPerformer = [[QNPartsUploadPerformerV2 alloc] initWithSource:self.uploadSource
fileName:self.fileName
key:self.key
token:self.token
option:self.option
configuration:self.config
recorderKey:self.recorderKey];
}
}
- (BOOL)isAllUploaded {
return [self.uploadPerformer.uploadInfo isAllUploaded];
}
- (void)setErrorResponseInfo:(QNResponseInfo *)responseInfo errorResponse:(NSDictionary *)response{
if (!responseInfo) {
return;
}
if (!self.uploadDataErrorResponseInfo || responseInfo.statusCode != kQNSDKInteriorError) {
self.uploadDataErrorResponseInfo = responseInfo;
self.uploadDataErrorResponse = response ?: responseInfo.responseDictionary;
}
}
- (int)prepareToUpload{
int code = [super prepareToUpload];
if (code != 0) {
return code;
}
// region
if (self.uploadPerformer.currentRegion && self.uploadPerformer.currentRegion.isValid) {
// currentRegionregionregionList
[self insertRegionAtFirst:self.uploadPerformer.currentRegion];
QNLogInfo(@"key:%@ 使用缓存region", self.key);
} else {
// currentRegion region
[self.uploadPerformer switchRegion:[self getCurrentRegion]];
}
QNLogInfo(@"key:%@ region:%@", self.key, self.uploadPerformer.currentRegion.zoneInfo.regionId);
if (self.uploadSource == nil) {
code = kQNLocalIOError;
}
return code;
}
- (BOOL)switchRegion{
BOOL isSuccess = [super switchRegion];
if (isSuccess) {
[self.uploadPerformer switchRegion:self.getCurrentRegion];
QNLogInfo(@"key:%@ 切换region%@", self.key , self.uploadPerformer.currentRegion.zoneInfo.regionId);
}
return isSuccess;
}
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo {
[self reportBlock];
return [super switchRegionAndUploadIfNeededWithErrorResponse:errorResponseInfo];
}
- (BOOL)reloadUploadInfo {
if (![super reloadUploadInfo]) {
return NO;
}
//
return [self.uploadPerformer couldReloadInfo] && [self.uploadPerformer reloadInfo];
}
- (void)startToUpload{
[super startToUpload];
//
self.uploadDataErrorResponseInfo = nil;
self.uploadDataErrorResponse = nil;
QNLogInfo(@"key:%@ serverInit", self.key);
// 1. upload
kQNWeakSelf;
[self serverInit:^(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
QNLogInfo(@"key:%@ uploadRestData", self.key);
// 2.
kQNWeakSelf;
[self uploadRestData:^{
kQNStrongSelf;
if (![self isAllUploaded]) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:self.uploadDataErrorResponseInfo]) {
[self complete:self.uploadDataErrorResponseInfo response:self.uploadDataErrorResponse];
}
return;
}
//
if ([self.uploadPerformer.uploadInfo getSourceSize] == 0) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
[self complete:responseInfo response:responseInfo.responseDictionary];
return;
}
QNLogInfo(@"key:%@ completeUpload errorResponseInfo:%@", self.key, self.uploadDataErrorResponseInfo);
// 3.
kQNWeakSelf;
[self completeUpload:^(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
[self complete:responseInfo response:response];
}];
}];
}];
}
- (void)uploadRestData:(dispatch_block_t)completeHandler {
QNLogInfo(@"key:%@ 串行分片", self.key);
[self performUploadRestData:completeHandler];
}
- (void)performUploadRestData:(dispatch_block_t)completeHandler {
if ([self isAllUploaded]) {
completeHandler();
return;
}
kQNWeakSelf;
[self uploadNextData:^(BOOL stop, QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (stop || !responseInfo.isOK) {
completeHandler();
} else {
[self performUploadRestData:completeHandler];
}
}];
}
//MARK:-- concurrent upload model API
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(responseInfo, response);
};
[self.uploadPerformer serverInit:completeHandlerP];
}
- (void)uploadNextData:(void(^)(BOOL stop, QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(BOOL, QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(BOOL stop, QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(stop, responseInfo, response);
};
[self.uploadPerformer uploadNextData:completeHandlerP];
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(responseInfo, response);
};
[self.uploadPerformer completeUpload:completeHandlerP];
}
- (void)complete:(QNResponseInfo *)info response:(NSDictionary *)response{
[self.uploadSource close];
if ([self shouldRemoveUploadInfoRecord:info]) {
[self.uploadPerformer removeUploadInfoRecord];
}
[super complete:info response:response];
[self reportBlock];
}
- (BOOL)shouldRemoveUploadInfoRecord:(QNResponseInfo *)info {
return info.isOK || info.statusCode == 612 || info.statusCode == 614 || info.statusCode == 701;
}
//MARK:-- block
- (void)reportBlock{
QNUploadRegionRequestMetrics *metrics = self.currentRegionRequestMetrics ?: [QNUploadRegionRequestMetrics emptyMetrics];
QNReportItem *item = [QNReportItem item];
[item setReportValue:QNReportLogTypeBlock forKey:QNReportBlockKeyLogType];
[item setReportValue:@([[NSDate date] timeIntervalSince1970]) forKey:QNReportBlockKeyUpTime];
[item setReportValue:self.token.bucket forKey:QNReportBlockKeyTargetBucket];
[item setReportValue:self.key forKey:QNReportBlockKeyTargetKey];
[item setReportValue:[self getTargetRegion].zoneInfo.regionId forKey:QNReportBlockKeyTargetRegionId];
[item setReportValue:[self getCurrentRegion].zoneInfo.regionId forKey:QNReportBlockKeyCurrentRegionId];
[item setReportValue:metrics.totalElapsedTime forKey:QNReportBlockKeyTotalElapsedTime];
[item setReportValue:metrics.bytesSend forKey:QNReportBlockKeyBytesSent];
[item setReportValue:self.uploadPerformer.recoveredFrom forKey:QNReportBlockKeyRecoveredFrom];
[item setReportValue:@([self.uploadSource getSize]) forKey:QNReportBlockKeyFileSize];
[item setReportValue:@([QNUtils getCurrentProcessID]) forKey:QNReportBlockKeyPid];
[item setReportValue:@([QNUtils getCurrentThreadID]) forKey:QNReportBlockKeyTid];
[item setReportValue:metrics.metricsList.lastObject.hijacked forKey:QNReportBlockKeyHijacking];
// region /
if (self.uploadDataErrorResponseInfo == nil && [self.uploadSource getSize] > 0 && [metrics totalElapsedTime] > 0) {
NSNumber *speed = [QNUtils calculateSpeed:[self.uploadSource getSize] totalTime:[metrics totalElapsedTime].longLongValue];
[item setReportValue:speed forKey:QNReportBlockKeyPerceptiveSpeed];
}
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
[item setReportValue:@(1) forKey:QNReportBlockKeyUpApiVersion];
} else {
[item setReportValue:@(2) forKey:QNReportBlockKeyUpApiVersion];
}
[item setReportValue:[QNUtils getCurrentNetworkType] forKey:QNReportBlockKeyClientTime];
[item setReportValue:[QNUtils systemName] forKey:QNReportBlockKeyOsName];
[item setReportValue:[QNUtils systemVersion] forKey:QNReportBlockKeyOsVersion];
[item setReportValue:[QNUtils sdkLanguage] forKey:QNReportBlockKeySDKName];
[item setReportValue:[QNUtils sdkVersion] forKey:QNReportBlockKeySDKVersion];
[kQNReporter reportItem:item token:self.token.token];
}
- (NSString *)upType {
if (self.config == nil) {
return nil;
}
NSString *sourceType = @"";
if ([self.uploadSource respondsToSelector:@selector(sourceType)]) {
sourceType = [self.uploadSource sourceType];
}
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
return [NSString stringWithFormat:@"%@<%@>",QNUploadUpTypeResumableV1, sourceType];
} else {
return [NSString stringWithFormat:@"%@<%@>",QNUploadUpTypeResumableV2, sourceType];
}
}
@end

View File

@@ -0,0 +1,87 @@
//
// QNPartsUploadPerformer.h
// QiniuSDK
//
// Created by yangsen on 2020/12/1.
// Copyright © 2020 Qiniu. All rights reserved.
//
/// 抽象类,不可以直接使用,需要使用子类
#import "QNFileDelegate.h"
#import "QNUploadSource.h"
#import "QNResponseInfo.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNUpToken.h"
NS_ASSUME_NONNULL_BEGIN
@protocol QNUploadRegion;
@class QNUploadInfo, QNRequestTransaction, QNUploadRegionRequestMetrics;
@interface QNPartsUploadPerformer : NSObject
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, copy, readonly) NSString *fileName;
@property (nonatomic, strong, readonly) id <QNUploadSource> uploadSource;
@property (nonatomic, strong, readonly) QNUpToken *token;
@property (nonatomic, strong, readonly) QNUploadOption *option;
@property (nonatomic, strong, readonly) QNConfiguration *config;
@property (nonatomic, strong, readonly) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy, readonly) NSString *recorderKey;
/// 断点续传时,起始上传偏移
@property(nonatomic, strong, readonly)NSNumber *recoveredFrom;
@property(nonatomic, strong, readonly)id <QNUploadRegion> currentRegion;
@property(nonatomic, strong, readonly)QNUploadInfo *uploadInfo;
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorderKey:(NSString *)recorderKey;
// 是否可以重新加载资源
- (BOOL)couldReloadInfo;
// 重新加载资源
- (BOOL)reloadInfo;
- (void)switchRegion:(id <QNUploadRegion>)region;
/// 通知回调当前进度
- (void)notifyProgress:(BOOL)isCompleted;
/// 分片信息保存本地
- (void)recordUploadInfo;
/// 分片信息从本地移除
- (void)removeUploadInfoRecord;
/// 根据字典构造分片信息 【子类实现】
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary * _Nonnull)fileInfoDictionary;
/// 根据配置构造分片信息 【子类实现】
- (QNUploadInfo *)getDefaultUploadInfo;
- (QNRequestTransaction *)createUploadRequestTransaction;
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction;
/// 上传前,服务端配置工作 【子类实现】
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
/// 上传文件分片 【子类实现】
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
/// 完成上传,服务端组织文件信息 【子类实现】
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,237 @@
//
// QNPartsUploadPerformer.m
// QiniuSDK
//
// Created by yangsen on 2020/12/1.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNAsyncRun.h"
#import "QNUpToken.h"
#import "QNZoneInfo.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNUploadInfo.h"
#import "QNUploadRegionInfo.h"
#import "QNRecorderDelegate.h"
#import "QNUploadDomainRegion.h"
#import "QNPartsUploadPerformer.h"
#import "QNUpProgress.h"
#import "QNRequestTransaction.h"
#define kQNRecordFileInfoKey @"recordFileInfo"
#define kQNRecordZoneInfoKey @"recordZoneInfo"
@interface QNPartsUploadPerformer()
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, strong) id <QNUploadSource> uploadSource;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy) NSString *recorderKey;
@property (nonatomic, strong) NSNumber *recoveredFrom;
@property (nonatomic, strong) id <QNUploadRegion> targetRegion;
@property (nonatomic, strong) id <QNUploadRegion> currentRegion;
@property (nonatomic, strong) QNUploadInfo *uploadInfo;
@property(nonatomic, strong) NSLock *progressLocker;
@property(nonatomic, strong) QNUpProgress *progress;
@property(nonatomic, strong) NSMutableArray <QNRequestTransaction *> *uploadTransactions;
@end
@implementation QNPartsUploadPerformer
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorderKey:(NSString *)recorderKey {
if (self = [super init]) {
_uploadSource = uploadSource;
_fileName = fileName;
_key = key;
_token = token;
_option = option;
_config = config;
_recorder = config.recorder;
_recorderKey = recorderKey;
[self initData];
}
return self;
}
- (void)initData {
self.uploadTransactions = [NSMutableArray array];
if (!self.uploadInfo) {
self.uploadInfo = [self getDefaultUploadInfo];
}
[self recoverUploadInfoFromRecord];
}
- (BOOL)couldReloadInfo {
return [self.uploadInfo couldReloadSource];
}
- (BOOL)reloadInfo {
self.recoveredFrom = nil;
[self.uploadInfo clearUploadState];
return [self.uploadInfo reloadSource];
}
- (void)switchRegion:(id <QNUploadRegion>)region {
self.currentRegion = region;
if (!self.targetRegion) {
self.targetRegion = region;
}
}
- (void)notifyProgress:(BOOL)isCompleted {
if (self.uploadInfo == nil) {
return;
}
if (isCompleted) {
[self.progress notifyDone:self.key totalBytes:[self.uploadInfo getSourceSize]];
} else {
[self.progress progress:self.key uploadBytes:[self.uploadInfo uploadSize] totalBytes:[self.uploadInfo getSourceSize]];
}
}
- (void)recordUploadInfo {
NSString *key = self.recorderKey;
if (self.recorder == nil || key == nil || key.length == 0) {
return;
}
@synchronized (self) {
NSDictionary *zoneInfo = [self.currentRegion zoneInfo].detailInfo;
NSDictionary *uploadInfo = [self.uploadInfo toDictionary];
if (zoneInfo && uploadInfo) {
NSDictionary *info = @{kQNRecordZoneInfoKey : zoneInfo,
kQNRecordFileInfoKey : uploadInfo};
NSData *data = [NSJSONSerialization dataWithJSONObject:info options:NSJSONWritingPrettyPrinted error:nil];
if (data) {
[self.recorder set:key data:data];
}
}
}
QNLogInfo(@"key:%@ recorderKey:%@ recordUploadInfo", self.key, self.recorderKey);
}
- (void)removeUploadInfoRecord {
self.recoveredFrom = nil;
[self.uploadInfo clearUploadState];
[self.recorder del:self.recorderKey];
QNLogInfo(@"key:%@ recorderKey:%@ removeUploadInfoRecord", self.key, self.recorderKey);
}
- (void)recoverUploadInfoFromRecord {
QNLogInfo(@"key:%@ recorderKey:%@ recorder:%@ recoverUploadInfoFromRecord", self.key, self.recorderKey, self.recorder);
NSString *key = self.recorderKey;
if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
return;
}
NSData *data = [self.recorder get:key];
if (data == nil) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord data:nil", self.key, self.recorderKey);
return;
}
NSError *error = nil;
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
if (error != nil || ![info isKindOfClass:[NSDictionary class]]) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord json error", self.key, self.recorderKey);
[self.recorder del:self.key];
return;
}
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoFromDictionary:info[kQNRecordZoneInfoKey]];
QNUploadInfo *recoverUploadInfo = [self getFileInfoWithDictionary:info[kQNRecordFileInfoKey]];
if (zoneInfo && self.uploadInfo && [recoverUploadInfo isValid]
&& [self.uploadInfo isSameUploadInfo:recoverUploadInfo]) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord valid", self.key, self.recorderKey);
[recoverUploadInfo checkInfoStateAndUpdate];
self.uploadInfo = recoverUploadInfo;
QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] init];
[region setupRegionData:zoneInfo];
self.currentRegion = region;
self.targetRegion = region;
self.recoveredFrom = @([recoverUploadInfo uploadSize]);
} else {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord invalid", self.key, self.recorderKey);
[self.recorder del:self.key];
self.currentRegion = nil;
self.targetRegion = nil;
self.recoveredFrom = nil;
}
}
- (QNRequestTransaction *)createUploadRequestTransaction {
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithConfig:self.config
uploadOption:self.option
targetRegion:self.targetRegion
currentRegion:self.currentRegion
key:self.key
token:self.token];
@synchronized (self) {
[self.uploadTransactions addObject:transaction];
}
return transaction;
}
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction {
if (transaction) {
@synchronized (self) {
[self.uploadTransactions removeObject:transaction];
}
}
}
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return nil;
}
- (QNUploadInfo *)getDefaultUploadInfo {
return nil;
}
- (void)serverInit:(void (^)(QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (void)uploadNextData:(void (^)(BOOL stop,
QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (void)completeUpload:(void (^)(QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (QNUpProgress *)progress {
[self.progressLocker lock];
if (_progress == nil) {
_progress = [QNUpProgress progress:self.option.progressHandler byteProgress:self.option.byteProgressHandler];
}
[self.progressLocker unlock];
return _progress;
}
@end

View File

@@ -0,0 +1,19 @@
//
// QNPartsUploadApiV1.h
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNPartsUploadPerformer.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNPartsUploadPerformerV1 : QNPartsUploadPerformer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,188 @@
//
// QNPartsUploadApiV1.m
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNDefine.h"
#import "QNRequestTransaction.h"
#import "QNUploadInfoV1.h"
#import "QNPartsUploadPerformerV1.h"
@interface QNPartsUploadPerformerV1()
@end
@implementation QNPartsUploadPerformerV1
+ (long long)blockSize{
return 4 * 1024 * 1024;
}
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return [QNUploadInfoV1 info:self.uploadSource dictionary:fileInfoDictionary];
}
- (QNUploadInfo *)getDefaultUploadInfo {
return [QNUploadInfoV1 info:self.uploadSource configuration:self.config];
}
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNResponseInfo *responseInfo = [QNResponseInfo successResponse];
completeHandler(responseInfo, nil, nil);
}
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV1 *uploadInfo = (QNUploadInfoV1 *)self.uploadInfo;
NSError *error;
QNUploadBlock *block = nil;
QNUploadData *chunk = nil;
@synchronized (self) {
block = [uploadInfo nextUploadBlock:&error];
chunk = [uploadInfo nextUploadData:block];
chunk.state = QNUploadStateUploading;
}
if (error) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:[NSString stringWithFormat:@"%@", error]];
completeHandler(YES, responseInfo, nil, nil);
return;
}
if (block == nil || chunk == nil) {
QNLogInfo(@"key:%@ no chunk left", self.key);
QNResponseInfo *responseInfo = nil;
if (uploadInfo.getSourceSize == 0) {
responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
} else {
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"no chunk left"];
}
completeHandler(YES, responseInfo, nil, nil);
return;
}
if (chunk.data == nil) {
QNLogInfo(@"key:%@ chunk data is nil", self.key);
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoOfZeroData:@"chunk data is nil"];;
completeHandler(YES, responseInfo, nil, nil);
return;
}
kQNWeakSelf;
void (^progress)(long long, long long) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
chunk.uploadSize = totalBytesWritten;
[self notifyProgress:false];
};
void (^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
NSString *blockContext = response[@"ctx"];
NSNumber *expiredAt = response[@"expired_at"];
if (responseInfo.isOK && blockContext && expiredAt) {
block.context = blockContext;
block.expiredAt = expiredAt;
chunk.state = QNUploadStateComplete;
[self recordUploadInfo];
[self notifyProgress:false];
} else {
chunk.state = QNUploadStateWaitToUpload;
}
completeHandler(NO, responseInfo, metrics, response);
};
if ([uploadInfo isFirstData:chunk]) {
QNLogInfo(@"key:%@ makeBlock", self.key);
[self makeBlock:block firstChunk:chunk chunkData:chunk.data progress:progress completeHandler:completeHandlerP];
} else {
QNLogInfo(@"key:%@ uploadChunk", self.key);
[self uploadChunk:block chunk:chunk chunkData:chunk.data progress:progress completeHandler:completeHandlerP];
}
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV1 *uploadInfo = (QNUploadInfoV1 *)self.uploadInfo;
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction makeFile:[uploadInfo getSourceSize]
fileName:self.fileName
blockContexts:[uploadInfo allBlocksContexts]
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
if (responseInfo.isOK) {
[self notifyProgress:true];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)makeBlock:(QNUploadBlock *)block
firstChunk:(QNUploadData *)chunk
chunkData:(NSData *)chunkData
progress:(void(^)(long long totalBytesWritten,
long long totalBytesExpectedToWrite))progress
completeHandler:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction makeBlock:block.offset
blockSize:block.size
firstChunkData:chunkData
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)uploadChunk:(QNUploadBlock *)block
chunk:(QNUploadData *)chunk
chunkData:(NSData *)chunkData
progress:(void(^)(long long totalBytesWritten,
long long totalBytesExpectedToWrite))progress
completeHandler:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction uploadChunk:block.context
blockOffset:block.offset
chunkData:chunkData
chunkOffset:chunk.offset
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
@end

View File

@@ -0,0 +1,17 @@
//
// QNPartsUploadApiV2.h
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNPartsUploadPerformer.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNPartsUploadPerformerV2 : QNPartsUploadPerformer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,148 @@
//
// QNPartsUploadApiV2.m
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNDefine.h"
#import "QNRequestTransaction.h"
#import "QNUploadInfoV2.h"
#import "QNPartsUploadPerformerV2.h"
@interface QNPartsUploadPerformerV2()
@end
@implementation QNPartsUploadPerformerV2
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return [QNUploadInfoV2 info:self.uploadSource dictionary:fileInfoDictionary];
}
- (QNUploadInfo *)getDefaultUploadInfo {
return [QNUploadInfoV2 info:self.uploadSource configuration:self.config];
}
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
if (uploadInfo && [uploadInfo isValid]) {
QNLogInfo(@"key:%@ serverInit success", self.key);
QNResponseInfo *responseInfo = [QNResponseInfo successResponse];
completeHandler(responseInfo, nil, nil);
return;
}
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction initPart:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
NSString *uploadId = response[@"uploadId"];
NSNumber *expireAt = response[@"expireAt"];
if (responseInfo.isOK && uploadId && expireAt) {
uploadInfo.uploadId = uploadId;
uploadInfo.expireAt = expireAt;
[self recordUploadInfo];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
NSError *error = nil;
QNUploadData *data = nil;
@synchronized (self) {
data = [uploadInfo nextUploadData:&error];
data.state = QNUploadStateUploading;
}
if (error) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:[NSString stringWithFormat:@"%@", error]];
completeHandler(YES, responseInfo, nil, nil);
return;
}
//
if (data == nil) {
QNLogInfo(@"key:%@ no data left", self.key);
QNResponseInfo *responseInfo = nil;
if (uploadInfo.getSourceSize == 0) {
responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
} else {
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"no chunk left"];
}
completeHandler(YES, responseInfo, nil, nil);
return;
}
kQNWeakSelf;
void (^progress)(long long, long long) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
data.uploadSize = totalBytesWritten;
[self notifyProgress:false];
};
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakObj(transaction);
[transaction uploadPart:uploadInfo.uploadId
partIndex:[uploadInfo getPartIndexOfData:data]
partData:data.data
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
NSString *etag = response[@"etag"];
NSString *md5 = response[@"md5"];
if (responseInfo.isOK && etag && md5) {
data.etag = etag;
data.state = QNUploadStateComplete;
[self recordUploadInfo];
[self notifyProgress:false];
} else {
data.state = QNUploadStateWaitToUpload;
}
completeHandler(NO, responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
NSArray *partInfoArray = [uploadInfo getPartInfoArray];
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction completeParts:self.fileName uploadId:uploadInfo.uploadId partInfoArray:partInfoArray complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
if (responseInfo.isOK) {
[self notifyProgress:true];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
@end

View File

@@ -0,0 +1,23 @@
//
// QNUpProgress.h
// QiniuSDK
//
// Created by yangsen on 2021/5/21.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadOption.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUpProgress : NSObject
+ (instancetype)progress:(QNUpProgressHandler)progress byteProgress:(QNUpByteProgressHandler)byteProgress;
- (void)progress:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes;
- (void)notifyDone:(NSString *)key totalBytes:(long long)totalBytes;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,87 @@
//
// QNUpProgress.m
// QiniuSDK
//
// Created by yangsen on 2021/5/21.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import "QNUpProgress.h"
@interface QNUpProgress()
@property(nonatomic, assign)long long maxProgressUploadBytes;
@property(nonatomic, assign)long long previousUploadBytes;
@property(nonatomic, copy)QNUpProgressHandler progress;
@property(nonatomic, copy)QNUpByteProgressHandler byteProgress;
@end
@implementation QNUpProgress
+ (instancetype)progress:(QNUpProgressHandler)progress byteProgress:(QNUpByteProgressHandler)byteProgress {
QNUpProgress *upProgress = [[QNUpProgress alloc] init];
upProgress.maxProgressUploadBytes = -1;
upProgress.previousUploadBytes = 0;
upProgress.progress = progress;
upProgress.byteProgress = byteProgress;
return upProgress;
}
- (void)progress:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes {
if ((self.progress == nil && self.byteProgress == nil) || uploadBytes < 0 || (totalBytes > 0 && uploadBytes > totalBytes)) {
return;
}
if (totalBytes > 0) {
@synchronized (self) {
if (self.maxProgressUploadBytes < 0) {
self.maxProgressUploadBytes = totalBytes * 0.95;
}
}
if (uploadBytes > self.maxProgressUploadBytes) {
return;
}
}
@synchronized (self) {
if (uploadBytes > self.previousUploadBytes) {
self.previousUploadBytes = uploadBytes;
} else {
return;
}
}
[self notify:key uploadBytes:uploadBytes totalBytes:totalBytes];
}
- (void)notifyDone:(NSString *)key totalBytes:(long long)totalBytes {
[self notify:key uploadBytes:totalBytes totalBytes:totalBytes];
}
- (void)notify:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes {
if (self.progress == nil && self.byteProgress == nil) {
return;
}
if (self.byteProgress) {
QNAsyncRunInMain(^{
self.byteProgress(key, uploadBytes, totalBytes);
});
return;
}
if (totalBytes <= 0) {
return;
}
if (self.progress) {
QNAsyncRunInMain(^{
double notifyPercent = (double) uploadBytes / (double) totalBytes;
self.progress(key, notifyPercent);
});
}
}
@end

33
Pods/Qiniu/QiniuSDK/Storage/QNUpToken.h generated Normal file
View File

@@ -0,0 +1,33 @@
//
// QNUpToken.h
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface QNUpToken : NSObject
+ (instancetype)parse:(NSString *)token;
@property (assign, nonatomic, readonly) long deadline;
@property (copy , nonatomic, readonly) NSString *access;
@property (copy , nonatomic, readonly) NSString *bucket;
@property (copy , nonatomic, readonly) NSString *token;
@property (readonly) BOOL isValid;
@property (readonly) BOOL hasReturnUrl;
+ (instancetype)getInvalidToken;
- (NSString *)index;
/// 是否在未来 duration 分钟内有效
- (BOOL)isValidForDuration:(long)duration;
/// 在是否在 date 之前有效
- (BOOL)isValidBeforeDate:(NSDate *)date;
@end

103
Pods/Qiniu/QiniuSDK/Storage/QNUpToken.m generated Normal file
View File

@@ -0,0 +1,103 @@
//
// QNUpToken.m
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNUrlSafeBase64.h"
#import "QNUpToken.h"
#define kQNPolicyKeyScope @"scope"
#define kQNPolicyKeyDeadline @"deadline"
#define kQNPolicyKeyReturnUrl @"returnUrl"
@interface QNUpToken ()
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token;
@end
@implementation QNUpToken
+ (instancetype)getInvalidToken {
QNUpToken *token = [[QNUpToken alloc] init];
token->_deadline = -1;
return token;
}
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token {
if (self = [super init]) {
_token = token;
_access = [self getAccess];
_bucket = [self getBucket:policy];
_deadline = [policy[kQNPolicyKeyDeadline] longValue];
_hasReturnUrl = (policy[kQNPolicyKeyReturnUrl] != nil);
}
return self;
}
- (NSString *)getAccess {
NSRange range = [_token rangeOfString:@":" options:NSCaseInsensitiveSearch];
return [_token substringToIndex:range.location];
}
- (NSString *)getBucket:(NSDictionary *)info {
NSString *scope = [info objectForKey:kQNPolicyKeyScope];
if (!scope || [scope isKindOfClass:[NSNull class]]) {
return @"";
}
NSRange range = [scope rangeOfString:@":"];
if (range.location == NSNotFound) {
return scope;
}
return [scope substringToIndex:range.location];
}
+ (instancetype)parse:(NSString *)token {
if (token == nil) {
return nil;
}
NSArray *array = [token componentsSeparatedByString:@":"];
if (array == nil || array.count != 3) {
return nil;
}
NSData *data = [QNUrlSafeBase64 decodeString:array[2]];
if (!data) {
return nil;
}
NSError *tmp = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&tmp];
if (tmp != nil || dict[kQNPolicyKeyScope] == nil || dict[kQNPolicyKeyDeadline] == nil) {
return nil;
}
return [[QNUpToken alloc] init:dict token:token];
}
- (NSString *)index {
return [NSString stringWithFormat:@"%@:%@", _access, _bucket];
}
- (BOOL)isValid {
return _access && _access.length > 0 && _bucket && _bucket.length > 0;
}
- (BOOL)isValidForDuration:(long)duration {
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:duration];
return [self isValidBeforeDate:date];
}
- (BOOL)isValidBeforeDate:(NSDate *)date {
if (date == nil) {
return NO;
}
return [date timeIntervalSince1970] < self.deadline;
}
@end

View File

@@ -0,0 +1,57 @@
//
// QNUploadBlock.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadData.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadBlock : NSObject
// block下标
@property(nonatomic, assign, readonly)NSInteger index;
// 当前data偏移量
@property(nonatomic, assign, readonly)long long offset;
// 块大小
@property(nonatomic, assign, readonly)NSInteger size;
// 需要上传的片数据
@property(nonatomic, strong, readonly)NSArray <QNUploadData *> *uploadDataList;
// block上传上下文信息
@property(nonatomic, copy, nullable)NSString *context;
// block md5
@property(nonatomic, copy, nullable)NSString *md5;
// 是否已完成上传【不进行离线缓存】
@property(nonatomic, assign, readonly)BOOL isCompleted;
// 上传大小 【不进行离线缓存】
@property(nonatomic, assign, readonly)NSInteger uploadSize;
// ctx 过期时间
@property(nonatomic, strong, nullable)NSNumber *expiredAt;
//MARK:-- 构造
+ (instancetype)blockFromDictionary:(NSDictionary *)dictionary;
- (instancetype)initWithOffset:(long long)offset
blockSize:(NSInteger)blockSize
dataSize:(NSInteger)dataSize
index:(NSInteger)index;
// 检测 block 是否有效
- (BOOL)isValid;
/// 获取下一个需要上传的块
- (QNUploadData *)nextUploadDataWithoutCheckData;
/// 检测 data 状态,处理出于上传状态的无 data 数据的情况,无 data 数据则状态调整为监测数据状态
- (void)checkInfoStateAndUpdate;
/// 清理上传状态
- (void)clearUploadState;
/// 转化字典
- (NSDictionary *)toDictionary;
@end
NS_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More