WsServer TLS 지원 완료 후 crosstest에 누락됐던 WSS phase를 4개 orchestrator에 추가. TypeScript는 handshake 안정성을 위해 phase별 서버 인스턴스를 분리했다. WsServer init 강화(setReuseAddr, startError 전파), TypeScript closeWebSocket 안정화 포함.
231 lines
12 KiB
Text
231 lines
12 KiB
Text
<!-- task=kotlin_wss_crosstest plan=0 tag=KOTLIN_WSS_CROSSTEST -->
|
|
|
|
# Code Review Reference - KOTLIN_WSS_CROSSTEST
|
|
|
|
## 개요
|
|
|
|
date=2026-04-27
|
|
task=kotlin_wss_crosstest, plan=0, tag=KOTLIN_WSS_CROSSTEST
|
|
|
|
## 이 파일을 읽는 리뷰 에이전트에게
|
|
|
|
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
|
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.
|
|
|
|
1. `CODE_REVIEW.md` → `code_review_0.log` (N = 기존 code_review_*.log 수)
|
|
2. `PLAN.md` → `plan_0.log` (M = 기존 plan_*.log 수)
|
|
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.
|
|
|
|
---
|
|
|
|
## 구현 항목별 완료 여부
|
|
|
|
| 항목 | 완료 여부 |
|
|
|------|---------|
|
|
| [KOTLIN_WSS_CROSSTEST-1] kotlin_dart.kt — WSS phase 추가 | [x] |
|
|
| [KOTLIN_WSS_CROSSTEST-2] kotlin_go.kt — WSS phase 추가 | [x] |
|
|
| [KOTLIN_WSS_CROSSTEST-3] kotlin_python.kt — WSS phase 추가 | [x] |
|
|
| [KOTLIN_WSS_CROSSTEST-4] kotlin_typescript.kt — WSS phase 추가 | [x] |
|
|
|
|
## 계획 대비 변경 사항
|
|
|
|
- 계획은 `WsServer(..., sslContext = serverCtx)`가 이미 존재한다고 전제했으나, 실제 `WsServer`에는 TLS 인자가 없었다. WSS crosstest 컴파일을 위해 `kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt`에 `SSLContext?` 인자와 `DefaultSSLWebSocketServerFactory` 설정을 추가했다.
|
|
- `WsServer.start()`는 Java-WebSocket 내부 시작 실패가 `onError(null, ex)`로 전달되고 호출자에는 숨겨질 수 있어, 실제 `onStart` 또는 시작 오류를 기다리도록 보강했다. 반복 검증 중 fixed port 재사용 실패를 줄이기 위해 `setReuseAddr(true)`도 추가했다.
|
|
- 기존 `TlsWsTest`가 `server.port()`를 사용하고 있어 `WsServer.port()` 호환 메서드를 추가했다.
|
|
- TypeScript WSS는 단일 Java-WebSocket TLS 서버에서 `npx tsx` 클라이언트가 연속 접속할 때 다음 handshake가 간헐적으로 멈췄다. `kotlin_typescript.kt`의 WSS phase만 TLS TCP처럼 `send-push`와 `requests`에 서버 인스턴스를 분리했다.
|
|
- TypeScript `WsClient.close()`가 `ws.close()` 호출 후 close 완료를 기다리지 않아 WSS 재접속 안정성이 떨어졌다. `typescript/src/ws_client.ts`에서 `close/error` 이벤트 또는 1초 timeout까지 기다리도록 보강했다.
|
|
|
|
## 주요 설계 결정
|
|
|
|
- Dart/Go/Python orchestrator는 계획대로 단일 WSS 서버 인스턴스를 `send-push`와 `requests`에 재사용했다.
|
|
- TypeScript orchestrator만 클라이언트 close/handshake 특성 때문에 WSS 서버를 phase별로 분리했다. 외부 동작은 동일하게 `mode=wss`, 동일 포트 `29800`, 동일 시나리오 set을 검증한다.
|
|
- `WsServer` TLS 지원은 기존 WS 호출과 호환되도록 nullable 기본 인자(`sslContext: SSLContext? = null`)로 추가했다.
|
|
|
|
## 리뷰어를 위한 체크포인트
|
|
|
|
- **포트 충돌 없음**: 각 파일의 `WSS_PORT`가 같은 파일 내 기존 TCP/WS/TLS_TCP 포트와 겹치지 않는지 확인 (dart=29496, go=29396, python=29400, typescript=29800).
|
|
- **WsServer sslContext 전달**: `WsServer(..., sslContext = serverCtx)` 형태로 생성하는지 확인. 기존 `runWs()`의 WsServer 생성과 비교.
|
|
- **서버 인스턴스 수**: Dart/Go/Python은 `runWss()` 안에서 서버를 한 번 start하고 `runWssSendPush` → `runWssRequests` 순으로 재사용하는지 확인. TypeScript는 계획 대비 변경 사항에 기록한 이유로 WSS phase별 서버를 사용한다.
|
|
- **client runner 인수**: `runDartClient("wss", WSS_PORT, ..., certPath)` 처럼 mode=`"wss"` + certPath를 전달하는지 확인.
|
|
- **PASS 메시지 순서**: `main()`에서 `runWss()`가 `runTlsTcp()` 다음, PASS 출력 직전에 위치하는지 확인.
|
|
- **4개 파일 일관성**: `runWssSendPush` / `runWssRequests`의 검증 내용과 client runner 인수가 4개 파일에서 동일한 패턴을 따르는지 확인. TypeScript의 서버 lifetime만 예외다.
|
|
|
|
## 검증 결과
|
|
|
|
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
|
|
|
### 중간 검증 (컴파일)
|
|
|
|
```
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew compileCrosstestKotlin
|
|
> Task :compileCrosstestKotlin
|
|
|
|
BUILD SUCCESSFUL in 2s
|
|
9 actionable tasks: 1 executed, 8 up-to-date
|
|
```
|
|
|
|
### 최종 검증
|
|
|
|
```
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinDartKt
|
|
INFO typeName kotlin=TestData
|
|
INFO typeName dart=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from dart client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName dart=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SLF4J: No SLF4J providers were found.
|
|
SLF4J: Defaulting to no-operation (NOP) logger implementation
|
|
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
|
|
INFO typeName dart=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from dart client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName dart=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName dart=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from dart client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName dart=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName dart=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from dart client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName dart=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
PASS all kotlin-server/dart-client crosstests passed
|
|
|
|
BUILD SUCCESSFUL in 7s
|
|
|
|
$ cd kotlin && env PATH=/config/go-sdk/go/bin:$PATH JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinGoKt
|
|
INFO typeName kotlin=TestData
|
|
INFO typeName go=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from go client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName go=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SLF4J: No SLF4J providers were found.
|
|
SLF4J: Defaulting to no-operation (NOP) logger implementation
|
|
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
|
|
INFO typeName go=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from go client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName go=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName go=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from go client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName go=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName go=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from go client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName go=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
PASS all kotlin-server/go-client crosstests passed
|
|
|
|
BUILD SUCCESSFUL in 6s
|
|
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinPythonKt
|
|
INFO typeName kotlin=TestData
|
|
SERVER_RECEIVED index=101 message=fire from python client
|
|
INFO typeName python=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName python=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SLF4J: No SLF4J providers were found.
|
|
SLF4J: Defaulting to no-operation (NOP) logger implementation
|
|
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
|
|
SERVER_RECEIVED index=101 message=fire from python client
|
|
INFO typeName python=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName python=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SERVER_RECEIVED index=101 message=fire from python client
|
|
INFO typeName python=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName python=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SERVER_RECEIVED index=101 message=fire from python client
|
|
INFO typeName python=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName python=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
PASS all kotlin-server/python-client crosstests passed
|
|
|
|
BUILD SUCCESSFUL in 4s
|
|
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinTypescriptKt
|
|
INFO typeName kotlin=TestData
|
|
INFO typeName ts=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from typescript client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName ts=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
SLF4J: No SLF4J providers were found.
|
|
SLF4J: Defaulting to no-operation (NOP) logger implementation
|
|
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
|
|
INFO typeName ts=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from typescript client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName ts=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName ts=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from typescript client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName ts=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
INFO typeName ts=TestData
|
|
PASS scenario=1 detail=fire-and-forget sent
|
|
SERVER_RECEIVED index=101 message=fire from typescript client
|
|
PASS scenario=2 detail=received push from kotlin server
|
|
INFO typeName ts=TestData
|
|
PASS scenario=3 detail=single request response matched
|
|
PASS scenario=4 detail=concurrent request responses matched
|
|
PASS all kotlin-server/typescript-client crosstests passed
|
|
|
|
BUILD SUCCESSFUL in 7s
|
|
```
|
|
|
|
### 추가 검증
|
|
|
|
```
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew test --tests com.tokilabs.toki_socket.TlsWsTest
|
|
> Task :test
|
|
|
|
BUILD SUCCESSFUL in 9s
|
|
11 actionable tasks: 4 executed, 7 up-to-date
|
|
|
|
$ cd typescript && npm run check
|
|
> toki-socket@1.0.5 check
|
|
> tsc --noEmit
|
|
```
|