iOS Networking - HTTP Connections & running in background - ios

I have an app that lets the user send messages with images. A user might hit send, then immediately close their phone or switch to another app.
We were running into an issue that if there's temporarily a bad network connection the message would fail to send. We switched to using NSURLSession backgroundConfigurationWithIdentifier so that backgrounding the app doesn't immediately time out the running request. We switched to using this for all our api requests, thinking that it wouldn't hurt for every request to able to continue in the background if the app were closed at the wrong time.
Fast forward a couple weeks we're noticing all requests seem slow. Using wireshark I just discovered that this background session seems to use a new http connection per request, meaning it requires setting up a TCP connection and new TLS handshake for every request, which was adding a ~500ms latency on every request in our app. This is a pretty big deal but I can't find this behavior documented anywhere, including the link above or Apple's background transfer considerations.
So my question is, is this behavior expected, or am I doing something wrong somewhere? Is there an easy way with NSURLSession to make an HTTP request that will use an existing keep-alive connection if there is one, but can fall back to the backgroundConfiguration if the app gets moved to the background?

NSURLSession is the recommended way to fulfill your use case. Have you tried setting backgroundSessionConfig.discretionary = true
iOS Reference
A Boolean value that determines whether background tasks can be
scheduled at the discretion of the system for optimal performance.
If that doesn't help, I recommend filing a bug with iOS.

Related

is threre any configuration to control permission to internet in IOS

