Skip to content

Ktor를 사용하여 Kotlin에서 WebSocket 애플리케이션 생성

코드 예시: tutorial-server-websockets

사용된 플러그인:

Routing
라우팅은 서버 애플리케이션에서 들어오는 요청을 처리하기 위한 핵심 플러그인입니다.
,
Static Content
스타일시트, 스크립트, 이미지 등과 같은 정적 콘텐츠를 제공하는 방법을 알아보세요.
,
Content Negotiation
ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상 및 특정 형식으로 콘텐츠를 직렬화/역직렬화하는 두 가지 주요 목적을 수행합니다.
,
WebSockets in Ktor Server
WebSockets 플러그인을 사용하면 서버와 클라이언트 간에 다중 통신 세션을 생성할 수 있습니다.
, kotlinx.serialization

이 글은 Ktor를 사용하여 Kotlin에서 WebSocket 애플리케이션을 생성하는 과정을 안내합니다. 이 내용은

RESTful API 생성
Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하고 JSON 파일을 생성하는 RESTful API의 예시를 통해 알아보세요.
튜토리얼에서 다룬 내용을 기반으로 합니다.

이 글에서는 다음을 수행하는 방법을 알려드립니다:

  • JSON 직렬화를 사용하는 서비스 생성.
  • WebSocket 연결을 통해 콘텐츠 송수신.
  • 여러 클라이언트에 동시에 콘텐츠 브로드캐스트.

사전 준비

이 튜토리얼은 독립적으로 수행할 수 있지만,

RESTful API 생성
Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하고 JSON 파일을 생성하는 RESTful API의 예시를 통해 알아보세요.
튜토리얼을 완료하여
Content Negotiation
ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상 및 특정 형식으로 콘텐츠를 직렬화/역직렬화하는 두 가지 주요 목적을 수행합니다.
및 REST에 익숙해지는 것을 권장합니다.

IntelliJ IDEA 설치를 권장하지만, 원하는 다른 IDE를 사용할 수도 있습니다.

Hello WebSockets

이 튜토리얼에서는

RESTful API 생성
Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하고 JSON 파일을 생성하는 RESTful API의 예시를 통해 알아보세요.
튜토리얼에서 개발된 Task Manager 서비스에 WebSocket 연결을 통해 Task 객체를 클라이언트와 교환하는 기능을 추가하여 개발을 이어나갈 것입니다. 이를 위해
WebSockets 플러그인
WebSockets 플러그인을 사용하면 서버와 클라이언트 간에 다중 통신 세션을 생성할 수 있습니다.
을 추가해야 합니다. 기존 프로젝트에 수동으로 추가할 수도 있지만, 이 튜토리얼에서는 새 프로젝트를 생성하여 처음부터 시작하겠습니다.

플러그인을 사용하여 초기 프로젝트 생성

  1. Ktor 프로젝트 생성기로 이동합니다.

  2. 프로젝트 아티팩트 필드에 프로젝트 아티팩트 이름으로 com.example.ktor-websockets-task-app을 입력합니다. Ktor 프로젝트 생성기에서 프로젝트 아티팩트 이름 지정

  3. 플러그인 섹션에서 다음 플러그인을 검색하여 추가 버튼을 클릭하여 추가합니다:

    1. Routing
    2. Content Negotiation
    3. Kotlinx.serialization
    4. WebSockets
    5. Static Content

    Ktor 프로젝트 생성기에서 플러그인 추가

  4. 플러그인을 추가한 후, 플러그인 섹션 오른쪽 상단에 있는 5개 플러그인 버튼을 클릭하여 추가된 플러그인을 표시합니다.

    그러면 프로젝트에 추가될 모든 플러그인 목록이 표시됩니다: Ktor 프로젝트 생성기의 플러그인 목록

  5. 다운로드 버튼을 클릭하여 Ktor 프로젝트를 생성하고 다운로드합니다.

초기 코드 추가

