How to return HTTP result synchronously in Swift3? - http

I have this code which- as far as I understand- is async:
func testConnection() -> Bool {
let url = URL(string: uri)!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
guard let data = data, let _:URLResponse = response , error == nil else {
print("error")
return
}
let dataString = String(data: data, encoding: String.Encoding.utf8)
}
task.resume()
}
How would a synchronous version look that allows to return the testConnection result to the sender?

You could use a semaphore to wait for the async call to finish:
func testConnection() -> Bool {
let url = URL(string: uri)!
var dataStringOrNil: String?
let semaphore = DispatchSemaphore(value: 0)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
defer {
semaphore.signal()
}
guard let data = data, error == nil else {
print("error")
return
}
dataStringOrNil = String(data: data, encoding: .utf8)
}
task.resume()
semaphore.wait()
guard let dataString = dataStringOrNil else {
return false
}
// some substring checking
return dataString.contains("href")
}
I wouldn't recommend it, but there can be reasons for sync calls. We use them sometimes in command-line tools.

Related

How to return both Response.text() and Response from async function?

I am trying to build an program that performs async http requests and returns their Response + body.
This is what the function that returns the Response looks like:
let responses = stream::iter(urls)
.map(|line| {
let client = &client;
async move {
client.get(&line).send().await.map(|resp| {
(line, resp)
})}
})
.buffer_unordered(concurrency_amount);
However, after returning resp, I can't use resp.text(), since resp.text() is of type Future<Output=Result<String>>.
How can I make the function also return resp.text() in the tuple?
Assuming that you are using reqwest, it should be possible to collect the bytes of the response body with Response::chunk(), text() consumes self but chunk() only takes a mutable reference.
Something like the following collects the response body and decodes it to a String in a lossy manner.
use futures_util::StreamExt;
#[tokio::main]
async fn main() {
let cli = reqwest::Client::new();
let urls = vec![
"https://stackoverflow.com".to_string(),
"https://google.com".into(),
"https://tokio.rs".into(),
];
let responses = futures_util::stream::iter(urls.into_iter())
.then(|url| { // note that I replaced `map` with `then` here.
let cli = cli.clone();
async move {
let mut resp = cli.get(url.clone()).send().await.unwrap();
let mut body = Vec::new();
while let Some(chunk) = resp.chunk().await.unwrap() {
body.extend_from_slice(&*chunk);
}
(url, resp, String::from_utf8_lossy(&body).to_string())
}
})
.collect::<Vec<_>>()
.await;
for (url, response, text) in responses {
println!("url: {} status: {} text: {}", url, response.status(), text);
}
}
As the in-line comment notes: I changed the map() call to then() so the stream yields the tuples rather than futures with the tuples as output.
Does this work?
let responses = stream::iter(urls)
.map(|line| {
let client = &client;
(async move || {
let resp = client.get(&line).send().await?;
let text = resp.text().await?;
(line, resp, text)
})()
})
.buffer_unordered(concurrency_amount);

SwiftUI HTTP Post Request

JSONService
During the http request process, completion nil is returned. completion does not work.. but print function is working. how can i fix this problem. I am using http request for the first time. my English is bad. I'm sorry for that.
class JSONService {
let parameters = ["index": "articles"]
func getNews(completion: #escaping (NewsRoot) -> ()) {
guard let url = URL(string: "http://yazar.io/api/v2/search/") else { return }
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard error == nil else { return }
guard let data = data else { return }
do {
let news = try JSONDecoder().decode(NewsRoot.self, from: data)
DispatchQueue.main.async {
completion(news) //does not work
print(news) //works
}
} catch let error {
print(error.localizedDescription)
}
}
task.resume()
}
}
DataStore
class DataStore: ObservableObject {
#Published var news: [News] = []
init() {
getNews()
}
func getNews() {
JSONService().getNews { (news) in
self.news = news.articles
}
}
}
ContentView
#ObservedObject var news = DataStore()

Can't get HTML from URL that has google script at bottom?

https://www.pickleballtournaments.com/oncourts.pl?tid=2027&venue=Main
you can view that source and see a near bottom...
Was able to get html before now there is a google script near the bottom and htmlString is now nil. Anyone can get this html? of course gets html for other sites! so the is the blocking issue here! hope we can get the html again please and thank you. Mike
'/table>
'script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');ga('create', 'UA-82207385-1', 'auto');ga('send', 'pageview');
override func viewDidLoad() {
super.viewDidLoad()
var url = URL(string: "https://www.pickleballtournaments.com/oncourts.pl?tid=2027&venue=Main")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error)
} else {
let htmlContent = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("28 htmlContent: \(htmlContent)")
}
}
task.resume()
28 htmlContent: nil
works great gets data with that line in the html content!
override func viewDidLoad() {
super.viewDidLoad()
var url = URL(string: "https://www.pickleballtournaments.com/oncourts.pl?tid=2027&venue=Main")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error)
} else {
let htmlContent = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
let data = data?.html2String
print("28 data: \(data)"
}
}
task.resume()
}

