iOS - Alamofire - cannot reach server on WiFi - ios

I am slowly distributing my app to Beta testers via TestFlight.
A few of them have already reported that the app showed “No Internet connection” while on WiFi. As soon as they switched to cellular everything started to work. The WiFi works for everything else.
I use Alamofire as a framework to make HTTPRequests and when these requests fail, “No Internet connection” error is displayed.
My backend service is hosted on Google Cloud
My domain is registered using AWS Route 53
I use SSL certificates managed by Google
All HTTPRequests are sent to https://api.myapp.com (where myapp.com is hosted on AWS).
All of the testers have Automatic DNS resolution set in Settings -> WiFi -> (i) -> DNS
Any ideas what the cause can be or how I can further investigate this issue?
This is the piece of code used to make GET requests:
func getJSON(_ url: String,
parameters: [String: String] = [:],
headers: [String: String] = [:],
completionHandler: #escaping (_ result: JSON?,
_ headers: [String: String]?,
_ statusCode: Int?,
_ error: Error?) -> Void) {
Self.sessionManager.request(
url,
method: .get,
parameters: parameters,
headers: self.createHeaders(headers: headers),
interceptor: self,
requestModifier: { $0.timeoutInterval = HttpClient.defaultTimeoutInterval }
).validate().responseJSON { response in
switch response.result {
case .success(let data):
let json = JSON(data)
completionHandler(json, self.getResponseHeaders(httpUrlResponse: response.response), response.response?.statusCode, nil)
case .failure(let error):
completionHandler(nil, self.getResponseHeaders(httpUrlResponse: response.response), response.response?.statusCode, error)
}
}
}
The connection error is displayed when error != nil. When it happens, it happens for all HTTP endpoints (does not matter if an endpoint requires authentication or authorization, all HTTP requests fail).

Related

Network request intermittently failing with error -1009 The internet connection appears to be offline

I have multiple request that I call on the viewDidLoad method of my initial view controller. Sometimes these requests are failing with Error Code -1009 The internet connection appears to be offline. for some user and others are not having it. Sometimes users who get the error can use it after sometime with no issues. This usually happens on a cellular network. And other apps are working in their phone and not ours.
All of these requests are using the same method on my Service class. First I have an enum of the APIs and I convert them to a URLRequest using Alamofire's URLRequestConvertible protocol. Then I pass this request into Alamofire and handle the response.
func get<T:Codable>(_ api: ServiceAPI, resultType: T.Type, completion: #escaping (_ result: T?, _ error: Error?) -> ()) {
Alamofire.request(api).validate().responseJSON { response in
switch response.result {
case .success(let json):
print("\(api.path):\n\(json)")
do {
if let data = response.data {
let jsonDecoder = JSONDecoder()
let dictionary = try jsonDecoder.decode([String: T].self, from: data)
if let result = dictionary["result"] {
completion(result, nil)
} else {
completion(nil, self.resultNotFoundError)
}
}
} catch {
completion(nil, error)
}
case .failure(let error):
error.trackMixPanelEvent()
completion(nil, error)
}
}
}
Since I am creating an instance of the Service class and calling the get method on it for each request, is it possible that the request is being deallocated? What could also be the cause of such intermittent network error?
To also point out, all of the web service requests are using POST method.

Super long wifi request travel time on iPhone 8 / iPhone X

