NSURLSession data task response handling - swift

I am currently working on implementing a networking model that communicates with a REST API through HTTP in Swift using NSURLSession class.
For preparing the request, I use this code:
var request = URLRequest(url: requestURL) // url is "http://somethingsomething...
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body
Now, I am sending the requests that way:
session.dataTask(with: request) { data, response, error in
// Parse the data, response and error
}.resume()
My problem is that I want to check the httpStatusCode of the response in order to inform about possible errors, however, the response inside the completion block is of type URLResponse, not HTTPURLResponse. This means that I have to cast the response to the type HTTPURLResponse. So far I've thought of two ways to approach it - first is to add another error scenario which would handle the "couldn't cast to HTTPURLResponse" scenario, the other way is to simply force the cast to HTTPURLResponse. The first option seems better but it could be just adding some useless code, because maybe the cast will always succeed?
So basically, my question is - can I be sure that, if I send the requests to a HTTP server, the response will always be of HTTPURLResponse type? Or do I have to implement the code that handles a situation where the response object is of a different type or is a nil?
Also, I think it would be good to mention that in my implementation if the completion block returns error that is not nil, the flow will return before trying to cast anything.

I believe this is what you need.
session.dataTask(with: request) { data, response, error in
// Parse the data, response and error
if let httpResponse = response as? HTTPURLResponse {
//here it is
print(httpResponse.statusCode)
}
}.resume()
I have never had the cast fail to my knowledge. I am not sure why URLResponse and HTTPURLResponse aren't merged into one class, but HTTPURLResponse is a subclass that the response always can be cast to as far as my knowledge goes.

Related

Alamofire error with JSON response

I have been struggling with Alamofire issue for days and I'm not sure if it's from the backend or the way I structure my code.
Here is the issue:
when I text my API with Postman I get the correct response if I hit the params button and add the parameters Notice the lower body is empty but the upper one is filled with parameters
However, when I use the lower parameters with the same info I get an error with no JSON. Also same error appears in Xcode when I try to call the API with the same link
this is Xcode Alamofire error
FAILURE: responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
and here is Postman error
and finally this is my code to call API:
let urlStr = "api/client/meal_add"
let url = URL(string: urlStr)
let para = ["meal_id": "31",
"user_id": "2",
"qty":"2"]
let headers: HTTPHeaders = [
"Auth": "Auth" // Edited for security
]
Alamofire.request(url!, method: .post, parameters: para,encoding: URLEncoding.default, headers: headers).responseJSON { response in
print(response)
print(response.result.debugDescription)
if let value : AnyObject = response.result.value as AnyObject {
it used to work perfectly 5 days ago but I have no idea why it stoped. Also In other View Controllers I do HTTP calls to the same API and the responses have no issues, only this one.
First, I want to talk about the difference between HTTP get and post request. Get request is putting parameters in url. They are appended to the url in this format
URL?key1=value1&key2=value2
The RESTful hanlder for that URL will then parse the key value pair and use them. When you click on the param button in your postman, it actuall doing this append param to url thing. However this get into problem when you have lots of param. Usually, an url will have some problem if it exceeds length 8000. So a post request is actually nesting the parameters inside the request, not on the url. So normally in a post request, you will only see the URL, not the ?key1=value1&key2=value2 part.
Back to your problem, I have no idea how your server side handles this post request with parameters in url but since an empty post request with param in url works, in your swift code, you can simply append the param in your url and do a post request with no parameters.
let urlStr = "api/client/meal_add?meal_id=31&user_id=2&qty=2"
let url = URL(string: urlStr)
Alamofire.request(url!, method: .post, parameters: [],encoding: URLEncoding.default, headers: headers).responseJSON { response in
Moving forward, I strongly recommand you to have a look at your server side to make sure your post request is written in the correct way

Preventing NSURLSession default HTTP headers

I'm trying to craft a very specific HTTP request to a server (ie. defining the exact set of HTTP headers), but NSURLSession keeps "helpfully" inserting a bunch of HTTP headers like Accept, Accept-Language and Accept-Encoding.
Consider the following playground (Swift 2.x) which sends a request to a service that just echos the HTTP headers that were sent:
import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let url = NSURL(string: "http://httpbin.org/headers")!
let request = NSMutableURLRequest(URL: url, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 30000)
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let session = NSURLSession(configuration: configuration)
let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) in
print(NSString(data: data!, encoding: NSUTF8StringEncoding))
XCPlaygroundPage.currentPage.finishExecution()
}
task.resume()
You can see that there are three Accept headers being sent. How can I prevent that?
I've tried setting the header using request.setValue(nil, forHTTPHeaderField: "Accept-Language") but that gets ignored. Tried setting it to "", but no good. I've also tried manipulating the HTTPAdditionalHeaders property on NSURLSessionConfiguration, but no love.
How do I get NSURLSession to not be quite so helpful?
I doubt what you're asking for is possible. NSURLSession (and NSURLConnection) automatically provide a number of headers, and that's one of them.
There's also no valid reason to remove them. All three of those headers have been part of the spec since the original HTTP/0.9 spec (https://www.w3.org/Protocols/HTTP/HTRQ_Headers.html). There's absolutely no excuse for any server not either handling those correctly or ignoring them outright.
With that said, if you provide the wrong value for those fields (and the default may be wrong), the server may refuse to give you results. To solve that problem, first figure out what type of data the server is actually going to provide, and specify that value in the Accept header instead of the default.
For example, you might set Accept to "application/json" or one of the other variants (What is the correct JSON content type?) if you're expecting JSON data.
That said, if you really must avoid having those headers sent, you can always open a socket, construct the request manually, and send it. Assuming the server doesn't require chunked encoding, it is pretty easy to do. (Chunked encoding, however, is a horror of Herculean proportions, so if your server sends that back, you'll pretty much have to resort to adding libcurl into your project and using that to make requests instead.)

