Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement close handshake #103

Closed
stephenyama opened this issue Jul 5, 2019 · 9 comments · Fixed by #105
Closed

Implement close handshake #103

stephenyama opened this issue Jul 5, 2019 · 9 comments · Fixed by #105

Comments

@stephenyama
Copy link

stephenyama commented Jul 5, 2019

The package does not implement the closing handshake as required by the RFC. The package closes the network connection after sending the close message. Any pending or future reads return immediately with an error derived from the close message. The application cannot read until a close message is sent by the peer or a timeout.

I didn't realize that this is a problem until recently. I incorrectly assumed that a CloseError represents a received close message.

@nhooyr nhooyr added the docs label Jul 5, 2019
@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

I know the RFC says it's required but it's not clear to me why and I couldn't find any information supporting its claim that TCP's closing handshake isn't always reliable. In fact, I found several blog posts criticizing the RFC for effectively reimplementing the TCP close handshake. Will link them if I can find it again.

Pretty much every protocol in existence relies on TCP's close being reliable.

The RFC also claims that the TCP handshake being closed by the server is better than the client as it enables the client to reuse ports better but the client has practically 65k ports. I don't see this being an issue in practice, nor is it an issue with any other protocol.

Thus, I don't think the WebSocket close handshake adds anything.

Yea, CloseError represents the close reason and status of the connection which can either be from a received or sent close frame.

This decision should definitely be documented better.

@nhooyr nhooyr added the question label Jul 5, 2019
@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

The application cannot read until a close message is sent by the peer or a timeout.

What do you mean by that? The application can't read as the connection is closed.

@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

Argh, there is one use case I didn't think of.

An application might want to send a bunch of messages to its peer, then close the connection with normal closure.

Right now, as the connection is closed immediately, there is no guarantee that the peer received all of the application's messages. Whereas if the close handshake was implemented and the status code was echoed by the peer, then it is guaranteed that the peer received all the application's messages.

There may be applications that rely on this guarantee so I will go ahead and implement this. It's very easy.

@nhooyr nhooyr changed the title Closing handshake not supported Implement close handshake Jul 5, 2019
@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

Actually there is no guarantee. Applications need to implement such a guarantee by themselves with an extra message because the close frame from the peer is not necessarily the echoed close frame, even if the status codes match. See https://tools.ietf.org/html/rfc6455#section-7.1.5

I'm again against implementing this.

Would it help your use case if *CloseError was only returned on received frames and for sent frames it was instead just the string?

@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

Nvm, I'm wrong. The peer would just guarantee it never sends StatusNormalClosure unless the application code sends it first which would make it ok.

@stephenyama
Copy link
Author

stephenyama commented Jul 5, 2019

You figured out the use case, but I'll add this since you mention TCP: In the TCP protocol, you can tell the peer that no more data is coming while continuing to read data from the peer. The Go standard library exposes this functionality through the socket CloseWrite method.

The application can't read as the connection is closed.

That strikes at the core of this issue. The package fully closes the connection when it should not.

Would it help your use case if *CloseError was only returned on received frames and for sent frames it was instead just the string?

That will make the issue more obvious, but it does not address the issue.

@nhooyr
Copy link
Owner

nhooyr commented Jul 5, 2019

The application cannot read until a close message is sent by the peer or a timeout.

To be clear, I didn't understand what you meant by that part. How would a message sent by the peer or a timeout allow the application to read again?

@nhooyr
Copy link
Owner

nhooyr commented Jul 12, 2019

So the use case I discussed doesn't necessarily work in the general case. E.g. if you're processing messages concurrently from the read loop like in javascript or with this library, you'd respond with a close frame before necessarily fully processing/validating the peer's messages at which point the close frame means nothing.

I think you're better served with an application level message that acts as CloseWrite.

@nhooyr
Copy link
Owner

nhooyr commented Jul 12, 2019

What I will do is document this better and make sure CloseError is only accessible via xerrors.As for received close frames.

nhooyr added a commit that referenced this issue Oct 7, 2019
I changed my mind after #103 as browsers include a wasClean event to indicate
whether the connection was closed cleanly. From my tests, if a server using
this library prior to this commit initiates the close handshake, wasClean
will be false for the browser as the connection was closed before it could
respond with a close frame. Thus, I believe it's necessary to fully implement
the close handshake.

@stephenyama You'll enjoy this.
This was referenced Oct 7, 2019
nhooyr added a commit that referenced this issue Oct 9, 2019
I changed my mind after #103 as browsers include a wasClean event to indicate
whether the connection was closed cleanly. From my tests, if a server using
this library prior to this commit initiates the close handshake, wasClean
will be false for the browser as the connection was closed before it could
respond with a close frame. Thus, I believe it's necessary to fully implement
the close handshake.

@stephenyama You'll enjoy this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants