Alamofire: Network error vs invalid status code? - swift

Using Alamofire 4/Swift 3 how can you differentiate between a request that fails due to:
Network connectivity (host down, can't reach host) vs
Invalid server HTTP response code (ie: 499) which causes the Alamofire request to fail due to calling validate()?
Code:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
//??NETWORK ERROR OR INVALID SERVER RESPONSE??
}
}
We want to handle each case differently. In the latter case we want to interrogate the response. (In the former we don't as there is no response).

Here's our current working solution:
sessionManager.request(url, method: .post, parameters:dict, encoding: JSONEncoding.default)
.validate() //Validate status code
.responseData { response in
if response.result.isFailure {
if let error = response.result.error as? AFError, error.responseCode == 499 {
//INVALID SESSION RESPONSE
} else {
//NETWORK FAILURE
}
}
}
If result.error it is of type AFError you can use responseCode. From the AFError source comments:
/// Returns whether the `AFError` is a response validation error. When `true`, the `acceptableContentTypes`,
/// `responseContentType`, and `responseCode` properties will contain the associated values.
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
Maybe there is a better way (?) but that seems to work...

Alamofire can tell you about the status of the request,
this code works just fine for me:
if let error = response.result.error as? NSError {
print(error)//print the error description
if (error.code == -1009){
print(error.code) // this will print -1009,somehow it means , there is no internet connection
self.errorCode = error.code
}
//check for other error.code
}else{
//there is no problem
}
the error.code will tell you what is the problem

Automatic validation should consider status code within 200...299 (success codes) range so when you get an invalid server HTTP response code 5xx (499 means Client Closed Request) you are sure it's not depend by validation.
About the statusCode, my advice is to follow the correct new rules to get it. If you have some problem to retrieve it look this SO answer.
Speaking about network reachability you could write:
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.listener = { status in
print("Network Status Changed: \(status)")
}
manager?.startListening()
There are some important things to remember when using network reachability to determine what to do next.
Do NOT use Reachability to determine if a network request should be
sent. You should ALWAYS send it.
When Reachability is restored, use the event to retry failed network
requests. Even though the network requests may still fail, this is a
good moment to retry them.
The network reachability status can be useful for determining why a
network request may have failed. If a network request fails, it is
more useful to tell the user that the network request failed due to
being offline rather than a more technical error, such as "request
timed out."
You can find these details also in the official Alamofire 4 GitHUB page

Related

Issues running asynchronous code in unit tests

So I am having issues running asynchronous code in the unit tests of my app. I am using expectations in order to wait for the code to execute before the test completes. The asynchronous code runs through heroku in order to get the values and then should return them in the app. With this unit test, I am trying to make sure the connection through heroku is working. Here is a look at my code:
func test() {
let url = "https://s.herokuapp.com/test"
let params: [String: Any] = ["account_id": AppState.sharedInstance.user.accounttoken]
let expectation = self.expectation(description: "Testing returning value")
let totalBalance = ""
Alamofire.request(url, method: .post, parameters: params)
.validate(statusCode: 200..<300)
.responseJSON { response in
switch response.result {
case .success:
print("Returned with success")
case .failure(let error):
let status = response.response?.statusCode
print("Failed, status: \(status)")
print("Here is the error: \(error)")
}
if let result = response.result.value {
let balance = result as! NSDictionary
let totalBalance = String(describing: "\(balance["Balance"]!)")
}
XCTAssert(totalBalance != "")
expectation.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
XCTAssert(totalBalance != "")
}
The reason I am confused is because I have no error getting the asynchronous code to return values in the actual app. I only have the wait time issue in unit testing. I am getting two fail errors, one for the XCTAssert not being true and one for the waitForExpectations going longer than 10 seconds. Here is some of the errors that are popping up as well if that helps find the solution:
Here is the error messages in text form:
2019-07-01 09:44:38.181971-0400 Spotbirdparking[49677:4306598] TIC TCP
Conn Failed [6:0x6000030b7cc0]: 3:-9816 Err(-9816) 2019-07-01
09:44:38.188607-0400 Spotbirdparking[49677:4306598]
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9816) 2019-07-01 09:44:38.188819-0400
Spotbirdparking[49677:4306598] Task
.<1> HTTP load failed (error
code: -1200 [3:-9816]) 2019-07-01 09:44:38.189215-0400
Spotbirdparking[49677:4306623] Task
.<1> finished with error - code:
-1200 /Users/drewloughran/Desktop/SpotBird/SpotbirdparkingTests/SpotbirdparkingTests.swift:117:
error: -[SpotbirdparkingTests.SpotbirdparkingTests test_stripe] :
Asynchronous wait failed: Exceeded timeout of 10 seconds, with
unfulfilled expectations: "Testing stripe returning value".
I am also fairly new to swift so any help would be appreciated with this problem.
Drew, please check this error first. Your request is failing to load.
HTTP load failed (error code: -1200 [3:-9816]) 2019-07-01 09:44:38.189215-0400 Spotbirdparking[49677:4306623] Task .<1> finished with error - code: -1200
Also make sure that the block you are using to fulfill the expectation is being executed. Probably it's not and that's the reason your expectation is not fulfilled and your test is failing.
Also, from this documentation I can see that your validation intersects an automatic validation case. which is redundant.
Response Validation
By default, Alamofire treats any completed request to be successful, regardless of the content of the response. Calling validate before a response handler causes an error to be generated if the response had an unacceptable status code or MIME type.
Manual Validation
Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
Automatic Validation
Automatically validates status code within 200...299 range, and that the Content-Type header of the response matches the Accept header of the request, if one is provided.
Alamofire.request("https://httpbin.org/get").validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
Good luck!
You should not be testing your Alamofire calls by doing actual networking. This causes you to hit the network every time you test; it is unreliable (the network can fail) and unnecessary. We know what the network does! You also do not have to test Alamofire; it isn't yours to test, and again, you know what it does. Therefore you should refactor your code for testing and mock the network call.

Is there a way to differentiate between success and error API answers in Swift when there is no request status in response?

I'm creating a login page, and I used to handle API error such as:
{"status":"error","answer":"Password is wrong"}
this way:
if error?["status"] as? String == "error" {
Alert.showBasic(title: error!["status"] as! String, message: error!["answer"] as! String, vc: self)
This worked perfectly. But now I'm working with another API, that responds to my request this way:
{"password":["password is wrong"]}
The confusion what I have, is that I don't get from API answer, a status for my request, like success or error.
Is there a way to handle it?
The error != nil depends and what you feed to your model and how you feed depends on status code
if let httpResponse = response as? HTTPURLResponse{
switch httpResponse.statusCode {
case 200 : //handle Root data model
case 401 : //handle wrong password model
}
}
URLSession error is different . Its when the request is failed and no response . But you got the response but its up to you differentiate whether its good or bad.
Server response should be static if there is a change not noticed to you means something wrong in the backend.

Error 401: Alamofire/Swift 3

This code previously works, and suddenly after several works around it stopped and return an Error 401.
Alamofire.request(WebServices.URLS().TabDataURL, method: .post, parameters: reqParams).validate().responseJSON { response in
let statusCode = (response.response?.statusCode) //Get HTTP status code
guard response.result.isSuccess, let value = response.result.value else {
// FIXME:you need to handle errors.
print("Status, Fetching News List:", statusCode)
return
}
I have check via Postman, the parameters are correct. Infact, I can also login (by passing 2 parameters). But when I want to pull in a JSON data from server, I am getting 401.
my main project doesn't have an error. but instead of returning the JSON data, it gave me an Error 401. my other projects (same code format, same server & parameters) is giving me this error.
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.} it worked!
Also, I have check with my other projects -- it seemed I cannot connect aswell. So I suspect it could be because of Alamofire, or my Xcode?
Anyone can help me?
Hi i think your server Response is not correct because, as error indicate object should not start with array same issue i come across tell backend developer to send response in dictionary,
{NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
let headers = [String: String]()// To add header if any
Alamofire.request(path,method: mType, parameters: parameters, encoding: JSONEncoding.default, headers : headers) .responseJSON
{ response in
//----- this code always works for me. & You don't need add header if not required.
if let JSON = response.result.value {
print("JSON: \(JSON)")
if response.response?.statusCode == 200
{
successCompletionHandler(JSON as! NSDictionary)
}
else if response.response?.statusCode == 401
{
failureCompletionHandler(JSON as! NSDictionary)
}
else
{
failureCompletionHandler(JSON as! NSDictionary)
}
}
else
{
print("error message")
failureCompletionHandler([WSAPIConst.ERROR:WSAPIConst.ERROR_MESSAGE])
}
}

Alamofire GET request from Server API

I'm just getting acquainted with Alamofire (using Alamofire 4 with Xcode 8, Swift3, iOS 10)
I'm attempting to use HTTP protocol methods on a on a server API given the an ip address of XXX.XXX.X.XX
To take a photo with the SDK I'm using, I must use a GET request. The documentation states it like this:
Take the photo
GET takepic/true
Trigger the camera to take a photo
Return http status code 200 means OK, 500 means failed
The thing is, I'm not sure how to successfully get the data from the server, and also do not know how to use this "takepic/true" property I'm given.
I attempted the issue like so:
Alamofire.request("http://myIPAddress", method: .get).responseJSON { (response) in
if let status = response.response?.statusCode
{
switch(status)
{
case 200:
print("example success")
case 500:
print("The response failed")
default:
print("error with response status: \(status)")
}
}
if let result = response.result.value
{
print(result)
}
}
After running the app, I get a bunch of text in my console. Here's some of the stuff it says:
nw_socket_set_common_sockopts setsockopt SO_NOAPNFALLBK failed: [42] Protocol not available, dumping backtrace:
nw_connection_endpoint_report [1 192.168.9.67:80 in_progress socket-flow (satisfied)] reported event flow:start_connect
I searched for my print statement which meant success, but could not find it. I could not find any of my other print statements either.
EDIT: The text in my console window comes from me installing SISocket to my project via Cocoapods.
My "result" returns this:
expression produced error: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x8).
The process has been returned to the state before expression evaluation.

How can I retrieve the status message of a request made with Alamofire?

The server that I am using returns error messages in the HTTP status message. For example, it will return "400 User already exists" rather than "400 Bad Request".
I would like to access the string "User already exists" in the response method called by Alamofire. However, I cannot find any way to access this string.
I found this question on StackOverflow already: Swift Alamofire: How to get the HTTP response status code
Unfortunately, no one gives an answer to the question. :(
Here is where Chrome shows where the error is:
I would suggest trying to print out all the possible data fields that you are given and see what you can find. Please try the following example and see if that sheds any light.
let URL = NSURL(string: "your/url/to/somewhere")!
let parameters = ["foo": "bar"]
Alamofire.request(.POST, URL, parameters: parameters)
.response { request, response, data, error in
println("Request: \(request)")
println("Response: \(response)")
println("Error: \(error)")
if let data = data as? NSData {
println("Data: \(NSString(data: data, encoding: NSUTF8StringEncoding)!)")
}
}
Return response in json format from the server and then i think you'll be able to get the appropriate status.
I've implemented that thing using php codeigniter..from where my response is like
$response['status'] = 'user_already_exists';
$this->response($response, 400);
Now in swift you can go with this
Alamofire.request(.POST,URL, parameters:parameters) .responseJSON
{
(request, response, data, error) in
var json = JSON(data!) //I've used swiftyJSON for reading json response
let status = json["status"].stringValue
println("Status : \(status)")
}
Hope this may help you.