WebSocket 애플리케이션 생성
Ktor를 사용하여 Kotlin에서 WebSocket 애플리케이션 만들기
코드 예제: tutorial-server-websockets
사용된 플러그인:
이 문서는 Ktor와 Kotlin을 사용하여 WebSocket 애플리케이션을 제작하는 과정을 안내합니다. 이 내용은
이 문서에서는 다음 내용을 학습하게 됩니다:
- JSON 직렬화를 사용하는 서비스 만들기.
- WebSocket 연결을 통해 콘텐츠 전송 및 수신.
- 여러 클라이언트에 동시에 콘텐츠 브로드캐스트.
사전 준비 사항
이 튜토리얼을 독립적으로 진행할 수 있지만,
IntelliJ IDEA를 설치하는 것을 권장하지만, 원하는 다른 IDE를 사용해도 좋습니다.
Hello WebSockets
이 튜토리얼에서는 WebSocket 연결을 통해 클라이언트와 Task 객체를 주고받는 기능을 추가하여
플러그인을 포함한 초기 프로젝트 생성
Ktor Project Generator로 이동합니다.
Project artifact 필드에 프로젝트 아티팩트 이름으로 com.example.ktor-websockets-task-app를 입력합니다.

플러그인 섹션에서 Add 버튼을 클릭하여 다음 플러그인들을 검색하고 추가합니다:
- Content Negotiation
- kotlinx.serialization
- WebSockets
- Static Content

플러그인을 추가하면 플러그인 섹션의 오른쪽 상단에 표시됩니다.
프로젝트에 추가될 모든 플러그인 목록을 확인할 수 있습니다:

Download 버튼을 클릭하여 Ktor 프로젝트를 생성하고 다운로드합니다.
시작 코드 추가
다운로드가 완료되면 IntelliJ IDEA에서 프로젝트를 열고 다음 단계를 따르세요:
- src/main/kotlin으로 이동하여 model이라는 새 서브패키지를 만듭니다.
model 패키지 안에 새 Task.kt 파일을 만듭니다.
Task.kt 파일을 열고 우선순위를 나타내는
enum과 작업을 나타내는data class를 추가합니다:kotlinTask클래스는kotlinx.serialization라이브러리의@Serializable어노테이션이 붙어 있습니다. 이는 인스턴스를 JSON으로 상호 변환할 수 있음을 의미하며, 이를 통해 네트워크를 통해 내용을 전송할 수 있습니다.WebSockets 플러그인을 포함했으므로 제너레이터가 src/main/kotlin 내의 Routing.kt 파일에
webSocket라우트를, 그리고 Websockets.kt 파일을 추가했을 것입니다.- Websockets.kt 파일을 열고 기존
.configureWebsockets()함수를 다음 내용으로 교체합니다:kotlin- WebSockets 플러그인이 설치되고 표준 설정으로 구성됩니다.
contentConverter속성이 설정되어, 플러그인이kotlinx.serialization라이브러리를 통해 송수신되는 객체를 직렬화할 수 있게 합니다.
Routing.kt 파일을 열고 기존
Application.configureRouting()함수를 아래 구현으로 교체합니다:kotlin- 상대 URL이
/tasks인 단일 엔드포인트로 라우팅이 구성됩니다. - 요청을 받으면 작업 목록이 WebSocket 연결을 통해 직렬화되어 전송됩니다.
- 모든 항목이 전송되면 서버는 연결을 닫습니다.
데모를 위해 작업을 전송하는 사이에 1초의 지연(delay)을 추가했습니다. 이를 통해 클라이언트에서 작업이 점진적으로 나타나는 것을 관찰할 수 있습니다. 이 지연이 없다면 이 예제는 이전 문서에서 개발한
RESTful 서비스및Kotlin과 Ktor를 사용하여 JSON 파일을 생성하는 RESTful API 예제를 포함한 백엔드 서비스를 구축하는 방법을 알아봅니다.웹 애플리케이션과 동일하게 보일 것입니다.Kotlin과 Ktor 및 Thymeleaf 템플릿을 사용하여 웹사이트를 구축하는 방법을 알아봅니다.이 반복 단계의 마지막 과정은 이 엔드포인트를 위한 클라이언트를 만드는 것입니다.
Static Content플러그인을 포함했으므로, Ktor 프로젝트 제너레이터가 src/main/resources/static 내에 index.html 파일을 추가했을 것입니다.스타일시트, 스크립트, 이미지 등과 같은 정적 콘텐츠를 제공하는 방법을 알아봅니다.- 상대 URL이
index.html 파일을 열고 기존 내용을 다음으로 바꿉니다:
html이 페이지는 모든 최신 브라우저에서 사용할 수 있는
WebSocket유형을 사용합니다. JavaScript에서 이 객체를 생성하고 생성자에 엔드포인트의 URL을 전달합니다. 그 후onopen,onclose,onmessage이벤트에 대한 이벤트 핸들러를 연결합니다.onmessage이벤트가 트리거되면 문서 객체의 메서드를 사용하여 테이블에 행을 추가합니다.IntelliJ IDEA에서 실행 버튼 (
)을 클릭하여 애플리케이션을 시작합니다.
http://0.0.0.0:8080/static/index.html로 이동합니다. 버튼이 있는 폼과 빈 테이블이 나타날 것입니다:

