Swift HTTP POST request with Combine - swift

In my app, I am using the Combine framework to make network requests and it works fine for GET requests. But I am running into this issue with POST requests.
The following code (without Combine) works fine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error)")
return
}
print("Response: \((response as! HTTPURLResponse).statusCode)")
}
task.resume()
I get a 200 response code from the above code.
The same code with Combine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let cancellable = URLSession.shared
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
Here is the output I receive:
receive subscription: (DataTaskPublisher)
request unlimited
receive cancel
Seems like the request with the Combine framework is somehow getting cancelled? I used a network tracing app and I can see the first request (without Combine), but not the second request.
I have looked at numerous posts and documentation, but can't see what's wrong with my code. What am I missing? Thanks in advance.

I see you are creating the cancellable, but are you keeping a strong reference to it? It looks like you are creating the request, and then the cancellable goes out of scope and is deallocated, which cancels the request.
Declare something like this in your class:
var cancellables = Set<AnyCancellable>()
and then you implicitly store the cancellable as:
URLSession.shared // You don't hold on to the returned cancellable here
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
.store(in: &cancellables) // This puts the cancellable into the variables so it stays in scope

Related

How to use POST request to Firebase Firestore from swift?

I would like to use the Firestore REST API from Swift, because I am using Siri Shortcut Intents, where I am not able to use the native SDK.
What I have tried so far is to create an URLSession with "POST" httpmethod, but no luck. I have been able successfully to create document to use the form found on firestore website. But I could make successful Swift version of it.
Here is the code I have tried:
private func addTask() {
let parent = "projects/reality-kanban/databases/(default)/documents/l3VXrtTLoz11VGn60ott"
let collectionId = "A33XrtfL2ea3dG340era"
let urlString = "https://firestore.googleapis.com/v1/\(parent)/\(collectionId)"
let requestBody = DocumentBody(name: parent, fields: RequestTask(description: "test")) // it is a codable struct
let jsonData = try! JSONEncoder().encode(requestBody)
print(String(data: jsonData, encoding: .utf8)!)
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Invalid Response received from the server") // this is what I get
return
}
}
task.resume()
}
This is the error I get: Invalid Response received from the server (400)
Add --debug to the command you are running to corroborate if you have set the right project.

error to send Authorization headers in swift 3 don't work, (the server dont' receive the Authorization headers )