다운로드가 완료되면 IntelliJ IDEA에서 프로젝트를 열고 다음 단계를 따르세요:

  1. src/main/kotlin으로 이동하여 model이라는 새 서브 패키지를 생성합니다.
  2. model 패키지 내에 새 Task.kt 파일을 생성합니다.

  3. Task.kt 파일을 열고 우선순위를 나타내는 enum과 작업을 나타내는 data class를 추가합니다:

    kotlin

    Task 클래스는 kotlinx.serialization 라이브러리의 Serializable 타입으로 어노테이션되어 있습니다. 이는 인스턴스가 JSON으로 변환되고 JSON에서 변환될 수 있음을 의미하며, 네트워크를 통해 내용을 전송할 수 있도록 합니다.

    WebSockets 플러그인을 포함했으므로, src/main/kotlin/com/example/plugins 내에 Sockets.kt 파일이 생성되었습니다.

  4. Sockets.kt 파일을 열고 기존 Application.configureSockets() 함수를 아래 구현으로 바꿉니다:

    kotlin

    이 코드에서는 다음 단계가 수행됩니다:

    1. WebSockets 플러그인이 설치되고 표준 설정으로 구성됩니다.
    2. contentConverter 속성이 설정되어 플러그인이 kotlinx.serialization 라이브러리를 통해 송수신되는 객체를 직렬화할 수 있도록 합니다.
    3. 라우팅은 상대 URL이 /tasks인 단일 엔드포인트로 구성됩니다.
    4. 요청을 받으면 작업 목록이 WebSocket 연결을 통해 직렬화됩니다.
    5. 모든 항목이 전송되면 서버는 연결을 닫습니다.

    시연 목적으로, 작업을 전송하는 사이에 1초 지연이 도입됩니다. 이를 통해 클라이언트에서 작업이 점진적으로 나타나는 것을 관찰할 수 있습니다. 이 지연이 없으면 예시는 이전 글에서 개발된

    RESTful 서비스
    Kotlin과 Ktor를 사용하여 백엔드 서비스를 구축하고 JSON 파일을 생성하는 RESTful API의 예시를 통해 알아보세요.
    웹 애플리케이션
    Ktor와 Thymeleaf 템플릿을 사용하여 Kotlin에서 웹사이트를 구축하는 방법을 알아보세요.
    과 동일하게 보일 것입니다.

    이 반복의 마지막 단계는 이 엔드포인트에 대한 클라이언트를 생성하는 것입니다.

    Static Content
    스타일시트, 스크립트, 이미지 등과 같은 정적 콘텐츠를 제공하는 방법을 알아보세요.
    플러그인을 포함했기 때문에, src/main/resources/static 내에 index.html 파일이 생성되었습니다.

  5. index.html 파일을 열고 기존 내용을 다음으로 바꿉니다:

    html

    이 페이지는 모든 최신 브라우저에서 사용 가능한 WebSocket 유형을 사용합니다. JavaScript에서 이 객체를 생성하고, 엔드포인트의 URL을 생성자에 전달합니다. 이어서 onopen, onclose, onmessage 이벤트에 대한 이벤트 핸들러를 연결합니다. onmessage 이벤트가 트리거되면, 문서 객체의 메서드를 사용하여 테이블에 행을 추가합니다.

  6. IntelliJ IDEA에서 실행 버튼 (intelliJ IDEA 실행 아이콘) 을 클릭하여 애플리케이션을 시작합니다.

  7. http://0.0.0.0:8080/static/index.html으로 이동합니다. 버튼 하나와 비어 있는 테이블이 있는 폼이 표시되어야 합니다:

    버튼 하나가 있는 HTML 폼을 표시하는 웹 브라우저 페이지

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

    버튼 클릭 시 목록 항목을 표시하는 웹 브라우저 페이지

    이로써 서비스가 예상대로 작동하고 있음을 확인할 수 있습니다. WebSocket 연결이 열리고, 항목이 클라이언트로 전송된 다음, 연결이 닫힙니다. 기본 네트워킹에는 많은 복잡성이 있지만, Ktor는 기본적으로 이 모든 것을 처리합니다.

WebSocket 이해하기

다음 반복으로 넘어가기 전에 WebSocket의 몇 가지 기본 사항을 검토하는 것이 도움이 될 수 있습니다. 이미 WebSocket에 익숙하다면, 서비스 디자인 개선으로 계속 진행할 수 있습니다.

