Skip to content

feat(access): migrate web console to coder/websocket with buffered relay and liveness keepalive#79

Open
jisung-02 wants to merge 7 commits into
mainfrom
feat/console-coder-websocket
Open

feat(access): migrate web console to coder/websocket with buffered relay and liveness keepalive#79
jisung-02 wants to merge 7 commits into
mainfrom
feat/console-coder-websocket

Conversation

@jisung-02

Copy link
Copy Markdown
Contributor

개요

웹 콘솔(브라우저 ↔ VM SSH 릴레이)의 WebSocket 스택을 전면 개선합니다.

  • 유지보수가 중단된 golang.org/x/net/websocketgithub.com/coder/websocket으로 교체
  • 릴레이 코어를 전송 계층 독립 구조로 분리
  • 출력 배칭·백프레셔·버퍼 풀링으로 대량 출력 시 성능/메모리 안정성 확보
  • keepalive + 세션 수명 제한으로 유휴 상태의 죽은 연결·방치 세션 정리

변경 사항

1. WebSocket 라이브러리 교체 (coder/websocket v1.8.14)

  • 핸드셰이크 전에 Origin을 직접 검증하고 실패 시 403 JSON 응답 (기존엔 라이브러리 콜백 의존)
  • 클라이언트 입력 프레임 크기 제한 1MB (SetReadLimit)

2. 릴레이 구조 분리

  • bridgeWebConsole(WebSocket 전용) → relayConsole(client io.ReadWriteCloser, ...)로 리팩토링.
    WebSocket은 webSocketConsole 어댑터로 감싸서 전달 — 릴레이 코어는 전송 방식을 모름
  • 양방향 복사를 수동 루프 + done 채널 폴링에서 io.Copy 펌프 + context 취소로 단순화
  • NS 프록시 dial / SSH 연결에 15초 타임아웃 추가 (dialViaNSProxy가 context를 받도록 변경)

3. 출력 배칭 + 백프레셔 + 버퍼 풀링

  • SSH 출력을 8ms 간격 또는 64KB 도달 시 하나의 바이너리 프레임으로 묶어 전송 → 프레임 폭주 방지
  • 출력 버퍼 1MB 상한. 가득 차면 Write가 블로킹되어 SSH 읽기 루프를 직접 늦춤
    (느린 클라이언트로 인한 무한 버퍼링 방지). WebSocket 쓰기 10초 타임아웃 초과 시 강제 종료
  • sync.Pool로 플러시 버퍼 재사용 → 대량 출력 시 GC 부담 감소

4. keepalive + 세션 수명 제한 (console_liveness.go)

기존에는 유휴 상태에서 죽은 브라우저/VM을 감지할 방법이 없어 SSH 세션·고루틴·
authorized key 정리가 무기한 지연될 수 있었습니다. 30초 간격 단일 감시 루프가
아래를 한 번에 검사하고, 실패 시 원인(context.WithCancelCause)을 담아 릴레이를
종료합니다:

  • WS ping: 브라우저는 프로토콜 ping에 자동 응답하므로 프론트엔드 수정 불필요
  • SSH keepalive: keepalive@openssh.com 글로벌 요청 (OpenSSH ServerAliveInterval과 동일)
  • 유휴 타임아웃 30분 / 절대 수명 12시간: 입출력 활동 시각을 추적해 방치된 탭 정리

테스트

  • go test ./internal/domain/access/ -race 통과
  • liveness 판정(유휴/수명/프로브 실패), 활동 스탬핑, 감시 루프 취소,
    SSH 프로브(성공/실패/행), 실제 WebSocket 왕복 ping 테스트 추가
  • 기존 Origin 검증·핸들러 테스트는 변경된 시그니처에 맞게 수정 후 통과

참고

  • 터미널 리사이즈(현재 PTY 24x80 고정)는 클라이언트가 크기 메시지를 보내야 하므로
    프론트엔드 작업과 함께 후속 PR에서 진행 예정

jisung-02 added 7 commits June 7, 2026 11:17
Replace golang.org/x/net/websocket with github.com/coder/websocket for the
web console SSH relay. Validate origin before Accept, set a read limit, drive
the connection with a Background-derived context, add a per-write deadline so
a slow client cannot stall the SSH pump, and drop the per-write payload copy.
…WebSocket decorator

Extract relayConsole as a transport-agnostic SSH core (ns-proxy dial with
timeout, inner handshake, host key callback, pty/shell, io.Copy pumps) that
takes an io.ReadWriteCloser, mirroring the ssh-gateway terminal path. The web
console now wraps the WebSocket in a webSocketConsole adapter and injects it.
Add a dial timeout/context to dialViaNSProxy.
Cap the batched output buffer per session and block the producer (via sync.Cond)
when it is full, throttling the SSH read loop instead of buffering unbounded.
This propagates flow control to the remote shell without dropping output; a slow
or dead client still hits the write deadline and tears the session down.
Pull the per-flush output buffer from a sync.Pool and return it after the write
completes, instead of allocating a fresh one each ~8ms window. Cuts allocations
under heavy output; oversized buffers grown past the cap are dropped.
@jisung-02 jisung-02 requested review from Choi-Eunseok and haramj June 10, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant