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
참조:
'개발 이야기 > Springboot' 카테고리의 다른 글
[Kotlin] Springboot, Mock을 활용한 유닛 테스트(feat. Method Stub) (0) | 2021.02.27 |
---|---|
[Kotlin] Springboot, AOP를 활용한 요청 데이터 조작(feat. Reflection) (0) | 2021.02.09 |
[Kotlin] Springboot + Redis 사용법 (0) | 2020.04.26 |
[Kotlin] Springboot + Mybatis 사용법 (0) | 2020.03.28 |
[Kotlin] Springboot에서 MySQL + JPA 사용법 (0) | 2020.02.23 |