이전 튜토리얼에서는 클라이언트가 HTTP 요청을 보내고 HTTP 응답을 받았습니다. 이는 잘 작동하며 인터넷이 확장 가능하고 탄력적일 수 있도록 합니다.

하지만 다음 시나리오에는 적합하지 않습니다:

  • 콘텐츠가 시간에 따라 점진적으로 생성되는 경우.
  • 이벤트에 따라 콘텐츠가 자주 변경되는 경우.
  • 클라이언트가 콘텐츠가 생성될 때 서버와 상호 작용해야 하는 경우.
  • 한 클라이언트가 보낸 데이터가 다른 클라이언트에 빠르게 전파되어야 하는 경우.

이러한 시나리오의 예로는 주식 거래, 영화 및 콘서트 티켓 구매, 온라인 경매 입찰, 소셜 미디어의 채팅 기능 등이 있습니다. WebSocket은 이러한 상황을 처리하기 위해 개발되었습니다.

WebSocket 연결은 TCP를 통해 설정되며 장기간 지속될 수 있습니다. 이 연결은 '전이중 통신'을 제공하며, 클라이언트가 서버에 메시지를 보내고 동시에 서버로부터 메시지를 받을 수 있음을 의미합니다.

WebSocket API는 네 가지 이벤트(open, message, close, error)와 두 가지 액션(send, close)을 정의합니다. 이 기능에 접근하는 방법은 언어 및 라이브러리마다 다를 수 있습니다. 예를 들어, Kotlin에서는 들어오는 메시지 시퀀스를 Flow로 사용할 수 있습니다.

디자인 개선

다음으로, 더 고급 예제를 위한 공간을 만들기 위해 기존 코드를 리팩터링할 것입니다.

  1. model 패키지에 새 TaskRepository.kt 파일을 생성합니다.

  2. TaskRepository.kt를 열고 TaskRepository 타입을 추가합니다:

    kotlin

    이전 튜토리얼에서 이 코드를 보셨을 수도 있습니다.

  3. plugins 패키지로 이동하여 Sockets.kt 파일을 엽니다.
  4. 이제 TaskRepository를 활용하여 Application.configureSockets()의 라우팅을 단순화할 수 있습니다:

    kotlin

WebSocket을 통한 메시지 전송

WebSocket의 강력한 기능을 보여주기 위해, 다음을 수행하는 새 엔드포인트를 생성할 것입니다:

  • 클라이언트가 시작되면 모든 기존 작업을 수신합니다.
  • 클라이언트가 작업을 생성하고 전송할 수 있습니다.
  • 한 클라이언트가 작업을 보내면 다른 클라이언트에게 알림이 전송됩니다.
  1. Sockets.kt 파일에서 현재 configureSockets() 메서드를 아래 구현으로 바꿉니다:

    kotlin

    이 코드를 통해 다음을 수행했습니다:

    • 모든 기존 작업을 전송하는 기능을 헬퍼 메서드로 리팩터링했습니다.
    • routing 섹션에서 모든 클라이언트를 추적하기 위해 스레드 안전한 session 객체 목록을 생성했습니다.
    • 상대 URL이 /task2인 새 엔드포인트를 추가했습니다. 클라이언트가 이 엔드포인트에 연결하면 해당 session 객체가 목록에 추가됩니다. 그러면 서버는 새 작업을 수신하기 위해 무한 루프에 들어갑니다. 새 작업을 받으면 서버는 이를 리포지토리에 저장하고 현재 클라이언트를 포함한 모든 클라이언트에 복사본을 보냅니다.

    이 기능을 테스트하기 위해 index.html의 기능을 확장하는 새 페이지를 생성할 것입니다.

  2. src/main/resources/static 내에 wsClient.html이라는 새 HTML 파일을 생성합니다.

  3. wsClient.html을 열고 다음 내용을 추가합니다:

    html

    이 새 페이지는 사용자가 새 작업 정보를 입력할 수 있는 HTML 폼을 도입합니다. 폼을 제출하면 sendTaskToServer 이벤트 핸들러가 호출됩니다. 이 핸들러는 폼 데이터로 JavaScript 객체를 생성하고 WebSocket 객체의 send 메서드를 사용하여 서버로 보냅니다.

  4. IntelliJ IDEA에서 재실행 버튼 (intelliJ IDEA 재실행 아이콘)을 클릭하여 애플리케이션을 다시 시작합니다.

  5. 이 기능을 테스트하려면 두 개의 브라우저를 나란히 열고 아래 단계를 따르세요.

    1. 브라우저 A에서 http://0.0.0.0:8080/static/wsClient.html로 이동합니다. 기본 작업이 표시되어야 합니다.
    2. 브라우저 A에서 새 작업을 추가합니다. 새 작업이 해당 페이지의 테이블에 나타나야 합니다.
    3. 브라우저 B에서 http://0.0.0.0:8080/static/wsClient.html로 이동합니다. 기본 작업과 브라우저 A에서 추가한 새 작업이 모두 표시되어야 합니다.
    4. 어떤 브라우저에서든 작업을 추가합니다. 새 항목이 두 페이지 모두에 나타나야 합니다.
    HTML 폼을 통해 새 작업을 생성하는 것을 보여주는 두 개의 웹 브라우저 페이지가 나란히 표시됨

