본문 바로가기
개발 이야기/Springboot

[Kotlin] Springboot + WebSocket 사용법

by 농개 2020. 6. 7.
반응형

Kotlin + Springboot 환경에서 WebSocket(웹소켓)을 사용하기 위한 방법을 기술합니다.

간단한 채팅앱을 만들어 보며 WebSocket 설정과 사용법을 알아보겠습니다.

(백엔드. 즉, Kotlin + Springboot + Gradle 환경 구성에 대한 내용만을 다루었습니다.)

 

 

 

01. 모듈 생성/다운로드

https://start.spring.io/를 통해 Springboot를 위한 모듈을 생성해봅니다. 

위와 같이 설정 체크 해주고 GENERATE 클릭 해서 zip파일을 다운 받습니다.

다운받은 zip파일 압축 해제 하고, IDE에서 해당 프로젝트를 추가해봅시다.

그리고 build.gradle.kt 파일을 열어보면 아래와 같습니다.

plugins {
	id("org.springframework.boot") version "2.3.0.RELEASE"
	id("io.spring.dependency-management") version "1.0.9.RELEASE"
	kotlin("jvm") version "1.3.72"
	kotlin("plugin.spring") version "1.3.72"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("org.springframework.boot:spring-boot-starter-websocket")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
	testImplementation("org.springframework.boot:spring-boot-starter-test") {
		exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs = listOf("-Xjsr305=strict")
		jvmTarget = "1.8"
	}
}

 

 

 

02. 패키지 구조

아래와 같은 형태로 패키지 구조를 잡았습니다.

WebsocApplication.kt (위의 과정에서 자동으로 생성된 Springboot 앱을 실행하는 메인 클래스입니다)와 같은 경로에 아래 두 패키지를 만들어 줬습니다.

  • config : Websocket 설정을 위한 패키지
  • chat : 채팅 기능을 위한 패키지

 

03. Config 작성

아래와 같이 Config 파일을 작성해봅시다.

package com.example.websoc.config

import org.springframework.context.annotation.Configuration
import org.springframework.messaging.simp.config.MessageBrokerRegistry
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer


@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/ws").withSockJS()
    }

    override fun configureMessageBroker(config: MessageBrokerRegistry) {
        config.setApplicationDestinationPrefixes("/app") 
        config.enableSimpleBroker("/topic") 
    }
}

@Configuration : 설정파일을 의미하는 어노테이션입니다.

@EnableWebSocketMessageBroker : Websocket을 통한 메시징 기능을 활성화 시킵니다.

 

WebSocketMessageBrokerConfigurer 인터페이스를 상속받아 아래 메서드들을 커스터마이징 해줍니다.

  • registerStompEndpoints : Websocket 연결을 위한 엔드포인트를 지정해줍니다.
  • configureMessageBroker : 메시지 주고 받을 엔드포인트에 대한 Prefix를 정해줍니다.
    • setApplicationDestinationPrefixes : 서버가 목적지 일때(Client -> Server 메시지 전송시 Endpoint)
    • enableSimpleBroker : 클라이언트가 Subscribe 할떄(Server -> Client 메시지 전송 시 Endpoint)

Stomp

Stomp는 Websocket 연결 시, 메시지의 형태(텍스트 또는 바이너리)를 HTTP 통신 프로토콜과 비슷하게 가공해주는 라이브러리입니다. 위 코드가 STOMP를 사용한 Websocket 설정이고, 기존에 Websocket을 설정에 비해 간소하고 쉽게 사용할 수 있게 도와줍니다. WebSocketConfigurer 인터페이스의 고수준 API를 사용케 해줍니다.

일종의 Websocket 메시지 브로커. (spring-boot-starter-websocket 에 포함됨)

 

 

 

설정이 제대로 되었다면 애플리케이션을 한번 실행 해봅시다.

아래와 같은 로그가 보인다면 정상적으로 WebSocket 설정이 된것입니다.

...(중략)
2020-06-07 14:44:12.038  INFO 12680 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-07 14:44:12.040  INFO 12680 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Starting...
2020-06-07 14:44:12.041  INFO 12680 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : BrokerAvailabilityEvent[available=true, SimpleBrokerMessageHandler [DefaultSubscriptionRegistry[cache[0 destination(s)], registry[0 sessions]]]]
2020-06-07 14:44:12.042  INFO 12680 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Started.
2020-06-07 14:44:12.058  INFO 12680 --- [           main] com.example.websoc.WebsocApplicationKt   : Started WebsocApplicationKt in 4.903 seconds (JVM running for 5.633)

 

 

