Preventing NSURLSession default HTTP headers - swift

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

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

NSURLSession data task response handling

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.

How to make sure that an Alamofire request runs?

I copy-pasted the first example of the Alamofire readme (at fa3c6d0) into main.swift:
import Foundation
import Alamofire
Alamofire.request("https://httpbin.org/get").responseJSON { response in
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
print("Done")
When I run this, all I get is Done, then the application terminates.
While I see here that I can pick a dispatch queue, this answer seems to suggest that I shouldn't have to.
Anyway, having had a similar issue with "basic" requests I tried the same solution but to no avail: the application now blocks. So, apparently, Alamofire has a different default than URLSession and wants to use the main thread.
What is the best way to have a request executed (and waited for) in an application like this?
We need to do two things.
Execute the request in the background.
Block the main thread until the request is done, i.e. the completion handler ran.
My original code does neither.
The first item is achieved by using .response(queue: DispatchQueue(label: "some-name")) (or one of its variants).
Waiting can be done in several ways.
Using a flag and active waiting, as shown here (won't scale to more than one request).
Use active waiting with countdown latch as shown here (works for multiple requests).
Use DispatchSemaphore as seen e.g. here.
And probably many more.

configuration for NSMutableURLRequest, that cachePolicy is always ReloadIgnoringLocalAndRemoteCacheData

In my project I'm working with NSMutableURLRequest. Sometimes there are mistakes because of caching.
So I did
let mutableURLRequest = makeURLRequestFrom(url: url, httpMethod: "GET", httpHeaders: httpHeaders, parameters: parameters)
mutableURLRequest.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
This is working fine. Now I don't want to set the cachePolicy for every NSMutableURLRequest. Is there a possibility to set a standard config for the cachePolicy?
For UI-elements I can set configs in the Appdelegate for the whole project, too.
Like this:
UILabel.appearance().textColor = UIColor.red()
Maybe there is a similar solution for the cachePolicy? (I can't find yet)
If you're using NSURLSession, you can specify a per-session cache policy. For NSURLConnection, I think the best you could do would be to modify the shared NSURLCache object and set its in-memory and on-disk sizes to zero, though I can't guarantee that the OS will honor that.

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!