Skip to content

Commit 31cdb9f

Browse files
committed
wip: commit progress on Go code
1 parent 2a10b35 commit 31cdb9f

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

coderd/httpapi/httpapi.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/coder/coder/v2/coderd/httpapi/httpapiconstraints"
2020
"github.com/coder/coder/v2/coderd/tracing"
2121
"github.com/coder/coder/v2/codersdk"
22+
"github.com/coder/websocket"
2223
)
2324

2425
var Validate *validator.Validate
@@ -280,6 +281,90 @@ func WebsocketCloseSprintf(format string, vars ...any) string {
280281
return msg
281282
}
282283

284+
type WebSocketEvent[T any] struct {
285+
}
286+
287+
func OneWayWebSocket[T any](rw http.ResponseWriter, r *http.Request) (
288+
sendEvent func(ctx context.Context, wsEvent T) error,
289+
closed chan struct{},
290+
err error,
291+
) {
292+
ctx, cancel := context.WithCancel(r.Context())
293+
r = r.WithContext(ctx)
294+
socket, err := websocket.Accept(rw, r, nil)
295+
if err != nil {
296+
cancel()
297+
return nil, nil, xerrors.Errorf("cannot establish connection: %w", err)
298+
}
299+
go Heartbeat(ctx, socket)
300+
301+
type SocketError struct {
302+
Code websocket.StatusCode
303+
Reason string
304+
}
305+
socketErrC := make(chan SocketError, 1)
306+
closed = make(chan struct{}, 1)
307+
go func() {
308+
select {
309+
case err := <-socketErrC:
310+
_ = socket.Close(err.Code, err.Reason)
311+
case <-ctx.Done():
312+
_ = socket.Close(websocket.StatusNormalClosure, "Connection closed")
313+
}
314+
cancel()
315+
close(closed)
316+
}()
317+
318+
// We have some tools in the UI code to help enforce one-way WebSocket
319+
// connections, but there's still the possibility that the client could send
320+
// a message when it's not supposed to. If that happens, the client likely
321+
// forgot to use those tools, and communication probably can't be trusted.
322+
// Better to just close the socket and force the UI to fix its mess
323+
go func() {
324+
msgType, _, err := socket.Read(ctx)
325+
if errors.Is(err, context.Canceled) {
326+
return
327+
}
328+
if err != nil {
329+
socketErrC <- SocketError{
330+
Code: websocket.StatusInternalError,
331+
Reason: "Unable to process invalid message from client",
332+
}
333+
return
334+
}
335+
switch msgType {
336+
case websocket.MessageBinary, websocket.MessageText:
337+
socketErrC <- SocketError{
338+
Code: websocket.StatusProtocolError,
339+
Reason: "Clients cannot send messages for one-way WebSockets",
340+
}
341+
}
342+
}()
343+
344+
sendEvent = func(context.Context, T) error {
345+
// Using multiple selects because we want possible errors to be
346+
// processed deterministically
347+
select {
348+
case _, open := <-ctx.Done():
349+
if !open {
350+
return xerrors.New("connection closed from client")
351+
}
352+
default:
353+
}
354+
select {
355+
case _, open := <-closed:
356+
if !open {
357+
return xerrors.New("connection closed internally")
358+
}
359+
default:
360+
}
361+
362+
return nil
363+
}
364+
365+
return sendEvent, closed, nil
366+
}
367+
283368
func ServerSentEventSender(rw http.ResponseWriter, r *http.Request) (sendEvent func(ctx context.Context, sse codersdk.ServerSentEvent) error, closed chan struct{}, err error) {
284369
h := rw.Header()
285370
h.Set("Content-Type", "text/event-stream")

0 commit comments

Comments
 (0)