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

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

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