04. Model 정의

아래와 같이 Chat 기능을 위한 도메인 모델을 정의 해줬습니다.

// ChatMessage.kt
package com.example.websoc.chat.domain

data class ChatMessage (
    var type: MessageType,
    var content: String?,
    var sender: String
)


// MessageType.kt
package com.example.websoc.chat.domain

enum class MessageType {
    CHAT,
    JOIN,
    LEAVE
}

 

 

 

05. Controller 정의

아래와 같이 채팅 기능을 위한 엔드포인트를 정의해줍니다.

package com.example.websoc.chat

import com.example.websoc.chat.domain.ChatMessage
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.handler.annotation.Payload
import org.springframework.messaging.handler.annotation.SendTo
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.web.bind.annotation.RestController


@RestController
class ChatController {
    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    fun sendMessage(@Payload chatMessage: ChatMessage?): ChatMessage? {
        return chatMessage
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    fun addUser(@Payload chatMessage: ChatMessage, headerAccessor: SimpMessageHeaderAccessor): ChatMessage? {
        headerAccessor.sessionAttributes!!["username"] = chatMessage.sender
        return chatMessage
    }
}

클라이언트에서 서버로 메시지를 보내는 엔드 포인트를 /chat.sendMessage 로 정의했으며,

동시에 /topic/public 를 통해 구독(subscribe)중인 클라이언트 앱에 ChatMessage 데이터를 발행(publish)합니다.

 

/chat.addUser는 채팅방에 유저가 접속할 때 실행되는 메서드이며, Session에 유저를 추가해줍니다.

 

 

 

06. EventListener를 추가하려면...

WebSocket 연결/해제 될 때 이벤트를 받아 비즈니스 로직을 추가하거나 Log를 남기는 등의 작업을 요할 때 이벤트 리스너를 설정해줄수 있습니다. 아래와 같이 이벤트 리스너를 작성해 봅시다.

package com.example.websoc.chat.listener

import com.example.websoc.chat.domain.ChatMessage
import com.example.websoc.chat.domain.MessageType
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.event.EventListener
import org.springframework.messaging.simp.SimpMessageSendingOperations
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
import org.springframework.stereotype.Component
import org.springframework.web.socket.messaging.SessionConnectedEvent
import org.springframework.web.socket.messaging.SessionDisconnectEvent

@Component
class WebSocketEventListener(
    private val messagingTemplate: SimpMessageSendingOperations?
) {
    private val logger: Logger = LoggerFactory.getLogger(WebSocketEventListener::class.java)

    @EventListener
    fun handleWebSocketConnectListener(event: SessionConnectedEvent?) {
        logger.info("Received a new web socket connection")
    }

    @EventListener
    fun handleWebSocketDisconnectListener(event: SessionDisconnectEvent) {
        val headerAccessor = StompHeaderAccessor.wrap(event.message)
        val username = headerAccessor.sessionAttributes!!["username"] as String?
        if (username != null) {
            logger.info("User Disconnected : $username")
            val chatMessage = ChatMessage(MessageType.LEAVE, "", username)

            messagingTemplate!!.convertAndSend("/topic/public", chatMessage)
        }
    }
}

Connection, Disconnection 시 이벤트를 다루기 위한 리스너 함수를 작성해주었습니다.

 

 

이렇게 하면 간단한 채팅 기능을 위한 Websocket 백엔드 서버의 구현은 완료 되었습니다.

이제 클라이언트 단의 기능만이 남았습니다.

 

아래 블로그의 내용과 같이 따라 해보시면 완벽히 동작 할것입니다.

https://ratseno.tistory.com/72?category=773803

 

[Spring]Springboot + websocket 채팅[2]

이전 포스팅 : https://ratseno.tistory.com/71 이번 포스팅에서는 화면 구성을 진행해보도록 하겠습니다. 최종 구성은 아래와 같습니다. src / main / resources / static 폴더는 Spring Boot에서 정적 파일의..

ratseno.tistory.com

 

참조:

https://ratseno.tistory.com/71

https://ratseno.tistory.com/72

반응형