12

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:

  • iPhone connects to the Internet via WiFi

Test 2:

  • iPhone connects to the Internet via 3G

Behaviour:

  • Both cases: app makes an HTTP/1.1 request to a web server with “Connection: keep-alive”; The Server (server ip = 10.217.81.131) responds with “Keep-Alive: timeout=300, max=99”
  • The client side (test 1 - app over WiFi) sends TCP FIN on the 30th second and the connection closes
  • The client side (test 2 – app over 3G) sends immediately (zero seconds) a TCP FIN request after it receives the HTTP/1.1 OK message from its first HTTP POST

Test 1 logs on the server side:

  1. At 23.101902 the app makes an HTTP/1.1 POST request to the server with “Connection: keep-alive” enter image description here

  2. At 23.139422 the server responds HTTP/1.1 200 OK with “Connection: Keep-Alive” and “timeout=300” (300 seconds) enter image description here

  3. The Round-Trip-Time (RTT) is reported as 333.82 msec (this highlights the margin of error we have on the following timestamps):

enter image description here

  1. 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): enter image description here

  2. The test is repeated numerous times with an approx. time of 30 seconds being always monitored

Test 2 logs on the server side:

  1. The HTTP/1.1 POST request from the app: enter image description here
  2. The HTTP OK server response with keep-alive being accepted and set at 300 seconds: enter image description here
  3. The RTT is at 859.849 msec enter image description here

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.

zirinisp
  • 9,971
  • 5
  • 32
  • 38
  • Could you also log the headers that are coming to your app from the server? Note that some proxies could change the keep-alive headers. – Sulthan Jan 05 '17 at 10:52
  • Have you tried with "Keep-Alive" rather than "keep-alive" in the client request? The spec is a little ambiguous, but it might matter. – dgatwood Jan 05 '17 at 21:38
  • @Sulthan I added the headers that are coming to the app from the server at the end of the question, under Client Debug. I can see that on the wifi tests keep alive has both timeout and max, while on the 3g tests it doesn't. I wonder whether that is normal behaviour. – zirinisp Jan 08 '17 at 11:12
  • 1
    @zirinisp It's probably caused by your operator. It's not easy to keep connection alive on 3G because your IP can change often. – Sulthan Jan 08 '17 at 11:30
  • @Sulthan We monitored ip changes and we had none. Yet the connection was dropping. – zirinisp Jan 08 '17 at 12:01
  • The server is responding with Keep-Alive: timeout=300, max=99, which might be throwing off the client. Maybe the server-returned max should be higher (or the timeout smaller). But, fwiw, I'm having the same problem trying to get persistent connections and see the same 30 second client-initiated closure over wifi. Trying over cellular soon. – Christian Jan 08 '18 at 15:55
  • @Christian we tried various values and the client ignored them, which makes me believe that iOS ignores the values or something else is off. – zirinisp Jan 10 '18 at 07:24
  • It seems like iOS is trying to keep the connections short lived and doesn't allow us to programatically do otherwise. – Christian Jan 11 '18 at 14:02
  • @Christian that is my impression too. But there has to be a way to keep the connection alive – zirinisp Jan 16 '18 at 11:24

1 Answers1

8

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. :-)

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • 2
    We tried setting HTTPShouldUsePipelining to true and we get identical results. – zirinisp Jan 17 '17 at 17:13
  • It looks like `httpShouldUsePipelining` doesn't work for POST requests. As the [docs](https://developer.apple.com/documentation/foundation/nsmutableurlrequest/1412705-httpshouldusepipelining) say "This may have no effect if an HTTP proxy is configured, or if the HTTP request uses an unsafe request method—for example, POST requests will not pipeline.". – Legonaftik Jun 05 '21 at 07:25
  • I guess that *kind of* makes sense, because sending out a POST request in parallel with other requests could result in nondeterminism if the request would affect the results of concurrent GET requests. – dgatwood Jun 06 '21 at 07:42