URLSession errors

I have recently used URLSession.shared.dataTask(with:completionHandler:), and it got me wondering:

  1. Why are status codes like 400/500 not presented in the Error? object of the completionHandler?
  2. Is there a list of errors that have the possibility of ending up in the Error? object of the completionHandler? The 'An error object indicating why the request failed' is quite broad and I am interested in a list of what actually ends up in there.


Thanks!

Craz1k0ek

Accepted Reply

Why are status codes like 400/500 not presented in the

Error?
object of the
completionHandler
?

The

URLSession
API draws a strict line between:
  • Transport errors, that is, errors getting your request too the server or getting the response back

  • Server-side errors, that is, the HTTP status code in the response from the server

The main reason for this is that the interpretation of server errors can be server specific, and that makes things hard for a generic HTTP client, like

URLSession
, that has to work with all servers.

If this setup is inconvenient to you, it’s quite straightforward to add your own wrapper to make things easier. For example, pasted in below is an extension on

URLSession
that treats anything except 200...299 as an error.

Note This code uses the Swift 5

Result
type, and thus requires Xcode 10.2 beta. If you need Swift 4 compatibility, you can use your own
Result
type (or the one from your favourite third-party library).

Is there a list of errors that have the possibility of ending up in the

Error?
object of the
completionHandler?
The An error object indicating why the request failed is quite broad and I am interested in a list of what actually ends up in there.

Most of the common errors are listed in

<Foundation/NSURLError.h>
. These include file system errors, DNS errors, TCP errors, TLS errors, and HTTP protocol errors. It’s also common to find extra information in the error’s user dictionary, and specifically an underlying error accessed via
NSUnderlyingErrorKey
.

It’s absolutely fine to look at these errors for debugging purposes, and to include all the details in logs, but I’d caution you about showing them directly to the user. While all the errors include a localised description, in many cases that description is not going to be helpful. For example, you have to be a TCP expert to understand what this means:

let e = NSError(domain: NSPOSIXErrorDomain, code: Int(ECONNRESET), userInfo: nil) as Error
print(e.localizedDescription)
// -> The operation couldn’t be completed. Connection reset by peer

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
extension URLSession {

    enum HTTPError: Error {
        case transportError(Error)
        case serverSideError(Int)
    }

    typealias DataTaskResult = Result<(HTTPURLResponse, Data), Error>

    func dataTask(with request: URLRequest, completionHandler: @escaping (DataTaskResult) -> Void) -> URLSessionDataTask {
        return self.dataTask(with: request) { (data, response, error) in
            if let error = error {
                completionHandler(Result.failure(HTTPError.transportError(error)))
                return
            }
            let response = response as! HTTPURLResponse
            let status = response.statusCode
            guard (200...299).contains(status) else {
                completionHandler(Result.failure(HTTPError.serverSideError(status)))
                return
            }
            completionHandler(Result.success((response, data!)))
        }
    }
}

Replies

Why are status codes like 400/500 not presented in the

Error?
object of the
completionHandler
?

The

URLSession
API draws a strict line between:
  • Transport errors, that is, errors getting your request too the server or getting the response back

  • Server-side errors, that is, the HTTP status code in the response from the server

The main reason for this is that the interpretation of server errors can be server specific, and that makes things hard for a generic HTTP client, like

URLSession
, that has to work with all servers.

If this setup is inconvenient to you, it’s quite straightforward to add your own wrapper to make things easier. For example, pasted in below is an extension on

URLSession
that treats anything except 200...299 as an error.

Note This code uses the Swift 5

Result
type, and thus requires Xcode 10.2 beta. If you need Swift 4 compatibility, you can use your own
Result
type (or the one from your favourite third-party library).

Is there a list of errors that have the possibility of ending up in the

Error?
object of the
completionHandler?
The An error object indicating why the request failed is quite broad and I am interested in a list of what actually ends up in there.

Most of the common errors are listed in

<Foundation/NSURLError.h>
. These include file system errors, DNS errors, TCP errors, TLS errors, and HTTP protocol errors. It’s also common to find extra information in the error’s user dictionary, and specifically an underlying error accessed via
NSUnderlyingErrorKey
.

It’s absolutely fine to look at these errors for debugging purposes, and to include all the details in logs, but I’d caution you about showing them directly to the user. While all the errors include a localised description, in many cases that description is not going to be helpful. For example, you have to be a TCP expert to understand what this means:

let e = NSError(domain: NSPOSIXErrorDomain, code: Int(ECONNRESET), userInfo: nil) as Error
print(e.localizedDescription)
// -> The operation couldn’t be completed. Connection reset by peer

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
extension URLSession {

    enum HTTPError: Error {
        case transportError(Error)
        case serverSideError(Int)
    }

    typealias DataTaskResult = Result<(HTTPURLResponse, Data), Error>

    func dataTask(with request: URLRequest, completionHandler: @escaping (DataTaskResult) -> Void) -> URLSessionDataTask {
        return self.dataTask(with: request) { (data, response, error) in
            if let error = error {
                completionHandler(Result.failure(HTTPError.transportError(error)))
                return
            }
            let response = response as! HTTPURLResponse
            let status = response.statusCode
            guard (200...299).contains(status) else {
                completionHandler(Result.failure(HTTPError.serverSideError(status)))
                return
            }
            completionHandler(Result.success((response, data!)))
        }
    }
}

I see that you force unwrapped response. Is the respose part of the data task completion block guarenteed to have a value? If yes, why is it optional in the first place?


Thank you very much if you answer this, because I couldn't find a good explanation for it.

I believe this response can still be nil for the sake of time outs. If the request times out you will get back a nil response.