폼을 클릭하면 서버에서 작업이 로드되어 초당 한 개씩 나타납니다. 결과적으로 테이블이 점진적으로 채워집니다. 브라우저의 개발자 도구에서 JavaScript 콘솔을 열어 기록된 메시지를 확인할 수도 있습니다.

이제 서비스가 예상대로 작동합니다. WebSocket 연결이 열리고, 항목이 클라이언트로 전송된 다음 연결이 닫힙니다. 기저의 네트워킹에는 많은 복잡성이 수반되지만, Ktor가 기본적으로 이 모든 것을 처리해 줍니다.
WebSocket 이해하기
다음 단계로 넘어가기 전에 WebSockets의 기본 사항을 복습하는 것이 도움이 될 수 있습니다. 이미 WebSockets에 익숙하다면 바로 서비스 설계 개선 단계로 넘어가도 좋습니다.
이전 튜토리얼에서 클라이언트는 HTTP 요청을 보내고 HTTP 응답을 받았습니다. 이는 잘 작동하며 인터넷이 확장 가능하고 탄력적으로 유지될 수 있게 합니다.
그러나 다음과 같은 시나리오에는 적합하지 않습니다:
- 콘텐츠가 시간이 지남에 따라 점진적으로 생성되는 경우.
- 이벤트에 따라 콘텐츠가 빈번하게 변경되는 경우.
- 콘텐츠가 생성되는 동안 클라이언트가 서버와 상호 작용해야 하는 경우.
- 한 클라이언트가 보낸 데이터가 다른 클라이언트에 빠르게 전파되어야 하는 경우.
이러한 시나리오의 예로는 주식 거래, 영화 및 콘서트 티켓 구매, 온라인 경매 입찰, 소셜 미디어의 채팅 기능 등이 있습니다. WebSockets는 이러한 상황을 처리하기 위해 개발되었습니다.
WebSocket 연결은 TCP를 통해 구축되며 장기간 유지될 수 있습니다. 이 연결은 전이중 통신(full duplex communication)을 제공합니다. 즉, 클라이언트가 서버로 메시지를 보내는 동시에 서버로부터 메시지를 받을 수 있습니다.
WebSocket API는 네 가지 이벤트(open, message, close, error)와 두 가지 동작(send, close)을 정의합니다. 이러한 기능에 접근하는 방법은 언어와 라이브러리에 따라 다를 수 있습니다. 예를 들어, Kotlin에서는 들어오는 메시지 시퀀스를 Flow로 소비할 수 있습니다.
설계 개선
다음으로, 더 고급 예제를 구현하기 위해 기존 코드를 리팩토링해 보겠습니다.
model 패키지에 새 TaskRepository.kt 파일을 만듭니다.
TaskRepository.kt를 열고
TaskRepository객체를 추가합니다:kotlin이 코드는 이전 튜토리얼에서 보았던 것과 비슷할 것입니다.
- src/main/kotlin으로 이동하여 Routing.kt 파일을 엽니다.
이제
TaskRepository를 활용하여Application.configureRouting()의 라우팅을 단순화할 수 있습니다:kotlin
WebSocket을 통해 메시지 전송
WebSocket의 강력함을 보여주기 위해 다음과 같은 새 엔드포인트를 만들어 보겠습니다:
- 클라이언트가 시작할 때 기존의 모든 작업을 수신합니다.
- 클라이언트가 작업을 생성하고 전송할 수 있습니다.
- 한 클라이언트가 작업을 전송하면 다른 모든 클라이언트가 알림을 받습니다.
Routing.kt 파일에서 현재
.configureRouting()메서드를 아래 구현으로 교체합니다:kotlin이 코드를 통해 다음 작업을 수행했습니다:
- 기존의 모든 작업을 전송하는 기능을 헬퍼 메서드로 리팩토링했습니다.
routing {}블록에서 모든 클라이언트를 추적하기 위해 스레드로부터 안전한session객체 리스트를 생성했습니다.- 상대 URL이
/tasks2인 새 엔드포인트를 추가했습니다. 클라이언트가 이 엔드포인트에 연결하면 해당session객체가 리스트에 추가됩니다. 그런 다음 서버는 새 작업을 수신하기 위해 대기하는 무한 루프에 진입합니다. 새 작업을 수신하면 서버는 이를 저장소에 저장하고 현재 클라이언트를 포함한 모든 클라이언트에게 복사본을 보냅니다.
이 기능을 테스트하기 위해 index.html의 기능을 확장하는 새 페이지를 만들겠습니다.
src/main/resources/static 내에 wsClient.html이라는 새 HTML 파일을 만듭니다.
wsClient.html을 열고 다음 내용을 추가합니다:
html이 새 페이지에는 사용자가 새 작업 정보를 입력할 수 있는 HTML 폼이 도입되었습니다. 폼을 제출하면
sendTaskToServer()이벤트 핸들러가 호출됩니다. 이는 폼 데이터로 JavaScript 객체를 빌드하고 WebSocket 객체의.send()메서드를 사용하여 서버로 전송합니다.IntelliJ IDEA에서 재실행 버튼(
)을 클릭하여 애플리케이션을 다시 시작합니다.
이 기능을 테스트하려면 두 개의 브라우저를 나란히 열고 다음 단계를 따르세요.
- 브라우저 A에서 http://0.0.0.0:8080/static/wsClient.html로 이동합니다. 기본 작업들이 표시되는지 확인합니다.
- 브라우저 A에서 새 작업을 추가합니다. 해당 페이지의 테이블에 새 작업이 나타나야 합니다.
- 브라우저 B에서 http://0.0.0.0:8080/static/wsClient.html로 이동합니다. 기본 작업과 브라우저 A에서 추가한 새 작업이 모두 표시되어야 합니다.
- 어느 한 브라우저에서 작업을 추가합니다. 양쪽 페이지 모두에 새 항목이 나타나는지 확인합니다.

