-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(Chat): 채팅 메시지 발송 Websocket, Stomp API 구현
- Loading branch information
1 parent
e23df26
commit d9b4143
Showing
20 changed files
with
436 additions
and
11 deletions.
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
application/src/main/kotlin/com/threedays/application/chat/port/outbound/SessionClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.threedays.application.chat.port.outbound | ||
|
||
import com.threedays.domain.chat.entity.Message | ||
import com.threedays.domain.chat.entity.Session | ||
|
||
interface SessionClient { | ||
|
||
suspend fun sendMessage( | ||
session: Session, | ||
message: Message, | ||
) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/WebSocketSessionClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.threedays.bootstrap.api.chat | ||
|
||
import com.threedays.application.chat.port.outbound.SessionClient | ||
import com.threedays.domain.chat.entity.Message | ||
import com.threedays.domain.chat.entity.Session | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import org.springframework.stereotype.Component | ||
import org.springframework.web.socket.TextMessage | ||
import org.springframework.web.socket.WebSocketSession | ||
|
||
@Component | ||
class WebSocketSessionClient( | ||
private val webSocketSessionManager: WebSocketSessionManager, | ||
) : SessionClient { | ||
|
||
companion object { | ||
|
||
private val logger = KotlinLogging.logger {} | ||
} | ||
|
||
override suspend fun sendMessage( | ||
session: Session, | ||
message: Message | ||
) { | ||
val webSocketSession: WebSocketSession = | ||
webSocketSessionManager.findById(session.id) ?: run { | ||
logger.warn { "WebSocketSession not found: sessionId=${session.id}" } | ||
return | ||
} | ||
|
||
val textMessage = TextMessage(message.toString()) | ||
webSocketSession.sendMessage(textMessage) | ||
} | ||
|
||
} |
38 changes: 38 additions & 0 deletions
38
.../api/src/main/kotlin/com/threedays/bootstrap/api/chat/WebSocketSessionHandlerDecorator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.threedays.bootstrap.api.chat | ||
|
||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import org.springframework.stereotype.Component | ||
import org.springframework.web.socket.WebSocketHandler | ||
import org.springframework.web.socket.WebSocketSession | ||
import org.springframework.web.socket.handler.WebSocketHandlerDecorator | ||
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory | ||
|
||
@Component | ||
class WebSocketSessionHandlerDecorator( | ||
private val webSocketSessionManager: WebSocketSessionManager, | ||
) : WebSocketHandlerDecoratorFactory { | ||
|
||
companion object { | ||
|
||
private val logger = KotlinLogging.logger {} | ||
} | ||
|
||
override fun decorate(handler: WebSocketHandler) = object : WebSocketHandlerDecorator(handler) { | ||
override fun afterConnectionEstablished(session: WebSocketSession) { | ||
super.afterConnectionEstablished(session) | ||
webSocketSessionManager.addSession(session) | ||
logger.info { "WebSocketSession established: sessionId=${session.id}" } | ||
} | ||
|
||
override fun afterConnectionClosed( | ||
session: WebSocketSession, | ||
closeStatus: org.springframework.web.socket.CloseStatus | ||
) { | ||
super.afterConnectionClosed(session, closeStatus) | ||
webSocketSessionManager.removeSession(session) | ||
logger.info { "WebSocketSession closed: sessionId=${session.id}" } | ||
} | ||
} | ||
|
||
|
||
} |
34 changes: 34 additions & 0 deletions
34
bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/WebSocketSessionManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.threedays.bootstrap.api.chat | ||
|
||
import com.threedays.bootstrap.api.support.config.WebSocketProperties | ||
import com.threedays.domain.chat.entity.Session | ||
import org.springframework.stereotype.Component | ||
import org.springframework.web.socket.WebSocketSession | ||
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator | ||
import java.util.concurrent.ConcurrentHashMap | ||
|
||
@Component | ||
class WebSocketSessionManager( | ||
private val webSocketProperties: WebSocketProperties, | ||
) { | ||
|
||
private val sessions = ConcurrentHashMap<String, WebSocketSession>() | ||
|
||
fun addSession(session: WebSocketSession) { | ||
val concurrentSession = ConcurrentWebSocketSessionDecorator( | ||
/* delegate = */ session, | ||
/* sendTimeLimit = */ webSocketProperties.session.sendTimeLimit, | ||
/* bufferSizeLimit = */ webSocketProperties.session.sendBufferSizeLimit | ||
) | ||
sessions[session.id] = concurrentSession | ||
} | ||
|
||
fun removeSession(session: WebSocketSession) { | ||
sessions.remove(session.id) | ||
} | ||
|
||
fun findById(sessionId: Session.Id): WebSocketSession? { | ||
return sessions[sessionId.value] | ||
} | ||
|
||
} |
32 changes: 32 additions & 0 deletions
32
bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/support/config/WebSocketConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.threedays.bootstrap.api.support.config | ||
|
||
import com.threedays.bootstrap.api.chat.WebSocketSessionHandlerDecorator | ||
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 | ||
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration | ||
|
||
@Configuration | ||
@EnableWebSocketMessageBroker | ||
class WebSocketConfig( | ||
private val properties: WebSocketProperties, | ||
private val webSocketSessionHandlerDecorator: WebSocketSessionHandlerDecorator, | ||
) : WebSocketMessageBrokerConfigurer { | ||
|
||
override fun configureMessageBroker(registry: MessageBrokerRegistry) { | ||
registry.enableSimpleBroker(*properties.broker.simpleBroker.toTypedArray()) | ||
registry.setApplicationDestinationPrefixes(*properties.broker.applicationDestinationPrefixes.toTypedArray()) | ||
} | ||
|
||
override fun registerStompEndpoints(registry: StompEndpointRegistry) { | ||
registry | ||
.addEndpoint(properties.endpoint) | ||
.setAllowedOrigins(*properties.allowedOrigins.toTypedArray()) | ||
} | ||
|
||
override fun configureWebSocketTransport(registry: WebSocketTransportRegistration) { | ||
registry.addDecoratorFactory(webSocketSessionHandlerDecorator) | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...rap/api/src/main/kotlin/com/threedays/bootstrap/api/support/config/WebSocketProperties.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.threedays.bootstrap.api.support.config | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
|
||
/** | ||
* @param endpoint the endpoint to connect to | ||
* @param allowedOrigins list of allowed origins | ||
* @param sockJs whether to use SockJS | ||
* @param broker configuration for the broker | ||
* @param session configuration for the session | ||
*/ | ||
@ConfigurationProperties(prefix = "websocket") | ||
data class WebSocketProperties( | ||
val endpoint: String, | ||
val allowedOrigins: List<String>, | ||
val sockJs: Boolean, | ||
val broker: Broker, | ||
val session: Session, | ||
) { | ||
|
||
/** | ||
* @param simpleBroker list of broker destinations | ||
* @param applicationDestinationPrefixes list of application destinations | ||
*/ | ||
data class Broker( | ||
val simpleBroker: List<String>, | ||
val applicationDestinationPrefixes: List<String>, | ||
) | ||
|
||
/** | ||
* @param sendTimeLimit in milliseconds | ||
* @param sendBufferSizeLimit in bytes | ||
*/ | ||
data class Session( | ||
val sendTimeLimit: Int, | ||
val sendBufferSizeLimit: Int, | ||
) | ||
|
||
} |
66 changes: 66 additions & 0 deletions
66
...n/kotlin/com/threedays/bootstrap/api/support/security/interceptor/StompAuthInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.threedays.bootstrap.api.support.security.interceptor | ||
|
||
import com.threedays.application.auth.config.AuthProperties | ||
import com.threedays.domain.auth.entity.AccessToken | ||
import com.threedays.domain.chat.entity.Session | ||
import com.threedays.domain.chat.repository.SessionRepository | ||
import org.springframework.messaging.Message | ||
import org.springframework.messaging.MessageChannel | ||
import org.springframework.messaging.simp.stomp.StompCommand | ||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor | ||
import org.springframework.messaging.support.ChannelInterceptor | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class StompAuthInterceptor( | ||
private val authProperties: AuthProperties, | ||
private val sessionRepository: SessionRepository, | ||
) : ChannelInterceptor { | ||
|
||
companion object { | ||
|
||
private const val AUTHORIZATION_HEADER = "Authorization" | ||
private const val BEARER_PREFIX = "Bearer " | ||
} | ||
|
||
override fun preSend( | ||
message: Message<*>, | ||
channel: MessageChannel, | ||
): Message<*>? { | ||
val accessor = StompHeaderAccessor.wrap(message) | ||
val command = accessor.command | ||
val sessionId = accessor.sessionId | ||
?.let { Session.Id(it) } | ||
?: return message | ||
|
||
when (command) { | ||
StompCommand.CONNECT -> handleConnect(sessionId, message) | ||
else -> return message | ||
} | ||
|
||
return message | ||
} | ||
|
||
private fun handleConnect( | ||
sessionId: Session.Id, | ||
message: Message<*>, | ||
) { | ||
extractToken(message)?.let { token -> | ||
AccessToken.verify( | ||
value = token, | ||
secret = authProperties.tokenSecret, | ||
) | ||
.let { Session.create(sessionId, it.userId) } | ||
.also { sessionRepository.save(it) } | ||
} | ||
} | ||
|
||
private fun extractToken(message: Message<*>): String? { | ||
val accessor = StompHeaderAccessor.wrap(message) | ||
val bearerToken: String? = accessor.getFirstNativeHeader(AUTHORIZATION_HEADER) | ||
return if (bearerToken != null && bearerToken.startsWith(BEARER_PREFIX)) { | ||
bearerToken.substring(BEARER_PREFIX.length) | ||
} else null | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.