POST Request with URLSession in Swift in command line app [duplicate] - swift

This question already has answers here:
Using NSURLSession from a Swift command line program
(3 answers)
Closed 2 years ago.
I am trying to send HTTP request with POST method in a command line app. Using JSON as body of the request. I am using session.uploadTask to send this request and use JSON data serialised from simple Dictionary. Maybe I missed something but it doesn't work. I even tried to write my request to console and it looks good -> it is the same format as iTranslate API wants.
//creating new session
let session = URLSession.shared
let url = URL(string: "https://api.itranslate.com/translate/v1")!
//setting httpMethod to POST
var request = URLRequest(url: url)
request.httpMethod = "POST"
//setting header
request.setValue("application/json", forHTTPHeaderField: "content-type")
//dictionary with json
let json = ["key": "...", "source": ["dialect":"en", "text": "How are you?"], "target": ["dialect": "es"]] as [String : Any]
//serialization from json to jsonData
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: [])
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
}
task.resume()

In most apps, there is a “run loop” that is constantly running, responding to user interaction, keeping the app alive until the user explicitly terminates it. In that scenario, we can initiate the asynchronous network request, confident that the run loop is keeping our app alive, and when the network response comes in, our completion handler closure is called.
In a command line app, on the other hand, when it gets to the end of the code, the app simply terminates, never giving the asynchronous network request a chance to respond, and the closure will never be called.
You can, however, have the command line app wait for the request to finish before terminating, e.g.
let semaphore = DispatchSemaphore(value: 0)
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
semaphore.signal()
}
task.resume()
semaphore.wait()
Now, this technique (waiting on the main thread, thereby blocking that thread) is a very bad practice in standard apps, because it will block the main thread while the network request is running. In a typical app, blocking the main thread would freeze the UI, offering a substandard user experience (and the OS might even kill the app for not being responsive). We generally would never block the main thread.
But in a command line app, where you often don't worry about a graceful and responsive UI, blocking the main thread is less of an issue and the above can keep the app alive while the network request is running.

Related

Swift URLSession not working for localhost calls

I'm writing a basic API call in Swift using URLRequests, and for whatever reason my call is never executed. I have multiple calls to an external server API using the same method and the functionality is just as expected, however, for my server running locally I get no response or even behavior within the dataTask closure.
I have tried any relevant solutions I could find online such as: Swift URL Session and URL Request not working and Swift 3, URLSession dataTask completionHandler not called. But none of these solutions seem to fix my issue. I know that the local API is working as any calls through Postman go through without fail, yet even after using the Swift snippet provided by Postman, I get no functionality.
func doFoo(id: String, completion: #escaping ([[Float]]) -> ()) {
let semaphore = DispatchSemaphore(value: 0)
var request = URLRequest(url: URL(string: "127.0.0.1:8080/doFoo/\(id)")!, timeoutInterval: Double.infinity)
request.httpMethod = "GET"
print("THIS IS REACHED")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
print("THIS IS NEVER REACHED")
guard let data = data else {
self.semaphore.signal()
return
}
do {
// Decode json using JSONDecoder
// Call completion with JSON data
} catch {
print(error)
}
self.semaphore.signal()
}
task.resume()
self.semaphore.wait()
}
Other posts suggest that this could be an issue with the thread or execution completing before the closure is executed; while I am not super familiar with how the request executes and the behavior of semaphores, my understanding is that they are a way to request threads and prevent the above from happening.
If anyone more familiar with these topics could help me identify and understand why this issue is occurring, it would be greatly appreciated!

Can timeout function be added when getting a string from a URL?

Sometimes I fetch information from a specific site.
But when the response is slow I would like to add a timeout function. I would like to know how.
Can I add a timeout function to the code below?
html = try String(contentsOf: url, encoding: String.Encoding.ascii)
You are not really supposed to use init(contentsOf:encoding:) to read from a remote URL. This initialiser is synchronous so while it is doing that your app's UI will freeze and the user won't be able to do anything, as you may have noticed.
You are supposed to use URLSession and URLRequest to fetch data from remote URLs. They are asynchronous so you get your data in a completion handler.
You can set a timeout in seconds when you create the URLRequest, and you will get an NSError in the completion handler if it timed out (among other reasons).
var request = URLRequest(url: URL(string: "https://example.com")!,timeoutInterval: 10)
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
let result = String(data: data, encoding: .ascii)
// do something with result
}
task.resume()

pho.to API Request Failing in Swift

