URLSession.Datatask returns 0 bytes of data - ios

Trying to figure this one out, I'm stumped. When making a REST call to get json data back from a response (GET or POST, each should return data) I get back 0 bytes.
This is pre-serialization. The POST successfully creates a message on the backend, and the backend shows a response being sent; with charles proxy on, I've confirmed that there is a response with valid JSON data.
Any ideas why this would be failing ONLY in iOS? Postman/Charles proxy (from the iOS calls!) shows valid data in the response, but the debugger picks up nothing.
Thanks in advance for anything thoughts.
let components = URLComponents(string: "mysuperValidURL.com")
guard let url = components?.url else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
setUrlRequestToken(request: &request)
let message = ChatMessage(content: message, group: group, userId: userId)
let jsonEncoder = JSONEncoder()
guard let data = try? jsonEncoder.encode(message) else {
return
}
URLSession.shared.uploadTask(with: request, from: data) { (data, response, error) in
// Here there be 0 bytes
}.resume()
}

Data will sometimes come back as 0 bytes in the debugger; add a print with debug description to ensure you're getting data. In this case it was a failure of the debugger mixed with a later serialization error that caused it to appear to be broken.
TLDR; don't trust the realtime debugger, use some prints to sanity check.

Related

HTTP DELETE Works From Browser But Not From Postman or IOS App

