How to handle 500 http errors - swift

I am trying to access the custom server response body for 500 errors in class HTTPURLResponse (URLResponse) using URLSession.shared.dataTask function. I can only have access to statusCode and allHeaderFields but it doesn't seem to help.
The equivalent in java for ex. is HttpURLConnection.getErrorStream(), but I cannot find something similar in pure swift (I would like to solve this without using 3rd party libs).
How can I get the text response for the 500 error?

let task = session.dataTask(with: urlRequest) { data, response, error in
if let data = data, let response = response as? HTTPURLResponse {
switch response.statusCode {
case 500...599:
let yourErrorResponseString = String(data: data, encoding: .utf8)
default:
break
}
}
}

There is no way you can get the response data out of HTTPURLResponse. It only contains header information.
If you want to retrieve the response data, you need to use something like dataTask(with:completionHandler:) to send your request. That function passes (Data?, URLResponse?, Error?) to your completion handler. The data parameter of the completion handler is the data returned by the server.
For example:
import Foundation
let url = URL(string: "http://httpstat.us/500")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, let response = response as? HTTPURLResponse else {
return
}
switch response.statusCode {
case 500...599:
print(String(data: data, encoding: .utf8) ?? "No UTF-8 response data")
default:
print("not a 500")
}
}
task.resume()
Edit: Removed force unwrap according to #Rob‘s suggestion

There is no way to get more details about a 500 error from the client side.
500 is "Internal Server Error" and it's intentionally vague and unhelpful since disclosing information about the cause of the error would assist hackers in compromising the site.
However you can get a great deal of information about the error from the server log and the log for whatever was processing your code on the server side (php, etc.).
If you have access to the server logs and don't see enough information, you can increase the level of logging for the server and application.

Related

URLRequest - "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" Error (Swift)

I am trying to perform an HTTP POST request in swift that will send some data to my server using PHP file, but it crashes with the error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
The token and selectedAreaNames (the error is in the first line) are just regular strings. What could be the problem?
let url = URL(string: "https://xxxxxxx.xxx/register.php/\(token)|\ (selectedAreaNames)")! //error is here...
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error)")
} else {
if let response = response as? HTTPURLResponse {
print("statusCode: \(response.statusCode)")
}
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("data: \(dataString)")
}
}
}
task.resume()
Assuming that’s really how your URL must look, you can do:
let url = URL(string: "https://xxxxxxx.xxx/register.php")!
.appendingPathComponent(token + "|" + selectedAreasNames)
That will percent escape those portions of the URL (including the |).
That having been said, this is an exceedingly unusual format for a POST request, which usually has the data being posted inside the body of the request, not just added as another path component of the URL. And if this was a GET request, where the parameters are added to the URL, you’d generally see this after a ? in the URL, separating the path of the request from the query. And this structure of simply TOKEN|VALUES is an unusual query structure, too.

Swift program never enters CompletionHandler for a dataTask

