What exactly happens when you cancel a network request? [duplicate] - networking

This question already has answers here:
FIN vs RST in TCP connections
(3 answers)
Closed 2 years ago.
I am using iOS but I am asking for networking in general. What does it mean to cancel a network request? Is there a message sent to the server or does the server acknowledge the socket being disconnected?

As you mention using NSURLSessionTask as your way to request, cancel() means a urlSession(_:task:didCompleteWithError:) will be send to the tasks delegate. But passing in a global error code NSURLErrorCancelled (-999) to the defined NSURLErrorDomain.
It is possible that cancelation is later called on the task as a complete processing of the request message is done. So it's up to you to act accordingly once your ErrorDomain is getting the error code NSURLErrorCancelled marking your intention to cancel, and therefore would want to throw away any data that is received since last request.
The Server gets possibly a complete request but your client is not receiving answers anymore. Or the request sequence is not complete so the Server recognises not correct what was intended but would work thru the request until it fails cause of incomplete or wrong formatted request data.
When your receiver callback is down do to canceling you just don't parse any answer of the Server and if you could still parse the Server data that would mean your task is still running. Any result after cancel() should be treated as possibly incomplete or misleading/wrong/invalid. This is why you set a NSURLErrorCancelled error to a NSURLErrorDomain, you want to know what the status is before you assume any received data is of value for you.
By the way NSURLErrorCancelled is also thrown when NSURLSessionAuthChallengeCancelAuthenticationChallenge is marking a server with no trust. So it's actually the same procedure, you decide if any received data is something you want to trust to.
If a socket is disconnected, there is no connection at all, no data passing thru, nothing to receive. nothing to request from. Any Error on both sides can't be exchanged. Server and Client are disconnected then.
Canceling a request does not imply a socket is stopped from working.
It just means the data since the last request is to be handled as invalid.
Why is this?
Because you can construct your own sockets, ignoring ErrorDomain stuff with a complete different request pattern.
Also means in case of client error/crash/canceling nothing is send, you just do not accept any answer as valid even if it was delivered thru the sockets.
For this reasons there are Protocols that define how a message should look like and what should happen in case it was incomplete or would need any kind of validation in a given pattern that validates any data that was send. TCP, UDP, JS-Websocket with handshake and ongoing "dataflow", even OSC etc. and lots of other protocols.

Related

Can HTTP/2 client terminate the stream by sending a HEADER frame in bidirectional streaming RPC?

