Files
waf-platform/HttpDNSSDK/sdk/ios/NewHttpDNSTests/Network/HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests.m

741 lines
37 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.

//
// HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests.m
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 trustapp.com. All rights reserved.
//
// 边界æ<C592>¡ä»¶ä¸Žè¶…æ—¶æµè¯?- 包å<E280A6>«è¾¹ç•Œæ<C592>¡ä»¶ (M)ã€<C3A3>超时交äº?(P) å’?Connection 头部 (R) 测试ç»?
// 测试总数�5 个(M:4 + P:6 + R:5�
//
#import "HttpdnsNWHTTPClientTestBase.h"
@interface HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests : HttpdnsNWHTTPClientTestBase
@end
@implementation HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests
#pragma mark - M. 边界æ<C592>¡ä»¶ä¸ŽéªŒè¯<C3A8>æµè¯?
// M.1 连接å¤<C3A5>用边界:端å<C2AF>£å†…å¤<C3A5>用,端å<C2AF>£é—´éš”离
- (void)testEdgeCase_ConnectionReuseWithinPortOnly_NotAcross {
XCTestExpectation *expectation = [self expectationWithDescription:@"Reuse boundaries"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 请求 A 到端å<C2AF>?11443
CFAbsoluteTime timeA = CFAbsoluteTimeGetCurrent();
NSError *errorA = nil;
HttpdnsNWHTTPClientResponse *responseA = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"RequestA"
timeout:15.0
error:&errorA];
CFAbsoluteTime elapsedA = CFAbsoluteTimeGetCurrent() - timeA;
XCTAssertNotNil(responseA);
// 请求 B 到端å<C2AF>?11443(应该å¤<C3A5>用连接,å<C592>¯èƒ½æ´å¿«ï¼?
CFAbsoluteTime timeB = CFAbsoluteTimeGetCurrent();
NSError *errorB = nil;
HttpdnsNWHTTPClientResponse *responseB = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"RequestB"
timeout:15.0
error:&errorB];
CFAbsoluteTime elapsedB = CFAbsoluteTimeGetCurrent() - timeB;
XCTAssertNotNil(responseB);
// 请求 C 到端å<C2AF>?11444(应该åˆå»ºæ°è¿žæŽ¥ï¼?
CFAbsoluteTime timeC = CFAbsoluteTimeGetCurrent();
NSError *errorC = nil;
HttpdnsNWHTTPClientResponse *responseC = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"RequestC"
timeout:15.0
error:&errorC];
CFAbsoluteTime elapsedC = CFAbsoluteTimeGetCurrent() - timeC;
XCTAssertNotNil(responseC);
// 请求 D 到端å<C2AF>?11444(应该å¤<C3A5>用端å<C2AF>?11444 的连接)
CFAbsoluteTime timeD = CFAbsoluteTimeGetCurrent();
NSError *errorD = nil;
HttpdnsNWHTTPClientResponse *responseD = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"RequestD"
timeout:15.0
error:&errorD];
CFAbsoluteTime elapsedD = CFAbsoluteTimeGetCurrent() - timeD;
XCTAssertNotNil(responseD);
// 验è¯<C3A8>所有请æ±éƒ½æˆ<C3A6>功
XCTAssertEqual(responseA.statusCode, 200);
XCTAssertEqual(responseB.statusCode, 200);
XCTAssertEqual(responseC.statusCode, 200);
XCTAssertEqual(responseD.statusCode, 200);
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:70.0];
}
// M.2 高端å<C2AF>£æ•°é‡<C3A9>åŽåŠæµè¯?
- (void)testEdgeCase_HighPortCount_AllPortsManaged {
XCTestExpectation *expectation = [self expectationWithDescription:@"High port count"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
// 第一轮:å<C5A1>所有端å<C2AF>£å<C2A3>„å<E2809E>起一个请æ±?
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"Round1"
timeout:15.0
error:&error];
XCTAssertNotNil(response, @"First round request to port %@ should succeed", port);
}
// 第二轮:å†<C3A5>次å<C2A1>所有端å<C2AF>£å<C2A3>起请æ±ï¼ˆåº”该å¤<C3A5>用连接ï¼?
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"Round2"
timeout:15.0
error:&error];
XCTAssertNotNil(response, @"Second round request to port %@ should reuse connection", port);
}
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:120.0];
}
// M.3 å¹¶å<C2B6>池访问安全æ€?
- (void)testEdgeCase_ConcurrentPoolAccess_NoDataRace {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
NSInteger requestsPerPort = 5;
// å<>三个端å<C2AF>£å¹¶å<C2B6>å<E28098>起请æ±?
for (NSNumber *port in ports) {
for (NSInteger i = 0; i < requestsPerPort; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Req %ld", port, (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentAccess"
timeout:15.0
error:&error];
// 妿žœæ²¡æœ‰å´©æºƒæˆæ­è¨€å¤±è´¥ï¼Œè¯´æ˜Žå¹¶å<C2B6>访问安å…?
XCTAssertTrue(response != nil || error != nil);
[expectation fulfill];
});
}
}
[self waitForExpectations:expectations timeout:50.0];
}
// M.4 端å<C2AF>£è¿<C3A8>移模å¼<C3A5>
- (void)testEdgeCase_PortMigration_OldConnectionsEventuallyExpire {
if (getenv("SKIP_SLOW_TESTS")) {
return;
}
XCTestExpectation *expectation = [self expectationWithDescription:@"Port migration"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 阶段 1:å<C5A1>端å<C2AF>£ 11443 å<>起多个请æ±
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
timeout:15.0
error:&error];
}
// 阶段 2:切æ<E280A1>¢åˆ°ç«¯å<C2AF>£ 11444,å<C592>起多个请æ±?
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444"
timeout:15.0
error:&error];
}
// 等待超过 30 ç§ï¼Œè®©ç«¯å<C2AF>?11443 的连接过æœ?
[NSThread sleepForTimeInterval:31.0];
// 阶段 3:验è¯<C3A8>端å<C2AF>?11444 ä»<C3A4>ç„¶å<C2B6>¯ç”¨
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444After"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Port 11444 should still work after 11443 expired");
// 阶段 4:端å<C2AF>?11443 应该创建新连接(旧连接已过期ï¼?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443New"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Port 11443 should work with new connection after expiry");
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:120.0];
}
#pragma mark - P. 超时与连接池交互测试
// P.1 å<>•次超时å<C2B6>Žè¿žæŽ¥è¢«æ­£ç¡®ç§»é™¤
- (void)testTimeout_SingleRequest_ConnectionRemovedFromPool {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 验è¯<C3A8>åˆ<C3A5>å§çжæ€?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty initially");
// å<>起超时请æ±ï¼ˆdelay 10s, timeout 1sï¼?
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
timeout:1.0
error:&error];
// 验è¯<C3A8>请æ±å¤±è´¥
XCTAssertNil(response, @"Response should be nil on timeout");
XCTAssertNotNil(error, @"Error should be set on timeout");
// 等待异步 returnConnection 完æˆ<C3A6>
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>池状æ€<C3A6>:超时连接应该被移é™?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Timed-out connection should be removed from pool");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"No connections should remain in pool");
// 验è¯<C3A8>统计
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should have created 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for timed-out connection");
}
// P.2 è¶…æ—¶å<C2B6>Žè¿žæŽ¥æ± æ<C2A0>¢å¤<C3A5>能åŠ
- (void)testTimeout_PoolRecovery_SubsequentRequestSucceeds {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 第一个请求:超时
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
timeout:1.0
error:&error1];
XCTAssertNil(response1, @"First request should timeout");
XCTAssertNotNil(error1);
// 等待清ç<E280A6>†å®Œæˆ<C3A6>
[NSThread sleepForTimeInterval:0.5];
// 第二个请æ±ï¼šæ­£å¸¸ï¼ˆéªŒè¯<C3A8>æ± å·²æ<C2B2>¢å¤<C3A5>)
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Second request should succeed after timeout");
XCTAssertEqual(response2.statusCode, 200);
// 等待 returnConnection
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>æ± æ<C2A0>¢å¤<C3A5>:现在应该æœ?1 个连接(æ<CB86>¥è‡ªç¬¬äºŒä¸ªè¯·æ±ï¼‰
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from second request");
// 第三个请æ±ï¼šåº”该å¤<C3A5>用第二个请æ±çš„连接
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ReuseTest"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3);
XCTAssertEqual(response3.statusCode, 200);
// 验è¯<C3A8>统计ï¼? 个超时(åˆå»ºå<C2BA>Žç§»é™¤ï¼‰+ 1 个æˆ<C3A6>功(åˆå»ºï¼? 1 个å¤<C3A5>ç”?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (1 timed out, 1 succeeded)");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Third request should reuse second's connection");
}
// P.3 å¹¶å<C2B6>场景:部分超时ä¸<C3A4>å½±å“<C3A5>æˆ<C3A6>功请æ±çš„连接å¤<C3A5>ç”?
- (void)testTimeout_ConcurrentPartialTimeout_SuccessfulRequestsReuse {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *successCountLock = [[NSLock alloc] init];
NSLock *timeoutCountLock = [[NSLock alloc] init];
__block NSInteger successCount = 0;
__block NSInteger timeoutCount = 0;
// å<>èµ· 10 个请æ±ï¼š5 个正常,5 个超æ—?
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
NSString *urlString;
NSTimeInterval timeout;
if (i % 2 == 0) {
// å<>¶æ•°ï¼šæ­£å¸¸è¯·æ±?
urlString = @"http://127.0.0.1:11080/get";
timeout = 15.0;
} else {
// 奇数:超时请�
urlString = @"http://127.0.0.1:11080/delay/10";
timeout = 0.5;
}
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentTest"
timeout:timeout
error:&error];
if (response && response.statusCode == 200) {
[successCountLock lock];
successCount++;
[successCountLock unlock];
} else {
[timeoutCountLock lock];
timeoutCount++;
[timeoutCountLock unlock];
}
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:20.0];
// 验è¯<C3A8>结果
XCTAssertEqual(successCount, 5, @"5 requests should succeed");
XCTAssertEqual(timeoutCount, 5, @"5 requests should timeout");
// 验è¯<C3A8>连接åˆå»ºæ•°å<C2B0>ˆç<CB86>†ï¼ˆ¸ªæˆ<C3A6>åŠ?+ 5个超æ—?= 最å¤?0个,å<C592>¯èƒ½æœ‰å¤<C3A5>用)
XCTAssertGreaterThan(self.client.connectionCreationCount, 0,
@"Should have created connections for concurrent requests");
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 10,
@"Should not create more than 10 connections");
// 等待所有连接å½è¿˜ï¼ˆå¼æ­¥æ“<C3A6>作需è¦<C3A8>æ´é•¿æ—¶é—´ï¼‰
[NSThread sleepForTimeInterval:2.0];
// 验è¯<C3A8>总连接数å<C2B0>ˆç<CB86>†ï¼ˆæ— æ³„æ¼<C3A6>ï¼? 关键验è¯<C3A8>ç?
// 在并å<C2B6>场景ä¸ï¼Œæˆ<C3A6>功的连接å<C2A5>¯èƒ½å·²ç»<C3A7>被关闭(remote close),池å<C2A0>¯èƒ½ä¸ºç©?
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
@"Total connections should not exceed pool limit (no leak)");
// å<>èµ·æ°è¯·æ±éªŒè¯<C3A8>æ± ä»<C3A4>ç„¶å<C2B6>¥åº·ï¼ˆèƒ½åˆå»ºæ°è¿žæŽ¥ï¼‰
NSError *recoveryError = nil;
HttpdnsNWHTTPClientResponse *recoveryResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&recoveryError];
XCTAssertNotNil(recoveryResponse, @"Pool should recover and handle new requests after mixed timeout/success");
XCTAssertEqual(recoveryResponse.statusCode, 200, @"Recovery request should succeed");
}
// P.4 连续超时ä¸<C3A4>导致连接泄æ¼?
- (void)testTimeout_ConsecutiveTimeouts_NoConnectionLeak {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 连续å<C2AD>èµ· 10 个超时请æ±?
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"LeakTest"
timeout:0.5
error:&error];
XCTAssertNil(response, @"Request %ld should timeout", (long)i);
// 等待清ç<E280A6>
[NSThread sleepForTimeInterval:0.2];
}
// 验è¯<C3A8>池状æ€<C3A6>:无连接泄æ¼?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty after consecutive timeouts");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"No connections should leak");
// 验è¯<C3A8>统计:æ¯<C3A6>次都åˆå»ºæ°è¿žæŽ¥ï¼ˆå ä¸ºè¶…时的被移除ï¼?
XCTAssertEqual(self.client.connectionCreationCount, 10,
@"Should have created 10 connections (all timed out and removed)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for timed-out connections");
}
// P.5 è¶…æ—¶ä¸<C3A4>阻塞连接池(并å<C2B6>正常请æ±ä¸<C3A4>å<EFBFBD>—å½±å“<C3A5>)
- (void)testTimeout_NonBlocking_ConcurrentNormalRequestSucceeds {
[self.client resetPoolStatistics];
XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Timeout request"];
XCTestExpectation *successExpectation = [self expectationWithDescription:@"Success request"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 请求 A:超时(delay 10s, timeout 2s�
dispatch_async(queue, ^{
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutRequest"
timeout:2.0
error:&error];
XCTAssertNil(response, @"Request A should timeout");
[timeoutExpectation fulfill];
});
// 请求 B:正常(应该ä¸<C3A4>å<EFBFBD>— A 阻塞ï¼?
dispatch_async(queue, ^{
// ç¨<C3A7>微延迟,确ä¿?A 先开å§?
[NSThread sleepForTimeInterval:0.1];
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"NormalRequest"
timeout:15.0
error:&error];
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
XCTAssertNotNil(response, @"Request B should succeed despite A timing out");
XCTAssertEqual(response.statusCode, 200);
// 验è¯<C3A8>è¯·æ± B 没有被请æ±?A 阻塞(应该很快完æˆ<C3A6>)
XCTAssertLessThan(elapsed, 5.0,
@"Request B should complete quickly, not blocked by A's timeout");
[successExpectation fulfill];
});
[self waitForExpectations:@[timeoutExpectation, successExpectation] timeout:20.0];
// 等待连接归还
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>池状æ€<C3A6>:å<C5A1>ªæœ‰è¯·æ± B 的连æŽ?
NSString *poolKey = @"127.0.0.1:11080:tcp";
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from successful request B");
}
// P.6 多端å<C2AF>£åœºæ™¯ä¸çš„è¶…æ—¶éš”ç¦?
- (void)testTimeout_MultiPort_IsolatedPoolCleaning {
[self.client resetPoolStatistics];
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
NSString *poolKey11444 = @"127.0.0.1:11444:tls";
// 端å<C2AF>£ 11443:超时请æ±?
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/delay/10"
userAgent:@"Port11443Timeout"
timeout:1.0
error:&error1];
XCTAssertNil(response1, @"Port 11443 request should timeout");
// 端å<C2AF>£ 11444:正常请æ±?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Success"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Port 11444 request should succeed");
XCTAssertEqual(response2.statusCode, 200);
// 等待连接归还
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>端å<C2AF>£éš”离:端å<C2AF>?11443 无连接,端å<C2AF>£ 11444 æœ?1 个连æŽ?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 0,
@"Port 11443 pool should be empty (timed out)");
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11444], 1,
@"Port 11444 pool should have 1 connection");
// 验è¯<C3A8>总连接数
XCTAssertEqual([self.client totalConnectionCount], 1,
@"Total should be 1 (only from port 11444)");
// å†<C3A5>次请æ±ç«¯å<C2AF>£ 11444:应该å¤<C3A5>用连æŽ?
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Reuse"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3);
XCTAssertEqual(response3.statusCode, 200);
// 验è¯<C3A8>å¤<C3A5>用å<C2A8>生
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Second request to port 11444 should reuse connection");
}
#pragma mark - R. Connection 头部处ç<E2809E>†æµè¯•
// R.1 Connection: close 导致连接被ç«å<E280B9>³å¤±æ•?
- (void)testConnectionHeader_Close_ConnectionInvalidated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 请求 1:æœ<C3A6>åŠ¡å™¨è¿”åž Connection: close
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close"
userAgent:@"CloseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Request with Connection: close should succeed");
XCTAssertEqual(response1.statusCode, 200);
// 等待异步 returnConnection 完æˆ<C3A6>
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>:连接ä¸<C3A4>在池中(已失效)
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Connection: close' header should be invalidated and not returned to pool");
// 请求 2:应该åˆå»ºæ°è¿žæŽ¥ï¼ˆç¬¬ä¸€ä¸ªè¿žæŽ¥å·²å¤±æ•ˆï¼?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAliveTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 验è¯<C3A8>统计:åˆå»ºäº† 2 个连接(第一个被 close,第二个正常ï¼?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by server)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse after Connection: close");
}
// R.2 Connection: keep-alive å…<C3A5>许连接å¤<C3A5>用
- (void)testConnectionHeader_KeepAlive_ConnectionReused {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 请求 1:æœ<C3A6>åŠ¡å™¨è¿”åž Connection: keep-alive
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAlive1"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
// 等待连接归还
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>:连接在池中
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Connection with 'Connection: keep-alive' should be returned to pool");
// 请求 2:应该å¤<C3A5>用第一个连æŽ?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAlive2"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 验è¯<C3A8>统计:å<C5A1>ªåˆå»ºäº?1 个连接,第二个请æ±å¤<C3A5>用了å®?
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should have created only 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Second request should reuse the first connection");
}
// R.3 Proxy-Connection: close 也会导致连接失效
- (void)testConnectionHeader_ProxyClose_ConnectionInvalidated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 请求 1:æœ<C3A6>åŠ¡å™¨è¿”åž Proxy-Connection: close (+ Connection: keep-alive)
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=proxy-close"
userAgent:@"ProxyCloseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Request with Proxy-Connection: close should succeed");
XCTAssertEqual(response1.statusCode, 200);
// 等待异步 returnConnection 完æˆ<C3A6>
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>:连接ä¸<C3A4>在池中(Proxy-Connection: close 应该失效连接ï¼?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Proxy-Connection: close' header should be invalidated");
// 请求 2:应该åˆå»ºæ°è¿žæŽ¥
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"RecoveryTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 验è¯<C3A8>统计:åˆå»ºäº† 2 个连æŽ?
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by proxy header)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse after Proxy-Connection: close");
}
// R.4 Connection 头部大å°<C3A5>写ä¸<C3A4>æ•<C3A6>感
- (void)testConnectionHeader_CaseInsensitive_AllVariantsWork {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 测试 1:CONNECTION: CLOSE (全大�
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-uppercase"
userAgent:@"UppercaseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>:连接ä¸<C3A4>在池中(大写 CLOSE 也应生效ï¼?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"'CONNECTION: CLOSE' (uppercase) should also close connection");
// 测试 2:Connection: Close (æ··å<C2B7>ˆå¤§å°<C3A5>å†?
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-mixed"
userAgent:@"MixedCaseTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
[NSThread sleepForTimeInterval:0.5];
// 验è¯<C3A8>:连接ä¸<C3A4>在池中(混å<C2B7>ˆå¤§å°<C3A5>写也应生效)
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"'Connection: Close' (mixed case) should also close connection");
// 验è¯<C3A8>统计:åˆå»ºäº† 2 个连接,都被 close
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (both closed due to case-insensitive matching)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for any closed connections");
}
// R.5 å¹¶å<C2B6>场景:混å<C2B7>?close å’?keep-alive
- (void)testConnectionHeader_ConcurrentMixed_CloseAndKeepAliveIsolated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *closeCountLock = [[NSLock alloc] init];
NSLock *keepAliveCountLock = [[NSLock alloc] init];
__block NSInteger closeCount = 0;
__block NSInteger keepAliveCount = 0;
// å<>èµ· 10 个请æ±ï¼š5 ä¸?closeï¼? ä¸?keep-alive
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
NSString *urlString;
if (i % 2 == 0) {
// å<>¶æ•°ï¼šclose
urlString = @"http://127.0.0.1:11080/connection-test?mode=close";
} else {
// 奇数:keep-alive
urlString = @"http://127.0.0.1:11080/connection-test?mode=keep-alive";
}
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentMixed"
timeout:15.0
error:&error];
if (response && response.statusCode == 200) {
if (i % 2 == 0) {
[closeCountLock lock];
closeCount++;
[closeCountLock unlock];
} else {
[keepAliveCountLock lock];
keepAliveCount++;
[keepAliveCountLock unlock];
}
}
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:30.0];
// 验è¯<C3A8>结果
XCTAssertEqual(closeCount, 5, @"5 close requests should succeed");
XCTAssertEqual(keepAliveCount, 5, @"5 keep-alive requests should succeed");
// 等待所有连接归�
[NSThread sleepForTimeInterval:1.0];
// 验è¯<C3A8>池状æ€<C3A6>:
// - close 连接全部被失效(ä¸<C3A4>在池中ï¼?
// - keep-alive 连接å<C2A5>¯èƒ½åœ¨æ± ä¸­ï¼ˆå<CB86>决于并å<C2B6>æ—¶åº<C3A5>åŒ remote closeï¼?
// - 关键是:总数ä¸<C3A4>è¶…è¿?4(池é™<C3A9>制),无泄æ¼?
NSInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed limit (no leak from close connections)");
// 验è¯<C3A8>统计:close 连接ä¸<C3A4>应该被å¤<C3A5>用
// 创建数应è¯?>= 6 (至少 5 ä¸?close + 1 ä¸?keep-alive,å ä¸?close ä¸<C3A4>能å¤<C3A5>用)
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 6,
@"Should have created at least 6 connections (5 close + at least 1 keep-alive)");
// å<>Žç»­è¯·æ±éªŒè¯<C3A8>æ± ä»<C3A4>ç„¶å<C2B6>¥åº?
NSError *recoveryError = nil;
HttpdnsNWHTTPClientResponse *recoveryResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&recoveryError];
XCTAssertNotNil(recoveryResponse, @"Pool should still be healthy after mixed close/keep-alive scenario");
XCTAssertEqual(recoveryResponse.statusCode, 200);
}
@end