I have the problem, my server doesn't receive the header Authorization,
I try with this code but doesn't work.
This is my code:
let token: String? = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MjE3NDc3OTYsImV4cCI6MTUyNDMzOTc5NiwiZGF0YSI6eyJpZCI6IjM2IiwiaWRfZW1wcmVzYSI6IjEiLCJub21icmVzIjoiU2VyZ2lvIEFsZWphbmRybyIsImFwZWxsaWRvX3BhdGVybm8iOiJSb3NhZG8iLCJhcGVsbGlkb19tYXRlcm5vIjoiQXp1bCIsInpvbmFfaG9yYXJpYSI6IkFtZXJpY2FcL01lcmlkYSIsImlkX3BlcmZpbCI6IjEiLCJjb3JyZW8iOiJydXNzZWxhbGV4aXMxMjNAZ21hLmNvbSIsInVzZXJuYW1lIjoiZW1wbGVhZG9kZW1vIiwicGFzc3dkIjoiKkE0QjYxNTczMTkwMzg3MjRFMzU2MDg5NEY3RjkzMkM4ODg2RUJGQ0YiLCJhY3Rpdm8iOiIxIiwiZmhfY2FwdHVyYSI6IjIwMTctMDItMjAgMjE6MDI6NTUiLCJkb21pY2lsaW8iOiIiLCJjcCI6IiIsInRlbGVmb25vIjoiIiwidXN1YXJpbyI6IjEiLCJpcCI6IjE4OS4xNTAuMTQxLjExNyJ9fQ.79HJPR04IjHBwLzpeUnjJel0UAYSG0rtqPvOPca7Uds"
guard let name = token, !name.isEmpty else { return }
let url = URL(string: "http://dev.viupruebas.com.mx/webservice/test/getvehiculos")
var request = NSMutableURLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Bearer \(name)", forHTTPHeaderField: "Authorization")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
i resolve my problem read the nex line:
let task = URLSession.shared.dataTask(with: request) { data, response,
error in
my finaly code is:
let token: String? = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MjE4MjY2MTgsImV4cCI6MTUyNDQxODYxOCwiZGidXN1YXJpbyI6IjEiLCJpcCI6IjE4Ny4xNTUuOTIuMjEzIn19.pu6_1LmfxliXke_WMY82hEKy0Dsn1fk-feaTKnfGpbo"
guard let name = token, !name.isEmpty else { return }
//url
let url = URL(string: "http:/webservice")!
var request = URLRequest(url: url)
request.addValue("\(name)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in

Post data using URLSession

When i try from Alamofire then it work fine but when i try to solve from URLSESSION Swift 4 then i got wrong response.
I checked in postman and it's response was right.
Parameter Description:
I have a key "data" whose value is another dictionary ["answer1":"1","answer2":"2","answer3":"3"]. Need to post this.
Wrong Reposnse = {"message = "Invalid data."; response = failure;}"
Right Reposnse = {"response":"success","message":"Data Inserted”}.
func postData()
{
let BASEURLS = "http://sebamedretail.techizer.in/babystore_api/question_data"
let parameter = ["data":["answer1":"1","answer2":"2","answer3":"3"]]
let session = URLSession.shared
var request = URLRequest.init(url: URL.init(string: BASEURLS)!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do
{
request.httpBody = try JSONSerialization.data(withJSONObject:parameter, options: [])
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
do{
let responseArr = try! JSONSerialization.jsonObject(with: data!, options: [])
}
})
task.resume()
}
catch
{}
}
Everything seems fine, maybe you should check the way you extract the JSON in your code, if it's PHP, here could be a solution: https://stackoverflow.com/a/18867369/7452015

Get value from callback function swift

Question I want to get the value returned from my ApiToken function so I can use it in another function. For some reason I can not get the value from this function it will not return anything. How could I return the value from my ApiToken function and use it in another function.
Here is my GetApiToken class with the ApiToken function
class GetApiToken {
public func ApiToken(link: String, completionBlock: #escaping (String) -> Void) -> Void
{
let url = URL(string: link)!
let jsonDict = ["username": "snow", "password": "ssssssssss"]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonDict, options: [])
var request = URLRequest(url: url)
request.httpMethod = "post"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error:", error)
return
}
do {
guard let data = data else { return }
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] else { return }
//self.token = json["access_token"] as? String ?? "x"
completionBlock((json["access_token"] as? String)!)
} catch {
print("error:", error)
}
}
task.resume()
}
}
Here is where I am trying to get the value
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.httpMethod = "GET"
var output = ""
GetApiToken().ApiToken(link: "http://localhost:5000/auth", completionBlock: { str in
output = str
})
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type") ..........
It's an asynchronous call, so you need to put everything that will happen once the data has been retrieved in the completion callback
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringCacheData,
timeoutInterval: 20)
request.httpMethod = "GET"
GetApiToken().ApiToken(link: "http://localhost:5000/auth",
completionBlock:
{ output in
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
.......
})

NSURLSession parameters not recognized

Im attemping to make a HTTPRequest using NSURLSession. When I set the full url the request returns the correct data but when using parameters (NSJSONSerialization.dataWithJSONObject -> HTTPBody I get this error
error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost."
is there something im doing wrong here?
let json = ["api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(json, options: .PrettyPrinted)
let url = NSURL(string: "https://api.themoviedb.org/3/discover/movie")!
let request = NSMutableURLRequest(URL: url)
request.HTTPBody = jsonData
request.HTTPMethod = "GET"
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){ data, response, error in
if error != nil{
print("Error -> \(error)")
return
}
do {
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]
print("Result -> \(result)")
} catch {
print("Error -> \(error)")
}
}
task.resume()
} catch {
print(error)
}
}
This is not a duplicate! I looked at the suggested answer (none of them worked) before asking this question
In your case that issue can be solved by changing the request.HTTPMethod = "GET" to request.HTTPMethod = "POST"
You should not send HTTP Body in the get request, to send the data with the body you should change HTTPMethod to post
Note: Please check if this api method supports POST requests, if it don't support post you can't use it with http body/post, as per doc i only find 'get' request for the discover/movie which can be like this:
let url = NSURL(string: "http://api.themoviedb.org/3/discover/movie?api_key=YOUR_API_KEY")!
let request = NSMutableURLRequest(URL: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
print(response)
print(String(data: data, encoding: NSUTF8StringEncoding))
} else {
print(error)
}
}
task.resume()
Ref: You can check more information from this url: http://docs.themoviedb.apiary.io/#reference/discover/discovermovie/get