자동화된 테스트 추가

QA 프로세스를 간소화하고 빠르고, 재현 가능하며, 수동 작업 없이 수행하기 위해 Ktor의 내장된

자동화 테스트 지원
특별한 테스트 엔진을 사용하여 서버 애플리케이션을 테스트하는 방법을 알아보세요.
을 사용할 수 있습니다. 다음 단계를 따르세요:

  1. Ktor 클라이언트 내에서

    Content Negotiation
    ContentNegotiation 플러그인은 클라이언트와 서버 간의 미디어 유형 협상 및 특정 형식으로 콘텐츠를 직렬화/역직렬화하는 두 가지 주요 목적을 수행합니다.
    지원을 구성할 수 있도록 build.gradle.kts에 다음 의존성을 추가합니다:

    kotlin
  2. IntelliJ IDEA에서 편집기 오른쪽에 있는 알림 Gradle 아이콘 (intelliJ IDEA Gradle 아이콘) 을 클릭하여 Gradle 변경 사항을 로드합니다.

  3. src/test/kotlin/com/example로 이동하여 ApplicationTest.kt 파일을 엽니다.

  4. 생성된 테스트 클래스를 아래 구현으로 바꿉니다:

    kotlin

    이 설정을 통해 다음을 수행합니다:

    • 서비스가 테스트 환경에서 실행되도록 구성하고, 라우팅, JSON 직렬화 및 WebSocket을 포함하여 프로덕션 환경에서와 동일한 기능을 활성화합니다.
    • Ktor 클라이언트 내에서
      Content Negotiation
      Ktor 클라이언트를 생성하고 구성하는 방법을 알아보세요.
      및 WebSocket 지원을 구성합니다. 이것이 없으면 클라이언트는 WebSocket 연결을 사용할 때 객체를 JSON으로 직렬화/역직렬화하는 방법을 알 수 없습니다.
    • 서비스가 다시 보낼 것으로 예상되는 Tasks 목록을 선언합니다.
    • 클라이언트 객체의 websocket 메서드를 사용하여 /tasks로 요청을 보냅니다.
    • 들어오는 작업을 flow로 사용하고, 점진적으로 목록에 추가합니다.
    • 모든 작업이 수신되면 expectedTasksactualTasks와 일반적인 방식으로 비교합니다.

다음 단계

잘하셨습니다! Ktor 클라이언트와 WebSocket 통신 및 자동화된 테스트를 통합하여 Task Manager 서비스를 크게 향상시켰습니다.

Exposed 라이브러리를 사용하여 서비스가 관계형 데이터베이스와 원활하게 상호 작용하는 방법을 알아보려면

다음 튜토리얼
Exposed SQL 라이브러리를 사용하여 Ktor 서비스를 데이터베이스 리포지토리에 연결하는 과정을 알아보세요.
로 계속 진행하세요.