Skip to content

Commit

Permalink
refactor: use a websocket connection to record the session
Browse files Browse the repository at this point in the history
  • Loading branch information
henrybarreto committed Nov 23, 2024
1 parent c10a98a commit 6d83f8e
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 121 deletions.
1 change: 1 addition & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hibiken/asynq v0.24.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand Down
105 changes: 11 additions & 94 deletions pkg/api/internalclient/mocks/internalclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 40 additions & 8 deletions pkg/api/internalclient/session.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package internalclient

import (
"context"
"errors"
"fmt"
"io"

"github.com/gorilla/websocket"
"github.com/shellhub-io/shellhub/pkg/api/requests"
"github.com/shellhub-io/shellhub/pkg/models"
)
Expand All @@ -26,8 +29,9 @@ type sessionAPI interface {
// It returns a slice of errors encountered during the operation.
KeepAliveSession(uid string) []error

// RecordSession records a session with the provided session information and record URL.
RecordSession(session *models.SessionRecorded, recordURL string) error
// RecordSession creates a WebSocket client to the URL, and writes each channel request received by it as a
// session record. When the context is done, the internal reading loop should return.
RecordSession(ctx context.Context, uid string, camera chan *models.SessionRecorded, recordURL string) error

// UpdateSession updates some fields of [models.Session] using [models.SessionUpdate].
UpdateSession(uid string, model *models.SessionUpdate) error
Expand Down Expand Up @@ -84,13 +88,41 @@ func (c *client) KeepAliveSession(uid string) []error {
return errors
}

func (c *client) RecordSession(session *models.SessionRecorded, recordURL string) error {
_, err := c.http.
R().
SetBody(session).
Post(fmt.Sprintf("http://"+recordURL+"/internal/sessions/%s/record", session.UID))
func (c *client) RecordSession(ctx context.Context, uid string, camera chan *models.SessionRecorded, recordURL string) error {
connection, _, err := websocket.
DefaultDialer.
DialContext(
ctx,
fmt.Sprintf("ws://"+recordURL+"/internal/sessions/%s/record",
uid,
),
nil)
if err != nil {
return err
}

return err
defer connection.Close()

for {
select {
case <-ctx.Done():
return nil
case frame, ok := <-camera:
if !ok {
// If the camera channel was closed, we stop the writing process.
return nil
}

err := connection.WriteJSON(frame)
if err == io.EOF {
break
}

if err != nil {
return err
}
}
}
}

func (c *client) UpdateSession(uid string, model *models.SessionUpdate) error {
Expand Down
19 changes: 5 additions & 14 deletions ssh/server/channels/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,11 @@ func pipe(ctx gliderssh.Context, sess *session.Session, client gossh.Channel, ag
// defined, we use an [io.Copy] for the data piping between agent and client.
recordURL := ctx.Value("RECORD_URL").(string)
if (envs.IsEnterprise() || envs.IsCloud()) && recordURL != "" {
// TODO: Should it be a channel of pointers to [models.SessionRecorded], or just the structure, could deliver a
// better performance?
camera := make(chan *models.SessionRecorded)

go func() {
for {
frame, ok := <-camera
if !ok {
break
}

sess.Record(frame, recordURL) //nolint:errcheck
}
}()
camera := make(chan *models.SessionRecorded, 100)

// Starts the session record web socket client, and writing each frame received by camera to it.
// While the context exists, the client remains open.
go sess.Record(ctx, camera, recordURL) //nolint:errcheck

buffer := make([]byte, 1024)
for {
Expand Down
Loading

0 comments on commit 6d83f8e

Please sign in to comment.