Try to serialize Object although Validation fails

I use ResponseObjectSerializable like described here:
https://github.com/Alamofire/Alamofire#generic-response-object-serialization
And i want to validate if the status code is in a range
https://github.com/Alamofire/Alamofire#validation
My call looks like this:
Alamofire.request(Router.Something())
.validate(statusCode: 200..<300)
.responseObject { (request, response, object:Object?, error) in
println(object)
println(request)
println(response)
}
My Problem is if the validation fails responseObject anyhow get called and try to serialize the empty response.
How I can handle that without validate the response a second time in my ResponseObjectSerializable?
That's a really good question.
The long story short is that you can't. Your responseObject serializer is not notified about a validation error. It only receives the request, response and data objects and needs to attempt to construct the object from the data.
The ResponseObjectSerializable link you posted does exactly that. If the serialization succeeds, it will return a valid object. If it fails, it will return a nil object and a serialization error.
Where it gets interesting is if you return a serialization error, but the validation also failed. In that case, your completionHandler will actually be called with a nil object, and the validation error, not the serialization error. Alamofire prioritizes the validation error over the serialization error if the validation is run before the responseObject.
As a sidenote, your responseObject serializer should handle the data coming back from the server safely, regardless of the status code that was returned. If parsing the data fails, your serializer should return a serialization error. If it succeeds, then return the object.

Can Alamofire call a different response serializer depending on the content-type of the response?

Depending on its mood and information we send in the request, our server might send back an empty-bodied 204 response with content type text/plain or something else like a 400-series response with an application/json body detailing the error. How should we handle this?
We thought something like this:
Alamofire.request(request)
.responseString({ (request, response, string, error) -> Void in
// Happy dance
})
.responseSwiftyJSON({ (request, response, json, error) -> Void in
// Parse the error out of the json response and inform the user
})
... but it seems all the chained response handlers are executed. Is there a way to say "only call this one for a specific error type" or "only call this one for a specific response code"?
Are we missing something about how Alamofire is meant to work?

Using NSURLSession (in Swift) to get data

I've been searching around to learn how to get some data using NSURLSession in Swift.
There is a general pattern being repeated on the internet for making an http request using NSURLSession, and it looks a lot like this:
let url = NSURL(string: "myPretendURLthatIsNotThisURL.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {(data, response, error) in println(NSString(data: data, encoding: NSUTF8StringEncoding))}
task.resume()
If I place a real URL in, I can see a lot of JSON output printed, which is the expected response from the server. Nice : ) I can see the data from println(). However, I would rather display the returned data in a text view when I press a button. I do believe my solution relies on using a delegate (somehow).
Is it all right to request an example of how this is done, in the following context:
Code is in Swift
Press a button (IBAction, contains the code above to make the request)
Display the data returned (it's JSON, I'll worry how to parse it later on) in a text view.
Thanks!