Files
waf-platform/HttpDNSSDK/sdk/ios/NewHttpDNS/Network/HttpdnsNWReusableConnection.m
2026-03-05 02:44:43 +08:00

548 lines
21 KiB
Objective-C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#import "HttpdnsNWReusableConnection.h"
#import "HttpdnsNWHTTPClient_Internal.h"
#import <Network/Network.h>
#import <Security/SecCertificate.h>
#import <Security/SecPolicy.h>
#import <Security/SecTrust.h>
#import "HttpdnsInternalConstant.h"
#import "HttpdnsLog_Internal.h"
#import "HttpdnsPublicConstant.h"
#import "HttpdnsUtil.h"
@class HttpdnsNWHTTPClient;
// å<>ªåœ¨æ­¤å®žçްæ‡ä»¶å†…å<E280A6>¯è§<C3A8>的交æ<C2A4>¢å¯¹è±¡ï¼Œæ‰¿è½½ä¸€æ¬¡è¯·æ±?å“<C3A5>应数æ<C2B0>®ä¸Žçжæ€?
@interface HttpdnsNWHTTPExchange : NSObject
@property (nonatomic, strong, readonly) NSMutableData *buffer;
@property (nonatomic, strong, readonly) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) BOOL finished;
@property (nonatomic, assign) BOOL remoteClosed;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, assign) NSUInteger headerEndIndex;
@property (nonatomic, assign) BOOL headerParsed;
@property (nonatomic, assign) BOOL chunked;
@property (nonatomic, assign) long long contentLength;
@property (nonatomic, assign) NSInteger statusCode;
@property (nonatomic, strong) dispatch_block_t timeoutBlock;
- (instancetype)init;
@end
@implementation HttpdnsNWHTTPExchange
- (instancetype)init {
self = [super init];
if (self) {
_buffer = [NSMutableData data];
_semaphore = dispatch_semaphore_create(0);
_headerEndIndex = NSNotFound;
_contentLength = -1;
}
return self;
}
@end
@interface HttpdnsNWReusableConnection ()
@property (nonatomic, weak, readonly) HttpdnsNWHTTPClient *client;
@property (nonatomic, copy, readonly) NSString *host;
@property (nonatomic, copy, readonly) NSString *port;
@property (nonatomic, assign, readonly) BOOL useTLS;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) nw_connection_t connectionHandle;
#else
@property (nonatomic, assign) nw_connection_t connectionHandle;
#endif
@property (nonatomic, strong) dispatch_semaphore_t stateSemaphore;
@property (nonatomic, assign) nw_connection_state_t state;
@property (nonatomic, strong) NSError *stateError;
@property (nonatomic, assign) BOOL started;
@property (nonatomic, strong) HttpdnsNWHTTPExchange *currentExchange;
@property (nonatomic, assign, readwrite, getter=isInvalidated) BOOL invalidated;
@end
@implementation HttpdnsNWReusableConnection
- (void)dealloc {
if (_connectionHandle) {
nw_connection_set_state_changed_handler(_connectionHandle, NULL);
nw_connection_cancel(_connectionHandle);
#if !OS_OBJECT_USE_OBJC
nw_release(_connectionHandle);
#endif
_connectionHandle = NULL;
}
}
- (instancetype)initWithClient:(HttpdnsNWHTTPClient *)client
host:(NSString *)host
port:(NSString *)port
useTLS:(BOOL)useTLS {
NSParameterAssert(client);
NSParameterAssert(host);
NSParameterAssert(port);
self = [super init];
if (!self) {
return nil;
}
_client = client;
_host = [host copy];
_port = [port copy];
_useTLS = useTLS;
_queue = dispatch_queue_create("com.New.sdk.httpdns.network.connection.reuse", DISPATCH_QUEUE_SERIAL);
_stateSemaphore = dispatch_semaphore_create(0);
_state = nw_connection_state_invalid;
_lastUsedDate = [NSDate date];
nw_endpoint_t endpoint = nw_endpoint_create_host(_host.UTF8String, _port.UTF8String);
if (!endpoint) {
return nil;
}
__weak typeof(self) weakSelf = self;
nw_parameters_t parameters = NULL;
if (useTLS) {
parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tlsOptions) {
if (!tlsOptions) {
return;
}
sec_protocol_options_t secOptions = nw_tls_copy_sec_protocol_options(tlsOptions);
if (!secOptions) {
return;
}
if (![HttpdnsUtil isIPv4Address:host] && ![HttpdnsUtil isIPv6Address:host]) {
sec_protocol_options_set_tls_server_name(secOptions, host.UTF8String);
}
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
if (@available(iOS 13.0, *)) {
sec_protocol_options_add_tls_application_protocol(secOptions, "http/1.1");
}
#endif
__strong typeof(weakSelf) strongSelf = weakSelf;
sec_protocol_options_set_verify_block(secOptions, ^(sec_protocol_metadata_t metadata, sec_trust_t secTrust, sec_protocol_verify_complete_t complete) {
__strong typeof(weakSelf) strongSelf = weakSelf;
BOOL isValid = NO;
if (secTrust && strongSelf) {
SecTrustRef trustRef = sec_trust_copy_ref(secTrust);
if (trustRef) {
NSString *validIP = NEW_HTTPDNS_VALID_SERVER_CERTIFICATE_IP;
isValid = [strongSelf.client evaluateServerTrust:trustRef forDomain:validIP];
if (!isValid && [HttpdnsUtil isNotEmptyString:strongSelf.host]) {
isValid = [strongSelf.client evaluateServerTrust:trustRef forDomain:strongSelf.host];
}
if (!isValid && !strongSelf.stateError) {
strongSelf.stateError = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"TLS trust validation failed"}];
}
CFRelease(trustRef);
}
}
complete(isValid);
}, strongSelf.queue);
}, ^(nw_protocol_options_t tcpOptions) {
nw_tcp_options_set_no_delay(tcpOptions, true);
});
} else {
parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, ^(nw_protocol_options_t tcpOptions) {
nw_tcp_options_set_no_delay(tcpOptions, true);
});
}
if (!parameters) {
#if !OS_OBJECT_USE_OBJC
nw_release(endpoint);
#endif
return nil;
}
nw_connection_t connection = nw_connection_create(endpoint, parameters);
#if !OS_OBJECT_USE_OBJC
nw_release(endpoint);
nw_release(parameters);
#endif
if (!connection) {
return nil;
}
_connectionHandle = connection;
nw_connection_set_queue(_connectionHandle, _queue);
nw_connection_set_state_changed_handler(_connectionHandle, ^(nw_connection_state_t state, nw_error_t stateError) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf handleStateChange:state error:stateError];
});
return self;
}
- (void)handleStateChange:(nw_connection_state_t)state error:(nw_error_t)error {
_state = state;
if (error) {
_stateError = [HttpdnsNWHTTPClient errorFromNWError:error description:@"Connection state error"];
}
if (state == nw_connection_state_ready) {
dispatch_semaphore_signal(_stateSemaphore);
return;
}
if (state == nw_connection_state_failed || state == nw_connection_state_cancelled) {
self.invalidated = YES;
if (!_stateError && error) {
_stateError = [HttpdnsNWHTTPClient errorFromNWError:error description:@"Connection failed"];
}
dispatch_semaphore_signal(_stateSemaphore);
HttpdnsNWHTTPExchange *exchange = self.currentExchange;
if (exchange && !exchange.finished) {
if (!exchange.error) {
exchange.error = _stateError ?: [HttpdnsNWHTTPClient errorFromNWError:error description:@"Connection failed"];
}
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
}
}
}
- (BOOL)openWithTimeout:(NSTimeInterval)timeout error:(NSError **)error {
if (self.invalidated) {
if (error) {
*error = _stateError ?: [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection invalid"}];
}
return NO;
}
if (!_started) {
_started = YES;
nw_connection_start(_connectionHandle);
}
dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
long waitResult = dispatch_semaphore_wait(_stateSemaphore, deadline);
if (waitResult != 0) {
self.invalidated = YES;
if (error) {
*error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection setup timed out"}];
}
nw_connection_cancel(_connectionHandle);
return NO;
}
if (_state == nw_connection_state_ready) {
return YES;
}
if (error) {
*error = _stateError ?: [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection failed to become ready"}];
}
return NO;
}
- (BOOL)isViable {
return !self.invalidated && _state == nw_connection_state_ready;
}
- (void)invalidate {
if (self.invalidated) {
return;
}
self.invalidated = YES;
if (_connectionHandle) {
nw_connection_cancel(_connectionHandle);
}
}
- (nullable NSData *)sendRequestData:(NSData *)requestData
timeout:(NSTimeInterval)timeout
remoteConnectionClosed:(BOOL *)remoteConnectionClosed
error:(NSError **)error {
if (!requestData || requestData.length == 0) {
if (error) {
*error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Empty HTTP request"}];
}
return nil;
}
if (![self isViable] || self.currentExchange) {
if (error) {
*error = _stateError ?: [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection not ready"}];
}
return nil;
}
HttpdnsNWHTTPExchange *exchange = [HttpdnsNWHTTPExchange new];
__weak typeof(self) weakSelf = self;
dispatch_sync(_queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
exchange.error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection released unexpectedly"}];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
return;
}
if (strongSelf.invalidated || strongSelf.currentExchange) {
exchange.error = strongSelf.stateError ?: [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection is busy"}];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
return;
}
strongSelf.currentExchange = exchange;
dispatch_data_t payload = dispatch_data_create(requestData.bytes, requestData.length, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
dispatch_block_t timeoutBlock = dispatch_block_create(0, ^{
if (exchange.finished) {
return;
}
exchange.error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Request timed out"}];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
nw_connection_cancel(strongSelf.connectionHandle);
});
exchange.timeoutBlock = timeoutBlock;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), strongSelf.queue, timeoutBlock);
nw_connection_send(strongSelf.connectionHandle, payload, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t sendError) {
__strong typeof(strongSelf) innerSelf = strongSelf;
if (!innerSelf) {
return;
}
if (sendError) {
dispatch_async(innerSelf.queue, ^{
if (!exchange.finished) {
exchange.error = [HttpdnsNWHTTPClient errorFromNWError:sendError description:@"Send failed"];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
nw_connection_cancel(innerSelf.connectionHandle);
}
});
return;
}
[innerSelf startReceiveLoopForExchange:exchange];
});
});
dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
long waitResult = dispatch_semaphore_wait(exchange.semaphore, deadline);
dispatch_sync(_queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (exchange.timeoutBlock) {
dispatch_block_cancel(exchange.timeoutBlock);
exchange.timeoutBlock = nil;
}
if (strongSelf && strongSelf.currentExchange == exchange) {
strongSelf.currentExchange = nil;
}
});
if (waitResult != 0) {
if (!exchange.error) {
exchange.error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Request wait timed out"}];
}
[self invalidate];
if (error) {
*error = exchange.error;
}
return nil;
}
if (exchange.error) {
[self invalidate];
if (error) {
*error = exchange.error;
}
return nil;
}
if (remoteConnectionClosed) {
*remoteConnectionClosed = exchange.remoteClosed;
}
self.lastUsedDate = [NSDate date];
return [exchange.buffer copy];
}
- (void)startReceiveLoopForExchange:(HttpdnsNWHTTPExchange *)exchange {
__weak typeof(self) weakSelf = self;
__block void (^receiveBlock)(dispatch_data_t, nw_content_context_t, bool, nw_error_t);
__block __weak void (^weakReceiveBlock)(dispatch_data_t, nw_content_context_t, bool, nw_error_t);
receiveBlock = ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t receiveError) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (exchange.finished) {
return;
}
if (receiveError) {
exchange.error = [HttpdnsNWHTTPClient errorFromNWError:receiveError description:@"Receive failed"];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
return;
}
if (content) {
dispatch_data_apply(content, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
if (buffer && size > 0) {
[exchange.buffer appendBytes:buffer length:size];
}
return true;
});
}
[strongSelf evaluateExchangeCompletion:exchange isRemoteComplete:is_complete];
if (exchange.finished) {
dispatch_semaphore_signal(exchange.semaphore);
return;
}
if (is_complete) {
exchange.remoteClosed = YES;
if (!exchange.finished) {
exchange.error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN
code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE
userInfo:@{NSLocalizedDescriptionKey: @"Connection closed before response completed"}];
exchange.finished = YES;
dispatch_semaphore_signal(exchange.semaphore);
}
return;
}
void (^callback)(dispatch_data_t, nw_content_context_t, bool, nw_error_t) = weakReceiveBlock;
if (callback && !exchange.finished) {
nw_connection_receive(strongSelf.connectionHandle, 1, UINT32_MAX, callback);
}
};
weakReceiveBlock = receiveBlock;
nw_connection_receive(_connectionHandle, 1, UINT32_MAX, receiveBlock);
}
- (void)evaluateExchangeCompletion:(HttpdnsNWHTTPExchange *)exchange isRemoteComplete:(bool)isComplete {
if (exchange.finished) {
return;
}
if (isComplete) {
// 远端已ç»<C3A7>å<EFBFBD>é€<C3A9>完并关闭,需è¦<C3A8>æ<EFBFBD><C3A6>å‰<C3A5>标记,é<C592>¿å…<C3A5>æ<EFBFBD><C3A6>å‰<C3A5>è¿”åžæ—¶æ¼<C3A6>记连接状æ€?
exchange.remoteClosed = YES;
}
if (!exchange.headerParsed) {
NSUInteger headerEnd = NSNotFound;
NSInteger statusCode = 0;
NSDictionary<NSString *, NSString *> *headers = nil;
NSError *headerError = nil;
HttpdnsHTTPHeaderParseResult headerResult = [self.client tryParseHTTPHeadersInData:exchange.buffer
headerEndIndex:&headerEnd
statusCode:&statusCode
headers:&headers
error:&headerError];
if (headerResult == HttpdnsHTTPHeaderParseResultError) {
exchange.error = headerError;
exchange.finished = YES;
return;
}
if (headerResult == HttpdnsHTTPHeaderParseResultIncomplete) {
return;
}
exchange.headerParsed = YES;
exchange.headerEndIndex = headerEnd;
exchange.statusCode = statusCode;
NSString *contentLengthValue = headers[@"content-length"];
if ([HttpdnsUtil isNotEmptyString:contentLengthValue]) {
exchange.contentLength = [contentLengthValue longLongValue];
}
NSString *transferEncodingValue = headers[@"transfer-encoding"];
if ([HttpdnsUtil isNotEmptyString:transferEncodingValue] && [transferEncodingValue rangeOfString:@"chunked" options:NSCaseInsensitiveSearch].location != NSNotFound) {
exchange.chunked = YES;
}
}
if (!exchange.headerParsed) {
return;
}
NSUInteger bodyOffset = exchange.headerEndIndex == NSNotFound ? 0 : exchange.headerEndIndex + 4;
NSUInteger currentBodyLength = exchange.buffer.length > bodyOffset ? exchange.buffer.length - bodyOffset : 0;
if (exchange.chunked) {
NSError *chunkError = nil;
HttpdnsHTTPChunkParseResult chunkResult = [self.client checkChunkedBodyCompletionInData:exchange.buffer
headerEndIndex:exchange.headerEndIndex
error:&chunkError];
if (chunkResult == HttpdnsHTTPChunkParseResultError) {
exchange.error = chunkError;
exchange.finished = YES;
return;
}
if (chunkResult == HttpdnsHTTPChunkParseResultSuccess) {
exchange.finished = YES;
return;
}
return;
}
if (exchange.contentLength >= 0) {
if ((long long)currentBodyLength >= exchange.contentLength) {
exchange.finished = YES;
}
return;
}
if (isComplete) {
exchange.remoteClosed = YES;
exchange.finished = YES;
}
}
@end
#if DEBUG
// æµè¯•专用:连接状æ€<C3A6>æ“<C3A6>作实çŽ?
@implementation HttpdnsNWReusableConnection (DebugInspection)
- (void)debugSetLastUsedDate:(nullable NSDate *)date {
self.lastUsedDate = date;
}
- (void)debugSetInUse:(BOOL)inUse {
self.inUse = inUse;
}
- (void)debugInvalidate {
[self invalidate];
}
@end
#endif