feat: the browser terminal on a WebSocket too, long polls when it does not connect or drops
This commit is contained in:
1 parent
73bb720ec6
commit
47f1b2e55b
4 files changed
+127
-12
No files matched your search
@@ -1,9 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
|
||||
"github.com/x-grk/longpolling-remote-access/internal/proto"
|
||||
)
|
||||
@@ -66,8 +71,14 @@ func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
takePanelIn(sess, m)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// takePanelIn passes the browser's keystrokes or resize on to the agent; a
|
||||
// repeat of one already taken is dropped.
|
||||
func takePanelIn(sess *Session, m panelInMsg) {
|
||||
if m.Seq > 0 && !sess.TakeSeq(m.Seq) {
|
||||
w.WriteHeader(http.StatusNoContent) // a repeat of one already taken
|
||||
return
|
||||
}
|
||||
switch m.T {
|
||||
@@ -76,7 +87,70 @@ func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) {
|
||||
case "r":
|
||||
sess.PushIn(proto.InFrame{Type: proto.FrameResize, Cols: m.Cols, Rows: m.Rows})
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GET /api/panel/term/{sid}/ws - the browser's terminal on a WebSocket: what
|
||||
// the "in" requests send comes as messages, and output goes out as soon as
|
||||
// it is there, in the "out" answers' shape. The page falls back to those
|
||||
// requests when this does not connect.
|
||||
func (s *Server) handlePanelTermWS(w http.ResponseWriter, r *http.Request) {
|
||||
sess := s.hub.Session(r.PathValue("sid"))
|
||||
if sess == nil {
|
||||
http.Error(w, "closed", http.StatusGone)
|
||||
return
|
||||
}
|
||||
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{CompressionMode: websocket.CompressionDisabled})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn.SetReadLimit(1 << 20)
|
||||
defer conn.CloseNow()
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
go func() {
|
||||
defer cancel()
|
||||
for ctx.Err() == nil {
|
||||
msgs, alive := sess.PollOutUntil(ctx.Done(), pollHold)
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
out := map[string]any{"frames": msgs}
|
||||
if !alive {
|
||||
out = map[string]any{"closed": true}
|
||||
}
|
||||
wctx, wcancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
err := wsjson.Write(wctx, conn, out)
|
||||
wcancel()
|
||||
if err != nil || !alive {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
t := time.NewTicker(wsPing)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
pctx, pcancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
err := conn.Ping(pctx)
|
||||
pcancel()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
var m panelInMsg
|
||||
if err := wsjson.Read(ctx, conn, &m); err != nil {
|
||||
return
|
||||
}
|
||||
takePanelIn(sess, m)
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/panel/term/{sid}/close - tear the session down.
|
||||
|
||||
Reference in new issue
Block a user