Not sure if it's ok to ask this here but I'm now confronting this frustrating problem and would like to ask for opinion on how to deal with this.
From some of the forum and discussions, looks like iPhone 8 and iPhone X has super slow internet issue when using wifi and running on iOS 11.4 / 11.4.1, and this results to the request travel time rediculously increases to almost 55 seconds (e.g. Hello world pinging test API) for a very simple request in my app. If I turn the wifi off and use 4G instead, the same request travel time is only 2 seconds(the API server is in the US while the app is oversea, so generally bearable), and the same request only travels 2 - 3 seconds on iPhone 6 plus running on iOS 11.2 / 11.4.1 in wifi mode.
I guess this is more a hardware or system side bug and on the app side we may not be able to do anything about this. However, as our client users who use iPhone 8 are unhappy about the waiting time and insist on solving it, and I also found that if I call the hello world API from safari browser of the iPhone, things are not that bad. Therefore, I would like to know if there is anything the app side can do (ex. detect this issue and do the workaround) to fix this or sooth the awful user experiences?
P.S. My app is written in Swift and I don't use third party library such as Alamofire to manage requests, simply use the build-in functionalities in Foundation. I would like to post some code on my sending request here, even if this may not help much.
func sendRequest(request: URLRequest, completion: #escaping (Int, Data?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let data = data, error == nil else {
print("HTTP request error=\(String(describing: error))")
// use 999 to represend unknown error
completion(HTTPHelper.DEFAULT_STATUS_CODE, nil, error)
return
}
guard let httpStatus = response as? HTTPURLResponse else { // check for http errors
return
}
if(httpStatus.statusCode != 200) {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print("error serializing JSON: \(error)")
}
let responseString = String(data: data, encoding: .utf8) ?? ""
print(responseString)
completion(httpStatus.statusCode, data, nil)
}
task.resume()
}
And this is the function where the above request is called, the request travel time is the time elapsed between two [TIME FLAG]:
func test(completion: #escaping (Bool) -> Void) {
let url = httpHelper.myUrlBuilder()
let request = httpHelper.wrapGetRequest(url: url, withToken: .none)
NSLog("[TIME FLAG] Test request sent")
httpHelper.sendRequest(request: request as URLRequest) { statusCode, data, error in
NSLog("[TIME FLAG] Test response get")
guard error == nil && self.httpHelper.validateStatusCode(statusCode: statusCode) else {
completion(false)
return
}
completion(true)
}
}
Thanks for any kind of answers and feedbacks.

Postman giving different response

I am a beginner in swift iOS and I am doing login module of an application in iOS but I am stuck at one thing I have login api but when I am checking response in postman when I am sending parameters as "raw" than it is showing user logged in but when I am sending the same parameters as "form-data" than it is showing wrong id and password....can anyone tell me how to send parameters as "raw" so that I can get correct response?? Thanks for your help!!
Please try this method if you are using Alamofire library for API call.
func request(_ method: HTTPMethod
, _ URLString: String
, parameters: [String : AnyObject]? = [:]
, headers: [String : String]? = [:]
, onView: UIView?, vc: UIViewController, completion:#escaping (Any?) -> Void
, failure: #escaping (Error?) -> Void) {
Alamofire.request(URLString, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
}
}
}
Also remember you need to pass Application/JSON header while calling this method.
["Content-Type": "application/json"]
From: http://toolsqa.com/postman/post-request-in-postman/
Check if your raw is using the correct format type as specified below.

HTTP Basic Auth with NSURLSession