When attempting an http request to my rest api, I continually get a 401 error when using the following code. I don not get this error making any other type of request. I have provided the function that makes the request below.
func deleteEvent(id: Int){
eventUrl.append(String(id))
let request = NSMutableURLRequest(url: NSURL(string: eventUrl)! as URL)
request.httpMethod = "DELETE"
print(eventUrl)
eventUrl.removeLast()
print(self.token!)
request.allHTTPHeaderFields = ["Authorization": "Token \(self.token)"]
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
return
}
print("response = \(String(describing: response))")
}
task.resume()
}
When sending the delete request with postman, the rest api just returns the data I want to delete but does not delete it. For reference I have posted the view and permissions classes associated with this request Any help understanding why this may be resulting in an error is greatly appreciated!
Views.py
class UserProfileFeedViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating profile feed items"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.ProfileFeedItemSerializer
queryset = models.ProfileFeedItem.objects.all()
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
Permissions.py
class UpdateOwnStatus(permissions.BasePermission):
"""Allow users to update their own status"""
def has_object_permission(self, request, view, obj):
"""Check the user is trying to update their own status"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.user_profile.id == request.user.id
HEADER SENT WITH DELETE REQUEST VIA POSTMAN
Preface: You leave out too much relevant information from the question for it to be properly answered. Your Swift code looks, and please don't be offended, a bit beginner-ish or as if it had been migrated from Objective-C without much experience.
I don't know why POSTMAN fails, but I see some red flags in the Swift code you might want to look into to figure out why your iOS app fails.
I first noticed that eventUrl seems to be a String property of the type that contains the deleteEvent function. You mutate it by appending the event id, construct a URL from it (weirdly, see below), then mutate it back again. While this in itself is not necessarily wrong, it might open the doors for racing conditions depending how your app works overall.
More importantly: Does your eventUrl end in a "/"? I assume your DELETE endpoint is of the form https://somedomain.com/some/path/<id>, right? Now if eventUrl just contains https://somedomain.com/some/path your code constructs https://somedomain.com/some/path<id>. The last dash is missing, which definitely throws your backend off (how I cannot say, as that depends how the path is resolved in your server app).
It's hard to say what else is going from from the iOS app, but other than this potential pitfall I'd really recommend using proper Swift types where possible. Here's a cleaned up version of your method, hopefully that helps you a bit when debugging:
func deleteEvent(id: Int) {
guard let baseUrl = URL(string: eventUrl), let token = token else {
// add more error handling code here and/or put a breakpoint here to inspect
print("Could not create proper eventUrl or token is nil!")
return
}
let deletionUrl = baseUrl.appendingPathComponent("\(id)")
print("Deletion URL with appended id: \(deletionUrl.absoluteString)")
var request = URLRequest(url: deletionUrl)
request.httpMethod = "DELETE"
print(token) // ensure this is correct
request.allHTTPHeaderFields = ["Authorization": "Token \(token)"]
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Encountered network error: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse {
// this is basically also debugging code
print("Endpoint responded with status: \(httpResponse.statusCode)")
print(" with headers:\n\(httpResponse.allHeaderFields)")
}
// Debug output of the data:
if let data = data {
let payloadAsSimpleString = String(data: data, encoding: .utf8) ?? "(can't parse payload)"
print("Response contains payload\n\(payloadAsSimpleString)")
}
}
task.resume()
}
This is obviously still limited in terms of error handling, etc., but a little more swifty and contains more console output that will hopefully be helpful.
The last important thing is that you have to ensure iOS does not simply block your request due to Apple Transport Security: Make sure your plist has the expected entries if needed (see also here for a quick intro).

login user with GET request swift

I have created a screen with a text field called customer_number text field and another screen with a text field called password text field. I want to integrate my app with an existing API made by the backend developers. I am new to IOS Development and I don't know how to go about it. How do I make a get request and pass the login credentials for the user to login?
I want to get the customer number from the API and pass it to the app and enable the customer to log in.
I think this question is too big and complex to be replied exhaustively. You didn't tell us about the API. What kind of input does it take? What kind of response?
Supposing the simplest case. You API expects JSON objects as input and respond with another JSON object containing the information you request.
I usually do tasks like this using the NSURLRequest.
let js = ["Username":username, "Password":password]
let session = URLSession.init(configuration: .default)
let url = URL(...)
var req = URLRequest.init(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10)
req.httpMethod = "POST"
// Add some header key-value pairs
req.addValue(..., forHTTPHeaderField: ...)
...
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let responseData = data else { return }
let code = (response as! HTTPURLResponse).statusCode
// Checking for code == 200 states for authorised user. Generally log-in APIs should return some 4xx code if not allowed or non-authorised user.
if code == 200 {
// Now we try to convert returned data as a JSON object
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
// use your json object here, for example checking if contains the user number...
} catch {
// handle errors
}
}
}
task.resume()
I coded this very quickly, please check the correctness of al mechanism!

iOS/Swift & Coinbase Pro API - Subscribe to Websocket Feed

I am attempting to subscribe to a websocket feed using Swift. Per the new Coinbase Pro API documentation for their websocket feed:
To begin receiving feed messages, you must first send a subscribe message to the server indicating which channels and products to receive. This message is mandatory — you will be disconnected if no subscribe has been received within 5 seconds.
The first thing I did was add Starscream to the project to make connecting to websockets easier to implement. I followed the steps on the README and added the delegate methods appropriately.
Next, I successfully sent an HTTP GET request (I get a 200 code in response) by creating a URLSession object and calling dataTask(with: ) after setting up a request, like so:
let session = URLSession.shared
guard let url = URL(string: "https://api.pro.coinbase.com/users/self/verify") else {
print("Could not create URL.")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
let body: [String: Any] = ["type": "subscribe",
"channels": [["name": "heartbeat"],
["product_ids": ["BTC-USD"]]]]
let data = try! JSONSerialization.data(withJSONObject: body,
options: JSONSerialization.WritingOptions.sortedKeys)
request.httpBody = data
let task = session.dataTask(with: request) { (data, response, error) in
// Check for errors, clean up data, etc.
}
task.resume
Everything appears to be linked up correctly, but I am still not receiving the "subscription" messages from the websocket feed. What am I missing?

Swift NSJSONSerialization.JSONObjectWithData() not able to read data for "OK" String only

This might be the strangest thing I have encountered. We have decided to move on from it but I wanted to make a post to try and understand.
So I am grabbing some JSON data from our server and everything seems to work just fine except for the string "OK".
Here is the function :
func getRequest(token:String, url:String, callback:(NSDictionary) -> ()){
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "GET"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if data != nil && response != nil{
do{
let responseTest = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
if responseTest != nil{
let response:NSDictionary = responseTest!
callback(response)
}
} catch let error as NSError{
print("A JSON parsing error occured: \(error)")
}
} else {
callback(NSDictionary())
}
}
task.resume()
}
The data comes back form the call and then the response parses all of the data correctly except for the status "OK" which came back with unable to read data. We changed the status to "Success" and it worked just fine. We tested out a bunch of different words and they all worked just fine except the word "OK" which always came back with Unable to read data. Strangest thing I have every seen. For now we are going to go with a status of "Success" but, I just can't get this out of my head. WHY?!?! What is different about those 2 letters? Anyone else run into this or just have an understanding that I seem to be lacking? It is driving me crazy.
Here is the JSON from one of our attempts:
{"username":"gang_su","status":"0K","status2":"This is much more OK","status3":"OK OK OK","status4":"OK","status5":true,"status6":123,"status7":12345.678,"status8":[1,2,3,4],"status9":[1.02,2.02,3.02,4.02]}
Thanks!!
{ username: req.user.username, status: 'OK', status2: "This is much more OK" }
The above "JSON" has single quotes around OK. This is invalid JSON.
It's not OK, you might say.
They need to be double quotes, as you have used in your other strings.
I just tested this in a playground, and it works fine with your provided example JSON. The logical conclusion is that that's not actually the data you're passing in.
You might want to dump that NSData to the console, or to a file, and check for any unexpected characters.

How to make sure that NSURLSession responses are received in same order as requests are made?

I make few NSURLSession requests in a loop. I'd like to store results from responses in the same order as tasks are created. But since completion handler runs in a separate thread it sometimes happens that the response to the second task gets received before the response to the first task.
How to make sure that I get responses in same order as tasks are being started?
var recivedData = [String]()
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData.append("\(result)") // save the result to array
}
}
task.resume()
}
While I'd discourage behaviour that requires responses to be received in a specific order, you can collate the responses (regardless of the order they are received) into a known order.
The receivedData array should be initialised with a capacity that matches the number of requests that will be made:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
Then when you receive the response, since you're in a block that has access to the index of the request you can add the response data directly into the index of the receivedData array:
receivedData[index] = result as (String)
The full code is as follows:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
// Insert the result into the correct index
receivedData[index] = result
}
}
task.resume()
}
Because you know the exact number of http requests.
You can create an array to the size of urls.count, and then set the result in completion handler, corresponding to the index in each loop.
receivedData = [String](count: urls.count, repeatedValue: "No Data")
for (index,url) in enumerate(urls){
let url = NSURL(string: url)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url){ data, response, error in
if error != nil{
self.receivedData[index] = "error: \(error.localizedDescription)"
return
}
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData[index] = "\(result)"
}
}
task.resume()
}
Actually, can not make sure the order of responses.
There are two workarounds for this:
Send the requests one after another, which means send the next request after the previous response is returned. You can use ReactiveCocoa to make your codes elegant. Or use the networking library I wrote STNetTaskQueue, there is a STNetTaskChain which can handle the requests one after another.
Send the requests parallel(is what you are doing now), and use a NSDictionary to keep track on the request and response, after all requests is finished, combine the response in the original order.
Hope this helps!

Resources