Is there any configuration like android user-permission in iOS to control access to internet?
I think all new projects access to internet by default, is that correct?
When I send a request to the internet it returns 0 http-error code, it means I can't access to the internet.
yes, it is correct all the new ios project have access to the internet by default.
A status code of 0 in an NSHTTPURLResponse object generally means there was no response and can occur for various reasons. The server will never return a status of 0 as this is not a valid HTTP status code.
Any http request will first be processed by the operating system, and during that phase you can get an error. Getting an error means that your request never got a response from the server (and with the exception of https requests where certificates were not accepted, most likely didn't reach the server).
If this phase succeeds, then you get eventually a reply from the server. This may take time, you may have to wait 60 seconds. Which is why you do all your internet requests on a background thread. That reply will have a status code (status, not error). The status code is NEVER 0.
By default, iOS doesn't allow http requests, and doesn't allow https requests to unsave servers, so you better use only https unless you have a very good reason. You will need a very good reason to convince Apple to let your app on the app store if you want http requests to succeed. But if you get this wrong, you get an error quite early on.
A status of zero most likely means that a background request didn't finish by the time you read the status, a basic programming mistake. You need to learn how background threads and callbacks work. Without that, you won't be able to use http successfully.
Also google for "Reachability" which can tell you if your app currently has internet access (like when WiFi and Mobile Data are turned off, or in Airplane mode).

First http request in iOS networking is slow, subsequent requests are much faster

I'm experiencing slow response times for my first http POST request to my server.
This happens both in Android and iOS networking libraries. (Volley on Android, and Alamofire in iOS).
First response is roughly 0.7s-0.9s, whereas subsequent requests are 0.2s.
I'm guessing this is due to the session being kept-alive by the server, therefore eliminating the need for establishing a new session on each request.
I figure I can make a dummy request when the app starts to start the session, but it doesn't seem very elegant.
I also control the server side (Node.js) so if any configuration needs to be done there I can also try it.
Investigating a little further, I tried sending an https CONNECT request before issuing the first "real" POST request, and the behavior replicates.
After 30 seconds or so, the connection is dropped (probably at the iOS URLSession level, the load balancer is configured to keep connections as 60 seconds).
In theory this makes sense because setting up an https connection takes up several (12 total) packets and I'm on an inter continental connection.
So my solution is to send a CONNECT request when I expect the user to send a regular request.

NSURLSession and NSURLConnection usage as it relates to sharing a SSL session

I have some logic that communicates with a backend server which has two versions, a NSURLConnection and NSURLSession. The latter was made because NSURLConnection is deprecated, and also we want to leverage the background download ability. These classes are written to support client-side certificates for authenticating the client.
The classes that do this communication is called from a few different types of routines throughout my application.
The issue I am seeing is that when using the NSURLSession version, I see that a SSL (TLSv1) session is never being reused, and there is a full chain of [Client Hello, Server Hello, Certificate, Certificate, Client Key Exchange, Certificate Verify, etc.] for each connection. Looking at the "Client Hello" message, I can see there is never a "session ID" in the message, so I think this is why the SSL session is never reused.
On the older, NSURLConnection version, I see that often the session is reused, and a previous session ID is given in the "Client Hello" message.
In both of these approaches, I am creating a new instance of NSURLConnection or NSURLSession. I think what is going on is that NSURLConnection works across the entire app, whereas NSURLSession does not.
However, I am not sure how I should be writing my NSURLSession logic so that the SSL session is shared between server and client and the client cert isn't sent every time. I think one way might be to use a single NSURLSession instance, but I am not sure if this is the right design pattern.
With NSURLSession, connections are not shared across multiple sessions, because different sessions can have different limits on the maximum number of concurrent connections, different keep-alive settings, different proxy settings, etc.
Thus, if you're only making one request per session, you'll end up creating a new connection every time, complete with the full TLS setup overhead.
There are two straightforward ways to fix this:
Create a single session when you launch the app and use it everywhere.
Use the shared session ([NSURLSession sharedSession]).
If you're doing background downloading, that session needs to be created once, when your app first starts, and then never recreated (unless the session was created in response to your app getting relaunched in the background to handle some task's results, in which case IIRC the session becomes invalid, so you'll recreate it again if your app subsequently gets launched in the usual way or whatever).

Change timeout for Parse requests

In the iOS Developers Guide for Parse, it states "By default, all connections have a timeout of 10 seconds." I am looking to change this for all requests made from the app, but am not finding any information on how to do so.
The reason we'd like to modify this is that it's taking a long time for requests to fail when the user doesn't have Wi-Fi or Cellular enabled. We want to reduce the amount of time it takes to receive said error message, just a little. We don't want to implement our own reachability tests, as it will result in duplicate popup error messages and we have many requests in various view controllers throughout the app.
Can the timeout be modified, or is there some other way to obtain a better user experience than waiting 10 seconds for an error message?
There is no information on this but certainly the request timeout limits are set by Parse and a developer will not be able to change them. I think they kept the timeouts to be long to avoid a user request being rejected if their connection becomes suddenly intermittent or they go in a tunnel, etc.
You can try to warp Parse queries around a timer which uses let say 5 seconds timeout, if the response does not come in that time you cancel your your query using PFQuery cancel function and show them a message.
If you want to avoid timing out, consider checking Reachability before making the call. You may want to show the user an alert if they're not connected and you need to do something.
A lot of people say you should just assume a connection and make the attempt without checking reachability; basically just let the connection fail and handle the error that way. I think as long as the failure isn't invisible to the user, so they don't blame your app vs their network you're good though.

Sending SPDY requests results in "The request timed out" errors with NSUrlSession in iOS

My iOS app loads images from an nginx HTTP server. After I send 400+ such requests the networking 'gets stuck' and all subsequent HTTP requests result in "The request timed out" error. I can make the images load again only when I restart the app.
Details:
I am using NSURLSession.sharedSession().dataTaskWithURL to send four hundred HTTP GET requests to jpeg files.
Requests are sent sequentially, one after another. The interval between requests is 10 ms.
Each previous unfinished request is cancelled with cancel() method of NSURLSessionDataTask object.
Interestingly:
I can only have this issue with HTTPS requests and when SPDY is enabled on the server.
Non-secure HTTP requests work fine.
Non-SPDY HTTPS requests work fine. I tested it by turning SPDY off on the server side, in the nginx config.
Problem appears both on iOS 8 and 9, on physical device and in the simulator. Both on Wi-Fi and LTE.
When I look at nginx access logs, I can still see the 'stuck' requests coming in. Important nuance: the request log record appears at the exact moment when the iOS app is giving up on it after the time out period ends.
I was hoping to analyze HTTP requests with Charles Proxy but the problem cures itself when requests go through Charles. That is - everything works with Charles, much like effect in quantum mechanics when the fact of looking influences the outcome.
I was able to reproduce the issue when the iOS app connected to two different servers with vastly different nginx configurations. This probably means that the issue is not related to a particular nginx setup.
I analyzed the app using "Activity Monitor" instrument. The number of threads it is using during the bulk HTTP requests jumps from 5 to 10. In comparison, when I send just a single HTTP requests the number of threads jumps to 8. CPU load rarely goes above 30%.
What can be the cause of the issue? Can anyone recommend other ways or tools for analysing and debugging it?
Analysing with scheduling instrument
Demo app
This demo app reproduces the issue 100% of the time for me.
https://github.com/exchangegroup/ImageLoadDemo
Versions and settings
My nginx config: http://pastebin.com/pYYjdxfP
OS X: 10.10.4 (14E46), iOS: 8 and 9, Xcode: 7.0 (7A218), nginx: 1.9.4
Not ideal workaround
I managed to keep requests working only if I create a new NSURLSession for each individual request and clear the previous session with finishTasksAndInvalidate or invalidateAndCancel.
// Request 1
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration)
session.dataTaskWithURL ...
// Request 2
// clear the previous request
session.finishTasksAndInvalidate()
let session2 = NSURLSession(configuration: configuration)
session2.dataTaskWithURL ...
One possibility is that iOS started sending the request, and then packet loss prevented the headers and request body from being fully delivered.
Another possibility that comes to mind is that your server may not be logging the request until it actually finishes trying to deliver it, which would make the time stamps in the server logs line up with when the connection was closed, rather than when it was opened. (IIRC, that's what Apache does; I haven't worked with nginx, so I can't speak for its behavior.) If that's the case, then this is just a simple connection stall. As for why it is stalling, I couldn't guess.
Does the problem occur exclusively for HTTPS traffic? If you can reproduce it with HTTP, you don't need Charles Proxy; just use OS X's "Internet Sharing" feature, and capture the packets with tcpdump or wireshark, listening on the bridge interface. If you can't reproduce it with HTTP, my money would be on a problem with fetching the CRLs or performing the OCSP check while validating the server's certificate.
Is your app ending up with a huge number of threads as a result of excessive async dispatching to new queues, by any chance? Because that could easily cause all sorts of odd misbehavior.
How long is the timeout? If it is too short, your app might simply be running up against performance limitations of the hardware while processing the results of 400 requests delivered in only four seconds.
Also, are you trying to schedule these requests simultaneously? Because I seem to recall reading about a bug that causes NSURLSession to hit a brick wall if you start too many tasks in a single session at the same time. You might try adding tasks only after the number of tasks in a session drops below some threshold and see if that fixes the problem.

Resources