자동화된 테스트 추가
QA 프로세스를 효율화하고 빠르고 재현 가능하며 자동화하기 위해 Ktor의 내장된
Ktor Client 내에서
Content Negotiation지원을 구성할 수 있도록 build.gradle.kts에 다음 의존성을 추가합니다:ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상과 특정 형식으로 콘텐츠를 직렬화/역직렬화하는 두 가지 주요 목적을 수행합니다.kotlinIntelliJ IDEA에서 편집기 오른쪽에 있는 Gradle 알림 아이콘 (
)을 클릭하여 Gradle 변경 사항을 로드합니다.
src/test/kotlin으로 이동하여 ServerTest.kt 파일을 엽니다.
생성된 테스트 클래스를 아래 구현으로 교체합니다:
kotlin이 설정을 통해 다음을 수행합니다:
- 서비스가 테스트 환경 내에서 실행되도록 구성하고, JSON 직렬화 및 WebSockets를 포함하여 프로덕션 환경과 동일한 기능을 활성화합니다.
- Ktor Client내에서 콘텐츠 협상 및 WebSocket 지원을 구성합니다. 이게 없으면 클라이언트는 WebSocket 연결을 사용할 때 객체를 JSON으로 직렬화/역직렬화하는 방법을 알 수 없습니다.Ktor 클라이언트를 생성하고 구성하는 방법을 알아봅니다.
- 서비스가 반환할 것으로 기대하는
Tasks목록을 선언합니다. client객체의.webSocket함수를 사용하여/tasks로 요청을 보냅니다.- 들어오는 작업을
Flow로 소비하여 리스트에 점진적으로 추가합니다. - 모든 작업을 수신하면 일반적인 방식으로
expectedTasks와actualTasks를 비교합니다.
다음 단계
수고하셨습니다! WebSocket 통신과 Ktor Client를 사용한 자동화 테스트를 통합함으로써 작업 관리자 서비스를 크게 개선했습니다.
