I am obviously missing something very fundamental/naïve/etc., but for the life of me I cannot figure out how to make simple GET requests.
I'm trying to make an HTTP GET request with Swift 5. I've looked at these posts/articles: one, two, but I can't get print() statements to show anything. When I use breakpoints to debug, the entire section within the URLSession.shared.dataTask section is skipped.
I am looking at the following code (from the first link, above):
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}
task.resume()
}
HTTP_Request()
I am running this in a MacOS Command Line Project created through XCode.
I would greatly appreciate any help I can get on this, thank you.
Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,
func httpRequest() {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
That should at least give you some indication of the problem.
A few other considerations:
If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.
For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:
func httpRequest(completion: #escaping () -> Void) {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
let task = URLSession.shared.dataTask(with: url) { data, response, error in
defer {
DispatchQueue.main.async {
completion()
}
}
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}
print(string)
}
task.resume()
}
var finished = false
httpRequest {
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: .distantFuture)
}
In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.
If playground, you have to set needsIndefiniteExecution.
By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.
In this case, you should just use https and avoid that consideration altogether.
Make sure the response get print before exiting the process, you could try to append
RunLoop.main.run()
or
sleep(UINT32_MAX)
in the end to make sure the main thread won't exit. If you want to print the response and exit the process immediately, suggest using DispatchSemaphore:
let semphare = DispatchSemaphore(value: 0)
func HTTP_Request() {
let url = URL(string: "http://www.stackoverflow.com")!
let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
semphare.signal()
}
task.resume()
}
HTTP_Request()
_ = semphare.wait(timeout: .distantFuture)
This works for me many times I suggest you snippet for future uses!
let url = URL(string: "https://google.com")
let task = URLSession.shared.dataTask(with: ((url ?? URL(string: "https://google.com"))!)) { [self] (data, response, error) in
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: [])
print(jsonResponse)
guard let newValue = jsonResponse as? [String:Any] else {
print("invalid format")
}
}
catch let error {
print("Error: \(error)")
}
task.resume()
}
Related
I am trying to do some parsing of HTML on client side using Swift inside Xcode Project. I first tested this function inside playgrounds for a variety of URLs, and it downloads instantly for all my use cases. However, running this inside my Xcode project for iOS (even when disabling ATS in my info.plist), the URLSession will not download anything for many of the URLs to common websites that worked in playgrounds. It will still download some, such as the html of apple.com. Can anybody explain what I might be missing or need to enable/disable to get this to work.
func fetchHTMLString(url: URL) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
print("String here")
self.sortData(htmlString: string)
} else {
print("couldnt get as string")
}
}
}
task.resume()
print("going")
}
Update, I attempted to change this function to use URLSession data task, and was able to successfully download. I am, however, now just curious to find out why this would allow the download to complete with data task and not with download task. Here's the code that works
func fetchHTMLString(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error \(error.localizedDescription)")
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
if let string = String(data: data, encoding: .utf8) {
print(string)
self.sortData(htmlString: string)
print("String here")
} else {
print("couldn't get as string")
}
} else {
print("Error \(response.statusCode)")
}
} else {
print("No data or error returned.")
}
}
task.resume()
print("going")
}
```
I was trying to build a program that I would regularly run to check if a website has changed. It's working in Swift Playgrounds, however, if I try to just copy it over to a .swift file and to then run it directly in the terminal, it doesn't return a response. In fact, it doesn't seem to be doing anything.
Here's my code:
func getSite(website: String) {
let url = URL(string: website)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if let data = data, let host = response?.url?.host, let scheme = response?.url?.scheme {
print("Retrieved data from \(host) over \(scheme)...")
let site = String(data: data, encoding: .utf8)
print(site)
}
}
}
task.resume()
}
Any ideas?
Thanks!
Ok, I just figured it out - Alexander's comment was a huge help.
I just needed to set up a semaphore:
var semaphore = DispatchSemaphore(value: 0)
and semaphore.wait() right after running the async thread. At the end of the completion handler, I put semaphore.signal(). That's it!
I am trying to figure out the simplest way to make an HTTP request in Swift 4 from the command line. I have copied this code from the URLSession programming guide, and added a couple print statements. I can't figure out why the .dataTask is not executing.
print("Testing URLSession")
let sessionWithoutADelegate = URLSession(configuration: URLSessionConfiguration.default)
if let url = URL(string: "https://www.example.com/") {
print("Encoded url \(url)")
(sessionWithoutADelegate.dataTask(with: url) { (data, response, error) in
print("Executing dataTask")
if let error = error {
print("Error: \(error)")
} else if let response = response,
let data = data,
let string = String(data: data, encoding: .utf8) {
print("Response: \(response)")
print("DATA:\n\(string)\nEND DATA\n")
}
}).resume()
}
The objective is to retrieve data from a REST api, but I can't even make a simple GET request to work properly...
I finally figured out how to make it work using CFRunLoop:
let runLoop = CFRunLoopGetCurrent()
let task = session.dataTask(with: request) { (data, response, error) in
print("Retrieved data")
CFRunLoopStop(runLoop)
}
task.resume()
CFRunLoopRun()
print("done")
I am trying to write a basic function to make an HTTP Get request and parse the xml data that comes back. I already have completed the section for parsing XML from a local file, but i can't seem to get any data from the server. I tested this code below, but the completion block does not even run, for information to be passed back from the server. Any suggestions please.
func getDatafromURL (url: String) {
guard let urlforRequest = NSURL(string: url) else {
print("Error: cannot create URL")
return
}
let request = NSURLRequest(URL: urlforRequest)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
print(response)
print(error)
}
task.resume()
}
I've been scouring examples looking to pull some ideas together, I've come up with this although I'm not getting any output. It never enters the do which leads me to believe I have an issue with my call.
Can anyone shed some light on this for me or lead me to an appropriate location with more information on API calls in swift 2.0? Examples of this are quite sparse.
let url : String = "http://www.fantasyfootballnerd.com/service/nfl-teams/json/test/"
let request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
print("Start")
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult: NSDictionary! = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
print("In method")
if (jsonResult != nil) {
// process jsonResult
print("Data added")
} else {
print("No Data")
// couldn't load JSON, look at error
}
}
catch {
print("Error Occured")
}
}
You're missing just one thing. You need to start the request:
// call this after you configure your session
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// process results
}.resume()