Suppose we have a bidirectional streaming RPC where the client is sending several request messages (i.e. multiple DATA frames) and the server is answering back with several response messages (i.e. multiple DATA frames).
As I understand it, when the RPC is complete, the server will normally send a HEADER frame with the status header as well as possibly some trailer headers like grpc-status and grpc-message to mark the completion of the request/response exchange.
My question is, suppose the server sends a bad response message, is it possible for the client to send the HEADER frame with the grpc-status and grpc-message headers to convey information about the error.
The reason why I'm asking is because in the c++ server code (generated from protobuf defininition), I'm struggling to find a way to get a hold of this last HEADER frame sent by the client to verify the values of the grpc-status and grpc-message headers.
Additionally, after going through the unit tests in the grpc project, it seems like only the server returns the status for the RPC, which further raises doubts.
I was however able to send the HEADER frame out from the client, but based on the above, I'm not certain whether this is the correct behavior even though I was able to do it.
I would appreciate it if someone can clarify this for me as I'm fairly new to HTTP/2 and gRPC.
Additionally, after going through the unit tests in the grpc project, it seems like only the server returns the status for the RPC, which further raises doubts.
Correct! In gRPC, the server is responsible for terminating the RPC with a status and optional trailing metadata. The client never sends a status to the server. The client can indicate it is done sending on the stream without a status (which internally happens by sending an empty data frame with the END_STREAM flag set, but users shouldn't need to be concerned with this detail). The client only sends HEADER frames at the start of the RPC.

Can HTTP request fail half way?

I am talking about only one case here.
client sent a request to server -> server received it and returned a response -> unfortunately the response dropped.
I have only one question about this.
Is this case even possible? If it's possible then what should the response code be, or will client simply see it as read timeout?
As I want to sync status between client/server and want 100% accuracy no matter how poor the network is, the answer to this question can greatly affect the client's 'retry on failure' strategy.
Any comment is appreciated.
Yes, the situation you have described is possible and occurs regularly. It is called "packet loss". Since the packet is lost, the response never reaches the client, so no response code could possibly be received. Web browsers will display this as "Error connecting to server" or similar.
HTTP requests and responses are generally carried inside TCP packets. If a TCP packet carrying the HTTP response does not arrive in the expected time window, the request is retransmitted. The request will only be retransmitted a certain number of times before a timeout error will occur and the connection is considered broken or dead. (The number of attempts before TCP timeout can be configured on both the client and server sides.)
Is this case even possible?
Yes. It's easy to see why if you picture a physical cable between the client and the server. If I send a request down the cable to the server, and then, before the server has a chance to respond, unplug the cable, the server will receive the request, but the client will never "hear" the response.
If it's possible then what should the response code be, or will client simply see it as read timeout?
It will be a timeout. If we go back to our physical cable example, the client is sitting waiting for a response that will never come. Hopefully, it will eventually give up.
It depends on exactly what tool or library you're using how this is wrapped up, however - it might give you a specific error code for "timeout" or "network error"; it might wrap it up as some internal 5xx status code; it might raise an exception inside your code; etc.

Will the server raise an exception if its HTTP response can't get to the client?

We have an application which creates an order and sends to the server via HTTP post.
Client sends the order as an HTTP request
Server processes it
Server sends the response
Server does some further operation on this order
The client receives the response and processes it.
I've been asked about what about in step3, the response won't get to the client and get lost on the way. Then the client will try to re-send the same order. And this will introduce a duplicate order problem. And how to tackle this.
I came up with the idea that the client generates a unique ID and send to the server so when the client sends it the 2nd time, the server could know that it's a duplicate order, and will only return the previous response.
But I soon remember that HTTP is built upon TCP which should have a three-way handshaking thing for the data connection. Which means:
From the client perspective, if the client doesn't receive any response from the server, the connection will be maintained until timeout, then an exception will be thrown to let the client know.
My questions are:
From the server perspective, after it sends the response, how could it determine the response has reached the client?
There should be a three-way handshaking connection termination at the transportation layer to ensure that the connection will only be closed after the client received the messages, right? So if the message gets lost on the way, the server should trigger an exception, am I right?
If this is the case, the problem could simply be solved by ensure the server only does step4 if there is no exception in step3? Any other solution for this problem if my whole above idea is wrong?
Thanks
The whole idea is wrong. You need to look up idempotence. Basically every transaction needs to be idempotent, which means that applying it twice or more has no more effect than applying it once. This is generally implemented via unique transaction sequence numbers which are recorded at the server when the transaction has been completed.

How do browsers handle HTTP keepalive race condition?

There exists a known race condition in the HTTP keepalive mechanism:
HTTP KeepAlive connection closed by server but client had sent a request in the mean time
https://github.com/mikem23/keepalive-race
As I understand, I need my HTTP client either to have a shorter timeout than my HTTP server, or retry when getting TCP-FIN or TCP-RST.
My question is, how do today's web-browsers, that use the HTTP keepalive feature, handle this race condition. Do they retry?
I'll be happy for references, a google search hasn't come up with anything.
According the the RFC, in these cases, a server should respond with a 408 error code, signalling to the client that the connection has already been closed on its side. As the RFC states:
If the client has an outstanding request in transit, the client MAY
repeat that request on a new connection.
This means that it's up to the client (aka each browser) to decide how a 408 response will be handled. There are 2 alternatives:
handle it gracefully: retrying the remaining requests in a new connection automatically, so that the user stays completely unaware of the underlying failure that happened
fail-fast: showing the user a failure with the appropriate 408 error message
For example, it seems that Chrome in the past was following the second approach until a point, where people started considering this as a "buggy" behaviour and switched to the first one. You can find the bug thread related to the Chromium bug here and the associated code change here.
Note: As you can read in the final emails in the linked thread, Chrome performs these retries, only when some requests have succeeded in this connection. As a result, if you try to reproduce that with a single request, returning a 408 response, you'll notice that Chrome won't probably retry in that case.

Is an HTTP request 'atomic'

I understand an HTTP request will result in a response with a code and optional body.
If we call the originator of the request the 'client' and the recipient of the request the 'server'.
Then the sequence is
Client sends request
Server receives request
Server sends response
Client receive response
Is it possible for the Server to complete step 3 but step 4 does not happen (due to dropped connection, application error etc).
In other words: is it possible for the Server to 'believe' the client should have received the response, but the client for some reason has not?
Network is inherently unreliable. You can only know for sure a message arrived if the other party has acknowledged it, but you never know it did not.
Worse, with HTTP, the only acknowledge for the request is the answer and there is no acknowledge for the answer. That means:
The client knows the server has processed the request if it got the response. If it does not, it does not know whether the request was processed.
The server never knows whether the client got the answer.
The TCP stack does normally acknowledge the answer when closing the socket, but that information is not propagated to the application layer and it would not be useful there, because the stack can acknowledge receipt and then the application might not process the message anyway because it crashes (or power failed or something) and from perspective of the application it does not matter whether the reason was in the TCP stack or above it—either way the message was not processed.
The easiest way to handle this is to use idempotent operations. If the server gets the same request again, it has no side-effects and the response is the same. That way the client, if it times out waiting for the response, simply sends the request again and it will eventually (unless the connection was torn out never to be fixed again) get a response and the request will be completed.
If all else fails, you need to record the executed requests and eliminate the duplicates in the server. Because no network protocol can do that for you. It can eliminate many (as TCP does), but not all.
There is a specific section on that point on the HTTP RFC7230 6.6 Teardown (bold added):
(...)
If a server performs an immediate close of a TCP connection, there is
a significant risk that the client will not be able to read the last
HTTP response.
(...)
To avoid the TCP reset problem, servers typically close a connection
in stages. First, the server performs a half-close by closing only
the write side of the read/write connection. The server then
continues to read from the connection until it receives a
corresponding close by the client, or until the server is reasonably
certain that its own TCP stack has received the client's
acknowledgement of the packet(s) containing the server's last
response. Finally, the server fully closes the connection.
So yes, this response sent step is a quite complex stuff.
Check for example the Lingering close section on this Apache 2.4 document, or the complex FIN_WAIT/FIN_WAIT2 pages for Apache 2.0.
So, a good HTTP server should maintain the socket long enough to be reasonably certain that it's OK on the client side. But if you really need to acknowledge something in a web application, you should use a callback (image callback, ajax callback) asserting the response was fully loaded in the client browser (so another HTTP request). That means it's not atomic as you said, or at least not transactional like you could expect from a relational database. You need to add another request from the client, that maybe you'll never get (because the server had crash before receiving the acknowledgement), etc.

Resources