I\'ve been struggling with an HTTP timeout issue recently. After more than one month of investigation I\'m quite sure that it is caused by bad HTTP persistent connections. Detai
After 2 weeks of research, I can give answers to question 3 and 4:
nginx
's persistent connection timeout is set to 5s on server, which should not be the cause. Server engineers found those timed-out requests are actually normally received and responded. So it is more likely a client side issue. Since I have a minimal reproducible code to rule out my code as the cause, the cause should be in iOS.CFNetwork
. Higher level API such as NSURLConnection
or NSURLSession
's Connection
header will be overwritten by system. Same issue here, iOS just try to reuse the connection after server drops it.
About two years ago, I switched to CFNetwork to workaround this issue, but recently I found it's not possible to implement SSL pinning with CFNetwork API. So now I'm considering go back to NSURLSession.
After some dig around, I found system will NOT reuse connections across NSURLSession
s, so creating new sessions within period of time, should solve the issue.
But I also found (at least on macOS): each connection made by NSURLSession can persistence last by 180 seconds, and that connection didn't close by release or reset the session, so you may need to implement some caching mechanism to avoid creating a lots of connections at the same time.
Here is the simple mechanism I'm currently use:
@interface HTTPSession : NSObject
@property (nonatomic, strong) NSURLSession * urlSession;
@property (nonatomic, assign) CFTimeInterval flushTime;
@end
+ (NSURLSession *)reuseOrCreateSession
{
static NSMutableArray<HTTPSession *> * sessions = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sessions = [NSMutableArray<HTTPSession *> array];
});
const CFTimeInterval serverTimeoutSeconds = 10;
const CFTimeInterval systemTimeoutSeconds = 40;
CFTimeInterval now = CFAbsoluteTimeGetCurrent();
HTTPSession * resultSession = nil;
for (HTTPSession * session in sessions) {
CFTimeInterval lifeTime = now - session.flushTime;
if (lifeTime < serverTimeoutSeconds) {
resultSession = session;
break;
}
if (lifeTime > systemTimeoutSeconds) {
resultSession = session;
resultSession.flushTime = now;
break;
}
}
if (!resultSession) {
resultSession = [HTTPSession new];
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
// setup session
resultSession.urlSession = session;
resultSession.flushTime = now;
[sessions addObject:resultSession];
}
return resultSession.urlSession;
}
What if you add timestamp to all your request URL? I think this will make each request unique and maybe iOS will establish new connection every time you send request ( I'm not sure. Need trying )