Skip to content

Commit b892206

Browse files
committed
Perform WebSocket Close handshake in Conn.Close
Closes #103
1 parent d772f32 commit b892206

File tree

2 files changed

+32
-17
lines changed

2 files changed

+32
-17
lines changed

statuscode.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ const (
4141
)
4242

4343
// CloseError represents a WebSocket close frame.
44-
// It is returned by Conn's methods when the Connection is closed with a WebSocket close frame.
44+
//
45+
// It is returned by Conn's methods when the Connection is closed due to reading a
46+
// WebSocket close frame.
47+
//
4548
// You will need to use https://golang.org/x/xerrors to check for this error.
4649
type CloseError struct {
4750
Code StatusCode

websocket.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ func (c *Conn) handleControl(ctx context.Context, h header) error {
304304
c.Close(StatusProtocolError, "received invalid close payload")
305305
return xerrors.Errorf("received invalid close payload: %w", err)
306306
}
307-
c.writeClose(b, xerrors.Errorf("received close frame: %w", ce))
307+
c.writeClose(b)
308+
c.close(xerrors.Errorf("received close frame: %w", ce))
308309
return c.closeErr
309310
default:
310311
panic(fmt.Sprintf("websocket: unexpected control opcode: %#v", h))
@@ -786,9 +787,19 @@ func (c *Conn) writePong(p []byte) error {
786787
return err
787788
}
788789

789-
// Close closes the WebSocket connection with the given status code and reason.
790+
// Close performs the WebSocket close handshake on the connection with the given
791+
// status code and reason. See https://tools.ietf.org/html/rfc6455#section-7.1.2
792+
//
793+
// First, it will write a WebSocket close frame with a timeout of 5 seconds.
794+
// Next, it will wait a maximum of 10 seconds for a Close frame from the peer.
795+
// You must be reading from the connection in another goroutine for the Close
796+
// frame to be read.
797+
// If the peer does not send a Close frame in the next 10 seconds, the connection
798+
// will be forcefully closed.
799+
//
800+
// The returned error will be a CloseError if a close frame was received.
801+
// Remember to use the golang.org/x/xerrors to access it.
790802
//
791-
// It will write a WebSocket close frame with a timeout of 5 seconds.
792803
// The connection can only be closed once. Additional calls to Close
793804
// are no-ops.
794805
//
@@ -823,31 +834,32 @@ func (c *Conn) exportedClose(code StatusCode, reason string) error {
823834
p, _ = ce.bytes()
824835
}
825836

826-
err = c.writeClose(p, xerrors.Errorf("sent close frame: %w", ce))
837+
err = c.writeClose(p)
827838
if err != nil {
828839
return err
829840
}
830841

831-
if !xerrors.Is(c.closeErr, ce) {
832-
return c.closeErr
833-
}
842+
const to = time.Second * 10
834843

835-
return nil
844+
t := time.NewTimer(to)
845+
defer t.Stop()
846+
847+
// Wait for the close frame to be read.
848+
select {
849+
case <-c.closed:
850+
case <-t.C:
851+
c.close(xerrors.Errorf("sent close frame (%v) but did not receive close frame from peer in %v", ce, to))
852+
}
853+
return c.closeErr
836854
}
837855

838-
func (c *Conn) writeClose(p []byte, cerr error) error {
856+
func (c *Conn) writeClose(p []byte) error {
839857
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
840858
defer cancel()
841859

842860
// If this fails, the connection had to have died.
843861
err := c.writeControl(ctx, opClose, p)
844-
if err != nil {
845-
return err
846-
}
847-
848-
c.close(cerr)
849-
850-
return nil
862+
return err
851863
}
852864

853865
func init() {

0 commit comments

Comments
 (0)