Skip to content

Commit

Permalink
Gracefully handle web socket closure by clients (gravitational#33480)
Browse files Browse the repository at this point in the history
Updates the terminal stream to detect when the web socket is closed
by the UI via setting the close handler. This reduces the time it
takes the server to know the client closed the connection from the
value of the keep alive interval in the networking config (default 5m)
to as soon as a user closes the tab. Prior to this any session that
was terminated by closing the browser tab would have an active
session tracker until the web socket ping loop, which runs at the
keep alive interval, failed. Some logging was also cleaned up to
reduce the amount of spam that appears as a result of a session
being terminated by a user.
  • Loading branch information
rosstimothy authored Oct 16, 2023
1 parent 4073b30 commit 687ad89
Showing 1 changed file with 26 additions and 2 deletions.
28 changes: 26 additions & 2 deletions lib/web/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/gogo/protobuf/proto"
Expand Down Expand Up @@ -283,6 +284,10 @@ type TerminalHandler struct {

// presenceChecker to use for presence checking
presenceChecker PresenceChecker

// closedByClient indicates if the websocket connection was closed by the
// user (closing the browser tab, exiting the session, etc).
closedByClient atomic.Bool
}

// ServeHTTP builds a connection to the remote node and then pumps back two types of
Expand Down Expand Up @@ -399,6 +404,20 @@ func (t *TerminalHandler) handler(ws *websocket.Conn, r *http.Request) {

t.log.Debug("Creating websocket stream")

defaultCloseHandler := ws.CloseHandler()
ws.SetCloseHandler(func(code int, text string) error {
t.closedByClient.Store(true)
t.log.Debug("web socket was closed by client - terminating session")

// Call the default close handler if one was set.
if defaultCloseHandler != nil {
err := defaultCloseHandler(code, text)
return trace.NewAggregate(err, t.Close())
}

return trace.Wrap(t.Close())
})

// Start sending ping frames through websocket to client.
go startPingLoop(ctx, ws, t.keepAliveInterval, t.log, t.Close)

Expand Down Expand Up @@ -766,8 +785,13 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor
// Establish SSH connection to the server. This function will block until
// either an error occurs or it completes successfully.
if err = nc.RunInteractiveShell(ctx, t.participantMode, t.tracker, beforeStart); err != nil {
t.log.WithError(err).Warn("Unable to stream terminal - failure running interactive shell")
t.stream.writeError(err.Error())
if !t.closedByClient.Load() {
t.stream.writeError(err.Error())
}
return
}

if t.closedByClient.Load() {
return
}

Expand Down

0 comments on commit 687ad89

Please sign in to comment.