URLSession.upload does not return server response

Hello,

I have a problem with uploading data via URLSession when the server responds with an error.

Specifically, I upload data with URLSession.upload(for:from:delegate). The server determines that the data is too large and responds with http status 413 (Payload Too Large). However, that response is not returned by URLSession.upload. Instead, it waits until a timeout occurs and throws that as an error.

The weird part is: in the simulator it works perfectly fine and the server response is returned immediately as one would expect. But on an actual device (iPhone, iOS 16.3), it always runs into the timeout. I tested this with the same data.

In Xcode's console, I see an interesting message: "Connection 1: received failure notification". I do not see that message in the console when running the app in the simulator. (I tried to include more log messages but the forum software wouldn't let me).

I also tried to activate network debugging via CFNETWORK_DIAGNOSTICS, but could not make much sense of it. But I do see that the server response is received almost immediately. So the response is received by the network layer, just not returned by URLSession.

By the way, calling upload(for:fromFile:delegate) behaves the same way. As does using a completion handler instead of async/await.

I'm at a loss because I don't think our code is wrong. And I'm completely puzzled by the fact that it works as expected in the simulator.

Replies

This is in a standard session, right? So not a background session?

Share and Enjoy

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

Correct! This is a normal call to URLSession.upload(for:from:delegate) while the app is in the foreground, not a background session. The URLSession is created by us, but with the URLSessionConfiguration.default and only with a custom user agent string. Everything else is left as is.

I might also add that the server is also our own software. The way it works is the server receives the data until the allowed maximum is reached, then it sends the response and closes the connection, while the network layer is still trying to send the remaining data. But again, I could see in the diagnostics log that the response is received and read.

The way it works is the server receives the data until the allowed maximum is reached, then it sends the response and closes the connection

So HTTP/1.1 then?

Share and Enjoy

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

Yes!

Is the upload request body using the chunked transfer encoding?

Share and Enjoy

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

It is not. At least not to my knowledge, and I don't see the "Transfer-Encoding: chunked" header on the server side (I don't know if I would though, or if the framework we are using on the server (Spring Boot) handles that transparently).

Would that be a possible solution? If so, how would I enable chunked transfer encoding? I tried to just add the Transfer-Encoding header to the URLRequest, but that didn't seem to do anything.

No, chunked would make this worse (-:

With the identity transfer encoding the client must set Content-Length, so the server can immediately determine the size of the upload. To prevent duplicating a large upload when using authentication, clients in this situation usually set the Expect: 100-continue so that the upload doesn’t start until the server has OKed it. See the discuss here and many other places on the ’net.

Does your server support 100 continue?

Share and Enjoy

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

Interesting, I did not know this header. It seems to do exactly what you would want: give the server a chance to reject the request before the upload of the body is even started.

I added the header to the URLRequest, but that did not fix the problem. But that might very well be a problem on the server side. I'm still investigating that.

So I guess that would be our best shot? Trying to make our server support this header?