I am in the process of implementing a REST API with Swift. Of course, part of this API is using HTTP requests to retrieve and send data.
Full disclosure, I am inexperienced with Swift and am using this as a learning project to get my feet wet, so to speak. But it's turned into much more of a difficult project than I anticipated.
In implementing the first get method, I have (finally) gotten rid of all the compilation errors. However, when I call the function which utilizes the URLRequest, URLSession, dataTask, etc, it is never entered.
Upon debugging the program, I can watch the program execution reach the CompletionHandler, and skip over it right to "task.resume()."
A similar construction works in a Swift Playground, but does not work in the actual project proper.
So far I have tried a few things, namely making the function access a class instance variable, in hopes that that would force it to execute. But it does not.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
import Foundation
/**
A class to wrap all GET and POST requests, to avoid the necessity of repeatedly writing request code in each API method.
*/
class BasicRequest {
private var url: URL
private var header: [String: String]
private var responseType: String
private var jsonResponse: Any?
init(url: URL, header: [String: String], responseType: String) {
self.url = url
self.header = header
self.responseType = responseType
} //END INIT
public func requestJSON() -> Any {
// Create the URLRequest object, and fill the header with the header fields as provided.
var urlRequest = URLRequest(url: self.url)
for (value, key) in self.header {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
print("Entered the completion handler")
if error != nil {
return
}
guard let httpResponse = response as? HTTPURLResponse, 200 == httpResponse.statusCode else {
print("HTTP Request unsuccessful")
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Not a JSON response")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
self.jsonResponse = json
} catch {
print("Could not transform to JSON")
return
}
}
task.resume()
return "Function has returned"
} //END REQUESTJSON
}
The expected result would be returning a JSON object, however that does not seem to be the case.
With respect to error messages, I get none. The only log I get in the debugger is the boilerplate "process exited with code 0."
To be truthful, I'm at a loss with what is causing this not to work.
It appears you're writing this in a command-line app. In that case the program is terminating before the URLRequest completes.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
Exactly.
The typical tool in Swift is DispatchGroup, which is just a higher-level kind of semaphore. Call dispatchGroup.enter() before starting the request, and all dispatchGroup.leave() at the end of the completion handler. In your calling code, include dispatchGroup.wait() to wait for it. (If that's not clear, I can add code for it, but there are also a lot of SO answers you can find that will demonstrate it.)

Swift get content text response after send request to web service

I know how to get data response from url. But the data response contains html source. Although I can handle it to get what I need but will be better if I know how to get only text. I use:
let task = URLSession.shared.dataTask(with: request)
{
data, response, error in guard
let data = data, error == nil else
{
// check for fundamental networking error
print(error!)
return
}
result = String(data: data, encoding: .utf8) ?? ""
}
task.resume()
You could do it like this.
let text = String(decoding: data, as: UTF8.self) // Convert data to string
.components(separatedBy: "\n") // Split string into multiple line
.first // Get the first line
Unless the endpoint has an option (like a query parameter) to return only the text, then you will get whatever the server wants to send and you will need to sort it out client side.

Partially downloading data in Swift

I'm trying to develop a download accelerator in Swift. It should get the file's size and divide it to n parts. Then it should download them at once by running multiple threads, and then merge the parts.
I read C# - Creating a Download Accelerator, unfortunately it doesn't help me.
I can do the multiple thread part easily by
DispatchQueue.main.async {
// The new thread
}
but the other part is harder. I usually download a file like this:
try Data(contentsOf: URL(string: assetsUrl!)!)
or I can do the thing that is explained in this answer
class Downloader {
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
}
But this is not C - it's very simplistic and doesn't accept many arguments. How can I make it get "first 200_000 bytes" from the server?
First of all, the server needs to implement HTTP range requests. If it doesn't, and you don't control the server, then you will not be able to do this.
If the server supports HTTP range requests, then you need to specify the range with request headers, as explained here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
The essentials are that you first send a HEAD request to figure out whether the server supports HTTP range requests. This is determined by whether the response includes the Accept-Ranges header, with a non-zero value.
If the server supports HTTP range requests, then you can make a request for the resource, with the Range header set for example to a value of bytes=0-1023 (depends which format the Accept-Ranges header specified, in this case bytes)

How can I retrieve the status message of a request made with Alamofire?

The server that I am using returns error messages in the HTTP status message. For example, it will return "400 User already exists" rather than "400 Bad Request".
I would like to access the string "User already exists" in the response method called by Alamofire. However, I cannot find any way to access this string.
I found this question on StackOverflow already: Swift Alamofire: How to get the HTTP response status code
Unfortunately, no one gives an answer to the question. :(
Here is where Chrome shows where the error is:
I would suggest trying to print out all the possible data fields that you are given and see what you can find. Please try the following example and see if that sheds any light.
let URL = NSURL(string: "your/url/to/somewhere")!
let parameters = ["foo": "bar"]
Alamofire.request(.POST, URL, parameters: parameters)
.response { request, response, data, error in
println("Request: \(request)")
println("Response: \(response)")
println("Error: \(error)")
if let data = data as? NSData {
println("Data: \(NSString(data: data, encoding: NSUTF8StringEncoding)!)")
}
}
Return response in json format from the server and then i think you'll be able to get the appropriate status.
I've implemented that thing using php codeigniter..from where my response is like
$response['status'] = 'user_already_exists';
$this->response($response, 400);
Now in swift you can go with this
Alamofire.request(.POST,URL, parameters:parameters) .responseJSON
{
(request, response, data, error) in
var json = JSON(data!) //I've used swiftyJSON for reading json response
let status = json["status"].stringValue
println("Status : \(status)")
}
Hope this may help you.