Im currently trying to work with the pho.to API in my iOS application. I am experimenting with making simple requests according to the documentation, however I cannot seem to get the request to go through successfully. Inside my API client file, I have this code:
let dataStr = """
<image_process_call>
<image_url>http://developers.pho.to/img/girl.jpg</image_url>
<methods_list>
<method order="1">
<name>desaturation</name>
</method>
<method order="2">
<name>caricature</name>
<params>type=1;crop_portrait=true</params>
</method>
</methods_list>
<thumb1_size>100</thumb1_size>
</image_process_call>
"""
let encodedStr = dataStr.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "")
let signData = encodedStr.hmac(key: key)
let urlStr = "https://opeapi.ws.pho.to/addtask/?app_id=\(appId)&key=\(key)&sign_data=\(signData)&data=\(encodedStr.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!))"
The HMAC encoding is being done according to this Stack Overflow post. Unfortunately when making a request to this URL using URLSession I get this response:
<?xml version=\"1.0\"?>\n<image_process_response><status>SecurityError</status><err_code>614</err_code><description>Error in POST parameters: one or more parameters (DATA , SIGN_DATA or APP_ID) are empty</description></image_process_response>
I feel like my issue is more related to actually forming the request rather than something specific to the API itself. I know my code is a little messy, however I was hoping that somebody could point me in the right direction in terms of making a request like this. Thanks!
As per their documentation you can see that data sent over from POST requests are in body (In cURL calls -d specifies the body of the request)
You are sending params/data in query, which the pho.to API doesn't accept, hence the error.
Here's a sample on how you can do:
let defaultSessionConfiguration = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: defaultSessionConfiguration)
// Setup the request with URL
let url = URL(string: "https://opeapi.ws.pho.to/addtask")!
var urlRequest = URLRequest(url: url)
// Convert POST string parameters to data using UTF8 Encoding
let postData = yourXMLString.data(using: .utf8)
// Set the httpMethod and assign httpBody
urlRequest.httpMethod = "POST"
urlRequest.httpBody = postData
// Create dataTask
let dataTask = defaultSession.dataTask(with: urlRequest) { (data, response, error) in
// Handle your response here
}
// Fire the request
dataTask.resume()

Making Repeated Requests to API

I know how to make a regular API call using swift. What I am not able to understand is how to make the API call to be repeated until required.
I want to call the API every one second
API Call Code Snippet:
let url = URL(string: "https://api.darksky.net/forecast/34eaef38915078ea03c22bb9063bd7ea/37.8267,-122.4233")
let request = URLRequest(url: url!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 10)
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: OperationQueue.main)
let task: URLSessionDataTask = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let error = error {
print(error)
} else if let data = data,
let dataDictionary = try! JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
print("API Data:")
print(dataDictionary)
}
})
task.resume()
Note: This is not the actual API I will be calling
Ideally, for software solutions like financials you mentioned, the server must have support for some sort of Long Polling / websockets mechanism where once connection is established server feeds the client with new values whenever there are updates (refer : https://stackoverflow.com/a/12855533/1436617)
If server does not support : (Not the ideal solution) :
You can actually use recursion in this. On response (both success & failure) of the request again call the same function. That way you can continuously keep polling.
Remember to keep request timer short (5 or 10 seconds instead of 60 seconds) so that if there happens to be an network issue you can quickly make the next call.

How to get data by URL in swift? [duplicate]

var arrayData: [String] = []
let bodyData = "parameter=test"
let URL: NSURL = NSURL(string: "Link to php file")
let request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
var output = NSString(data: data, encoding: NSUTF8StringEncoding)
self.arrayData = self.JSONParseArray(output)
println(self.arrayData) //Print the result
}
println(self.arrayData) //Print nothing
It look like the new value is only available in sendAsynchronousRequest
Is there a way to make the new value accessible out of the sendAsynchronousRequest ?
sendAsynchronousRequest is, as the name suggests, asynchronous. So your final println runs before the request is complete. After the completion handler runs, the new data will be available for all readers (in or outside this handler).
sendAsynchronousRequest requires a callback passed as argument, which is executed once the asynchronous request has been completed. That's out of the linear flow in your code.
This is what happens:
you call sendAsynchronousRequest
the request is started, but the function returns immediately (it doesn't wait for the request to be completed)
the println line is executed next (last line in your code)
some time later, the asynchronous request is completed, and the closure is executed
the closure assigns a value to self.arrayData
If you want to use the updated value, you have to change the logic a little bit. You can call another closure or a class method (meaning an instance method, not a static one) to post process that variable once it has been set.