Decode Cloud Firestore data result item to NSObject

I've a cloud firebase result item:
let mpUserRef = db?.collection(MPUser.PROPERTY_DB)
.whereField(MPUser.PROPERTY_FIREBASE_USER_ID, isEqualTo: firebaseUSer.uid)
.limit(to: 1).getDocuments() { (document, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in document!.documents {
let mpUser = fromJsonToMpUser( userJson: "\(document.data())" )
if mpUser != nil{
delegate.mpUser = mpUser
}
}
}
}
And i want to decode result item in my business Object calling the method:
class func fromJsonToMpUser( userJson: String ) -> MPUser?{
let decoder = JSONDecoder()
do{
let json = userJson.data(using: .utf8)!
let mpUser = try decoder.decode(MPUser.self, from: json)
return mpUser
}catch{
print("Errore while ecode Club")
return nil
}
}
But this does not work, the error message that i have is:
"The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Badly formed array around character 17." UserInfo={NSDebugDescription=Badly formed array around character 17.}))).
So, how can i converte cloud firestore item result in valid Json?
I've resolved my question using CodableFirebase library ( https://github.com/alickbass/CodableFirebase created by #alickbass )

HTTP POST error Handling in Swift 2

I'm new here, and this is my first question...
I try to write an App in Swift 2 that makes an HTTP POST request but I can't figure out how to use the new error handling of Swift 2. Can anyone tell me please how to implement the "do-try-catch" error handling of Swift 2 to the code snippet below?
(This code snippet uses the old error handling of swift 1.2)
func post(params : Dictionary<String, String>, url : String) {
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil/*, error: &err*/)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
var strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Body: \(strData)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves/*, error: &err*/) as? NSDictionary
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
if(err != nil) {
print(err!.localizedDescription)
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
}
else {
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
var success = parseJSON["success"] as? Int
print("Succes: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: \(jsonStr)")
}
}
})
task!.resume()
}
You presumably want to wrap your NSJSONSerialization calls in do/try/catch logic as shown below.
In Swift 3:
var request = URLRequest(url: URL(string: urlString)!)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = try! JSONSerialization.data(withJSONObject: parameters)
// or if you think the conversion might actually fail (which is unlikely if you built `parameters` yourself)
//
// do {
// request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
// } catch {
// print(error)
// }
let task = session.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error: \(error)")
return
}
// this, on the other hand, can quite easily fail if there's a server error, so you definitely
// want to wrap this in `do`-`try`-`catch`:
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
let success = json["success"] as? Int // Okay, the `json` is here, let's get the value for 'success' out of it
print("Success: \(success)")
} else {
let jsonStr = String(data: data, encoding: .utf8) // No error thrown, but not dictionary
print("Error could not parse JSON: \(jsonStr)")
}
} catch let parseError {
print(parseError) // Log the error thrown by `JSONObjectWithData`
let jsonStr = String(data: data, encoding: .utf8)
print("Error could not parse JSON: '\(jsonStr)'")
}
}
task.resume()
Or, in Swift 2
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(parameters, options: [])
// or if you think the conversion might actually fail (which is unlikely if you built `parameters` yourself)
//
// do {
// request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: [])
// } catch {
// print(error)
// }
let task = session.dataTaskWithRequest(request) { data, response, error in
guard let data = data where error == nil else {
print("error: \(error)")
return
}
// this, on the other hand, can quite easily fail if there's a server error, so you definitely
// want to wrap this in `do`-`try`-`catch`:
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary {
let success = json["success"] as? Int // Okay, the `json` is here, let's get the value for 'success' out of it
print("Success: \(success)")
} else {
let jsonStr = String(data: data, encoding: NSUTF8StringEncoding) // No error thrown, but not NSDictionary
print("Error could not parse JSON: \(jsonStr)")
}
} catch let parseError {
print(parseError) // Log the error thrown by `JSONObjectWithData`
let jsonStr = String(data: data, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
}
}
task.resume()
I'd also suggest being a little more careful about forced unwrapping of data, because you want to detect/handle the errors, not crash. For example, above I use a guard statement to unwrap it.
In general if a function throws you have to write it inside a do catch block or just mark the outer scope function (in this case post) as throws:
func post(params : Dictionary<String, String>, url : String) {
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: .PrettyPrinted)
} catch {
//handle error. Probably return or mark function as throws
print(error)
return
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// handle error
guard error == nil else { return }
print("Response: \(response)")
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Body: \(strData)")
let json: NSDictionary?
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary
} catch let dataError {
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
print(dataError)
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
// return or throw?
return
}
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
let success = parseJSON["success"] as? Int
print("Succes: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: \(jsonStr)")
}
})
task!.resume()
}
If you don't want to handle these errors right in your post function you can just declare it as throws than you don't have to use do catch at all

Resources