I'm trying to implement HTTP Basic Auth with NSURLSession, but I run into several issues. Please read the entire question before responding, I doubt this is a duplicate of an other question.
According to the tests I've run, the behavior of NSURLSession is the following :
The first request is always made without the Authorization header.
If the first request fails with a 401 Unauthorized response and a WWW-Authenticate Basic realm=... header, it is automatically retried.
Before retrying the request, the session will attempt to obtain credentials by looking into the NSURLCredentialStorage of the session configuration or by calling the URLSession:task:didReceiveChallenge:completionHandler: delegate method (or both).
If credentials could be obtained the request is retried with the proper Authorization header. If not it is retried without the header (which is weird because in this case, this is exactly the same request).
If the second request succeeds, the task is transparently reported as successful and you're not even notified that the request was attempted twice. If not, the failure of the second request is reported (but not the first).
The problem I have with this behavior is that I am uploading large files to my server through multipart requests, so when the request is attempted twice, the entire POST body is sent twice which is a terrible overhead.
I have tried to manually add the Authorization header to the httpAdditionalHeaders of the session configuration, but it works only if the property is set before the session is created. Attempting to modify session.configuration.httpAdditionalHeaders afterwards doesn't work. Also the documentation clearly says that the Authorization header should not be set manually.
So my question is: If I need to start the session before I obtain the credentials and If I want to be sure that requests are always made with the proper Authorization header the first time, how do I do ?
Here is a code sample that I've used for my tests. You can reproduce all the behaviors I've described above with it.
Note that in order to be able to see the double requests you wil need to either use your own http server and log the requests or connect through a proxy that logs all requests (I've used Charles Proxy for this)
class URLSessionTest: NSObject, URLSessionDelegate
{
static let shared = URLSessionTest()
func start()
{
let requestURL = URL(string: "https://httpbin.org/basic-auth/username/password")!
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
let protectionSpace = URLProtectionSpace(host: "httpbin.org", port: 443, protocol: NSURLProtectionSpaceHTTPS, realm: "Fake Realm", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
let useHTTPHeader = false
let useCredentials = true
let useCustomCredentialsStorage = false
let useDelegateMethods = true
let sessionConfiguration = URLSessionConfiguration.default
if (useHTTPHeader) {
let authData = "\(credential.user!):\(credential.password!)".data(using: .utf8)!
let authValue = "Basic " + authData.base64EncodedString()
sessionConfiguration.httpAdditionalHeaders = ["Authorization": authValue]
}
if (useCredentials) {
if (useCustomCredentialsStorage) {
let urlCredentialStorage = URLCredentialStorage()
urlCredentialStorage.set(credential, for: protectionSpace)
sessionConfiguration.urlCredentialStorage = urlCredentialStorage
} else {
sessionConfiguration.urlCredentialStorage?.set(credential, for: protectionSpace)
}
}
let delegate = useDelegateMethods ? self : nil
let session = URLSession(configuration: sessionConfiguration, delegate: delegate, delegateQueue: nil)
self.makeBasicAuthTest(url: requestURL, session: session) {
self.makeBasicAuthTest(url: requestURL, session: session) {
DispatchQueue.main.asyncAfter(deadline: .now() + 61.0) {
self.makeBasicAuthTest(url: requestURL, session: session) {}
}
}
}
}
func makeBasicAuthTest(url: URL, session: URLSession, completion: #escaping () -> Void)
{
let task = session.dataTask(with: url) { (data, response, error) in
if let response = response {
print("response : \(response)")
}
if let data = data {
if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) {
print("json : \(json)")
} else if data.count > 0, let string = String(data: data, encoding: .utf8) {
print("string : \(string)")
} else {
print("data : \(data)")
}
}
if let error = error {
print("error : \(error)")
}
print()
DispatchQueue.main.async(execute: completion)
}
task.resume()
}
#objc(URLSession:didReceiveChallenge:completionHandler:)
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
{
print("Session authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
#objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
{
print("Task authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
Note 1: When making multiple requests in a row to the same endpoint, the behavior I've described above concerns only the first request. Subsequent requests are tried with the proper Authorization header the first time. However, if you wait some time (about 1 minute), the session will return to the default behavior (first request tried twice).
Note 2: This is not directly related, but using a custom NSURLCredentialStorage for the urlCredentialStorage of the session configuration doesn't seem to work. Only using the default value (which is the shared NSURLCredentialStorage according to the documentation) works.
Note 3: I've tried using Alamofire, but since it's based on NSURLSession, it behaves in the exact same way.
If possible, the server should respond with an error long before the client finishes sending the body. However, in many high-level server-side languages, this is difficult, and there's no guarantee that the upload will stop even if you do so.
The real problem is that you're performing a large upload using a single POST request. That make authentication problematic, and also prevents any sort of useful continuation of uploads if the connection drops midway through the upload. Chunking the upload basically solves all of your issues:
For your first request, send only the amount that will fit without adding additional Ethernet packets, i.e. compute your typical header size, mod by 1500 bytes, add a few tens of bytes for good measure, subtract from 1500, and hard-code that size for your first chunk. At most, you've wasted a few packets.
For subsequent chunks, crank the size up.
When a request fails, ask the server how much it got, and retry from where the upload left off.
Issue a request to tell the server when you've finished uploading.
Periodically purge partial uploads on the server side with a cron job or whatever.
That said, if you don't have control over the server side, the usual workaround is to sent an authenticated GET request right before your POST request. This minimizes wasted packets while still mostly working as long as the network is reliable.

Redirect back to app with Alamofire.request

I'm developing an iOS app in Swift 3 (I'm a complete Swift n00b.) I am authenticating against an external app, to get an access token back, but the code is not redirecting back to my app after authenticating against the external app.
AppDelegate.swift
func application(_ application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: Any) -> Bool {
print(url)
DispatchQueue.main.async {
print(url.scheme)
if (url.scheme == "myapp"){
print("i just caught a url and i feel fine and the url be says ", url)
}
}
return true
}
in class that handles response from external app:
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.responseJSON { response in
print(response)
if((response.result.value) != nil) {
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar[key].string {
let autostart_data = swiftyJsonVar[key].string
... snip ...
let url = URL(string: "extapp:///?key=\(data!)&redirect=\(myapp)://key")
UIApplication.shared.open(url!, options: [:])
... snip ...
}
}
}
Is it possible to redirect back to my app, with Alamofire, once the response is received? I have been researching online with Google for the past two days and have tried out many possible solutions, but have come to nothing so far. I'd appreciate any help!
Problem is not with Alamofire. It's to do with the external app's API and my iOS app not being able to redirect back.

Resources