I am currently developing an application where we need some request to hit our server ASAP. To speed up the request process we have to eliminate handshake (as it takes extra) and have a permanent connection.
The application is using the Alamofire framework to make all request to our server and the setup is the following:
We have a session manager set up with default configuration and http header.
lazy var sessionManager: Alamofire.SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(configuration: configuration)
return manager
}()
The session manager is persistent across all requests. Each request is made using the following code:
self.sessionManager.request(request.urlString, method: request.method, parameters: request.parameters)
.responseJSON { [weak self] response in
// Handle the response
}
request.urlString is the url of our server "http://ourserver.com/example"
request.method is set to post
request.parameters is a dictionary of paramaters
The request is working fine and we get a valid response. The problem arises on the keep alive timer, which is set by our server to 300 seconds. The device holds the connection for a maximum of 30 seconds on wifi and closes it almost instantly over GSM.
Server Debug
We did some debugging on our server and found the following results
Tests:
Test 1:
Test 2:
Behaviour:
Test 1 logs on the server side:
At 23.101902 the app makes an HTTP/1.1 POST request to the server with “Connection: keep-alive”
At 23.139422 the server responds HTTP/1.1 200 OK with “Connection: Keep-Alive” and “timeout=300” (300 seconds)
The Round-Trip-Time (RTT) is reported as 333.82 msec (this highlights the margin of error we have on the following timestamps):
The app, however, closes the connection in 30 seconds (approx. given the Internet transport variations – the difference between the 54.200863 and the 23.451979 timestamps):
The test is repeated numerous times with an approx. time of 30 seconds being always monitored
Test 2 logs on the server side:
The app closes immediately the connection, where immediately is 21.197918 – 18.747780 = 2.450138 seconds
The tests are repeated while switching from WiFi to 3G and back with the same results being recorded.
Client Debug
Using WiFi
First Attempt (connection established)
Optional(
[AnyHashable("Content-Type"): text/html,
AnyHashable("Content-Encoding"): gzip,
AnyHashable("Content-Length"): 36,
AnyHashable("Set-Cookie"): user_cookieuser_session=HXQuslXgivCRKd%2BJ6bkg5D%2B0pWhCAWkUPedUEGyZQ8%2Fl65UeFcsgebkF4tqZQYzVgp2gWgAQ3DwJA5dbXUCz4%2FnxIhUTVlTShIsUMeeK6Ej8YMlB11DAewHmkp%2Bd3Nr7hJFFQlld%2BD8Q2M46OMRGJ7joOzmvH3tXgQtRqR9gS2K1IpsdGupJ3DZ1AWBP5HwS41yqZraYsBtRrFnpGgK0CH9JrnsHhRmYpD40NmlZQ6DWtDt%2B8p6eg9jF0xE6k0Es4Q%2FNiAx9S9PkhII7CKPuBYfFi1Ijd7ILaCH5TXV3vipz0TmlADktC1OARPTYSwygN2r6bEsX15Un5WUhc2caCeuXnmd6xy8sbjVUDn72KELWzdmDTl6p5fRapHzFEfGEEg2LOEuwybmf2Nt6DHB6o6EA5vfJovh2obpp4HkIeAQ%3D; expires=Sun, 08-Jan-2017 12:51:43 GMT; path=/,
AnyHashable("Keep-Alive"): timeout=300, max=100,
AnyHashable("Connection"): Keep-Alive,
AnyHashable("X-Powered-By"): PHP/5.3.10-1ubuntu3.11,
AnyHashable("Server"): Apache/2.2.22 (Ubuntu),
AnyHashable("Vary"): Accept-Encoding,
AnyHashable("Date"): Sun, 08 Jan 2017 10:51:43 GMT])
Second Attempt (within 30 sec, the connection is still alive)
Optional([AnyHashable("Content-Type"): text/html,
AnyHashable("Content-Encoding"): gzip,
AnyHashable("Content-Length"): 36,
AnyHashable("Keep-Alive"): timeout=300, max=99,
AnyHashable("Connection"): Keep-Alive,
AnyHashable("X-Powered-By"): PHP/5.3.10-1ubuntu3.11,
AnyHashable("Server"): Apache/2.2.22 (Ubuntu),
AnyHashable("Vary"): Accept-Encoding,
AnyHashable("Date"): Sun, 08 Jan 2017 11:00:18 GMT])
Then after 30 seconds the connection drops (FI)
Using 3G
First Attempt
Optional([AnyHashable("Content-Type"): text/html,
AnyHashable("Content-Encoding"): gzip,
AnyHashable("Content-Length"): 36,
AnyHashable("Connection"): keep-alive,
AnyHashable("X-Powered-By"): PHP/5.3.10-1ubuntu3.11,
AnyHashable("Server"): Apache/2.2.22 (Ubuntu),
AnyHashable("Vary"): Accept-Encoding,
AnyHashable("Date"): Sun, 08 Jan 2017 11:04:31 GMT])
Then the connection drops almost instantly.
All modern browsers use persistent connections as long as the server has Keep-Alive enabled. In order to check if your pages are delivered with a Keep-Alive header, you can use the HTTP Header Checker tool. This will display the Connection: Keep-Alive field if the HTTP Keep-Alive header is enabled.
Enabling Keep-Alive ensures that a single TCP connection is used to transfer multiple files from the server to the browser. This helps your page load faster as the browser doesn't need to establish multiple connections to retrieve all your page resources.
Enabling Keep-Alive is a great way to optimize your website as it helps improve speed and performance, ensuring faster load times and higher efficiency. By turning the Keep-Alive header on, the client and server can reuse a single TCP connection for a number of requests and responses.
Keep-Alive Timeout The default is 30 seconds, meaning the connection times out if idle for more than 30 seconds. The maximum is 3600 seconds (60 minutes).
Now that I looked at the code a second time, I think I see the problem. The underlying NSURLSession
class defaults to ignoring the keep-alive
header, because some servers "support" it, but in practice, break badly if you actually try to use it, IIRC.
If you want a session to support keep-alive, you have to explicitly set HTTPShouldUsePipelining
in the session configuration to YES
.
Note that there is still no guarantee that the connection will stay up, depending on how aggressively iOS decides to power manage the radio, but at least you'll have a prayer. :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With