Swift URLSession closes connection immediately, while Postman keeps it open (Sony TV PIN request)

Hello,

I'm trying to implement a PIN request feature for a Sony TV in my iOS app. The goal is to keep the PIN entry window open on the TV until the user enters the PIN. However, I'm encountering an issue where the connection is closed immediately when using Swift's URLSession, while the same request works as expected in Postman.

Here's my Swift code:

let parameters = """
        {
            "method": "actRegister",
            "params": [
                {
                    "clientid": "MyDevice:1",
                    "nickname": "My Device",
                    "level": "private"
                },
                [
                    {
                        "value": "yes",
                        "function": "WOL"
                    }
                ]
            ],
            "id": 1,
            "version": "1.0"
        }
        """
        
        guard let postData = parameters.data(using: .utf8) else {
            completion(.failure(NSError(domain: "Invalid data", code: 0, userInfo: nil)))
            return
        }
        
        var request = URLRequest(url: URL(string: "http://\(ipAddress)/sony/accessControl")!,timeoutInterval: 30)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = postData
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "No data received", code: 0, userInfo: nil)))
                return
            }
            
            if let responseString = String(data: data, encoding: .utf8) {
                print("Response: \(responseString)")
            }
        }
    
        task.resume()

When I send this request using Postman, the PIN window on the TV stays open as expected. I receive a 401 response, which is normal for this type of request. In Postman, I can simulate the unwanted behavior by sending the request twice in quick succession, which closes the PIN window.

However, when I run this code in the iPhone Simulator, the PIN window on the TV closes immediately after appearing.

What I've tried:

  • Increasing the timeoutInterval
  • Using URLSession.shared.dataTask and URLSession(configuration:delegate:delegateQueue:)
  • Implementing URLSessionDataDelegate methods

Expected behavior: The PIN window should stay open on the TV until the user enters the PIN or a timeout occurs.

Actual behavior: The PIN window appears briefly on the TV and then closes immediately.

Questions:

  1. Why does the behavior differ between Postman and my Swift code?
  2. How can I modify my Swift code to keep the connection open and the PIN window displayed on the TV?
  3. Is there a way to prevent URLSession from automatically closing the connection after receiving the 401 response?

Any insights or suggestions would be greatly appreciated. Thank you!

Environment:

  • iOS 15+
  • Swift 5
  • Xcode 13+
Answered by DTS Engineer in 804115022

There’s really only one relevant question here, namely:

Is there a way to prevent URLSession from automatically closing the connection after receiving the 401 response?

The answer to that is “No.” URLSession handles the complicated problem of mapping HTTP requests to the underlying TCP and QUIC connections. It gives you very little control over that mapping.

There is one specific gotcha in this space — folks who start a new session for every request — but the snippet you posted shows that you’re using the shared session, and thus you haven’t fallen into that trap. Beyond that, there are very few options you have in the URLSession API itself.

Now, if you controlled the server, there are a lot more things you might be able to do. However, it doesn’t sound like that’s the case here.

Note IMO the server is not using HTTP correctly, because the semantics of HTTP messages are supposed to be independent of the underlying transport. However, it’s not alone in this misapprehension. And, yes, I am looking at your NTLM!

If you need fine-grained control over the connections used by your HTTP requests, your only real option is to use a low-level networking API. My advice, per TN3151, is to use Network framework.

The bad news here is that you’ll have to do your own HTTP framing. The good news is that this is usually pretty easy to do, because servers with this problem are usually quite basic [1].


Oh, wait, there is one other option that might be more convenient. You could look at the AsyncHTTPClient from the server-side Swift. I believe you can combine that with SwiftNIO Transport Services, which gives you a lower-level HTTP client that’s still integrated with the smarts from Network framework.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] And I mean that in the pejorative sense. Many folks think that HTTP/1.1 is a simple protocol. It is not. For example, many servers claim to support HTTP/1.1 but then immediately fall over if you send them a request that uses the chunked transfer encoding, despite the fact that this is a mandatory feature of HTTP/1.1.

There’s really only one relevant question here, namely:

Is there a way to prevent URLSession from automatically closing the connection after receiving the 401 response?

The answer to that is “No.” URLSession handles the complicated problem of mapping HTTP requests to the underlying TCP and QUIC connections. It gives you very little control over that mapping.

There is one specific gotcha in this space — folks who start a new session for every request — but the snippet you posted shows that you’re using the shared session, and thus you haven’t fallen into that trap. Beyond that, there are very few options you have in the URLSession API itself.

Now, if you controlled the server, there are a lot more things you might be able to do. However, it doesn’t sound like that’s the case here.

Note IMO the server is not using HTTP correctly, because the semantics of HTTP messages are supposed to be independent of the underlying transport. However, it’s not alone in this misapprehension. And, yes, I am looking at your NTLM!

If you need fine-grained control over the connections used by your HTTP requests, your only real option is to use a low-level networking API. My advice, per TN3151, is to use Network framework.

The bad news here is that you’ll have to do your own HTTP framing. The good news is that this is usually pretty easy to do, because servers with this problem are usually quite basic [1].


Oh, wait, there is one other option that might be more convenient. You could look at the AsyncHTTPClient from the server-side Swift. I believe you can combine that with SwiftNIO Transport Services, which gives you a lower-level HTTP client that’s still integrated with the smarts from Network framework.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] And I mean that in the pejorative sense. Many folks think that HTTP/1.1 is a simple protocol. It is not. For example, many servers claim to support HTTP/1.1 but then immediately fall over if you send them a request that uses the chunked transfer encoding, despite the fact that this is a mandatory feature of HTTP/1.1.

Swift URLSession closes connection immediately, while Postman keeps it open (Sony TV PIN request)
 
 
Q