[Android | STOMP] 안드로이드 채팅 구현하기(1) 설정 및 connect (feat. Kotlin & Krossbow)

한시삼십사분·2024년 5월 23일
0

Android

목록 보기
9/10
post-thumbnail

안드로이드에서 STOMP를 사용하고자 하는 그대, 여기까지 흘러들어오셨군요...

🙋‍♂️ 서론

최근에 진행한 학급관리 안드로이드 프로젝트에서 채팅 기능 개발을 맡았다. 보통 안드로이드 네이티브 프로그래밍으로 채팅을 구현한다고 하면 FCM이나 socket.io를 많이 사용하는 것 같다. 하지만 백엔드 개발자가 STOMP를 사용해서 백엔드 개발을 거의 완성해둔 상황이었기 때문에 나도 STOMP 사용해서 채팅 기능을 구현해야 했다.

STOMP?

STOMP (Simple Text Oriented Messaging Protocol)


STOMP는 단순 텍스트 지향 메시징 프로토콜로, 주로 메시징 중개자를 통해 통신하는 프로토콜입니다. 주로 웹 소켓을 통해 사용되며, 메시지 브로커(우리 프로젝트는 Kafka를 사용했다)와 통신할 때 유용합니다. STOMP는 다음과 같은 특징을 가지고 있습니다:

  • 단순함: 텍스트 기반의 프로토콜로, 이해하기 쉽고 디버깅하기도 용이합니다.
  • 다양한 클라이언트 지원: 다양한 언어와 플랫폼에서 사용할 수 있는 클라이언트 라이브러리를 제공합니다.
  • 확장성: 메시징 브로커를 통해 높은 확장성을 제공합니다.

라고 한다.
사실 나는 websocket을 사용해본 적도 없기 때문에 장단점을 비교할 수가 없었기 때문에 개발과정에서 내가 체감한 장단점을 적어보자면

  • 단순함: 초기 설정과 연결에 성공했다면 이후 메세지를 송수신 하는 것은 간단했던 것 같다.
  • 레퍼런스의 부족: 하지만 안드로이드에서 STOMP를 사용하는 레퍼런스를 찾기가 매우 힘들다.

레퍼런스가 정말 정말 적다... 보통 websocket 라이브러리인 socket.io를 사용해서 채팅을 구현한 레퍼런스가 대부분이고 stomp로 구현한 글을 찾으면 자료가 많이 부족하다.

쓸만한 Library가 없네...

몇 개 없는 블로그를 읽어보면 모든 글에서 이 라이브러리를 사용한다. 예.. 개인 개발자가 만들어놓은 4년전 update가 마지막인 라이브러리다. 그마저도 Java로 개발했기 때문에 같은 라이브러리를 Kotlin 버전으로 만든 라이브러리를 보면 5년전이 마지막 업데이트고 문서라고 할 것 조차 없이 몇 줄의 예제 코드가 전부다.

나도 저 라이브러리를 사용해서 개발을 시작했지만 내가 커스텀해서 사용할 수 있는 부분이 적었고, 옛날에 개발된 후 업데이트가 이루어지지 않은 라이브러리다보니 다른 라이브러리, gradle, android 등등 과의 버전과 호환되지 않는 부분도 존재했다.


나는 다시 사이버 망령이 되어서 라이브러리를 찾기 시작했다.

Krossbow

그러던 중 발견한 한 줄기의 빛 Krossbos를 발견했다. 미친 신뢰를 주는 star 160회, latest releas 2 weeks ago, 세세하게 정리된 공식 문서까지... 이 라이브러리가 나를 구원해줄 것만 같았다.

Krossbow

Kotlin 멀티플랫폼 프로젝트를 위한 WebSocketSTOMP 클라이언트 라이브러리다. Android 뿐만 아니라 JVM, JavaScript, ios 등 여러 플랫폼에서 WebSocket과 STOMP 사용을 지원한다. 게다가 코루틴을 활용하여 비동기 프로그래밍을 쉽게 할 수 있게 해준다.

또한 여러 종류의 Json 파싱 라이브러리에도 대응한다. moshi, Jackson, Koltinx Serialization, custom conversation 까지 지원하기 때문에 더욱 편한 개발을 할 수 있다.

유일한 단점 아닌 단점을 뽑자면 한국어로 된 레퍼런스가 없다는 것...? 공식 문서를 열심히 읽고 번역해가며 사용해야 한다.

개발 환경 설정

dependency

implementation("org.hildan.krossbow:krossbow-stomp-core:${your version}")
implementation("org.hildan.krossbow:krossbow-websocket-okhttp:${your version}")
implementation("org.hildan.krossbow:krossbow-stomp-moshi:${your version}")

Websocket Client로는 OkHttp를, Json 파싱 라이브러리는 Moshi를 사용했기 때문에 앞으로 등장할 코드는 해당 라이브러리에 대응된 코드다.

Stomp Client 생성

OkHttp Client에 추가적인 설정을 할 필요가 없다면 다음과 같이 사용할 수 있다.

val client = StompClient(OkHttpWebSocketClient())

OkHttp Client에 추가적인 설정을 할 수 있다.

val okHttpClient = OkHttpClient.Builder()
    .callTimeout(Duration.ofMinutes(1))
    .pingInterval(Duration.ofSeconds(10))
    .build()
val wsClient = OkHttpWebSocketClient(okHttpClient)
val stompClient = StompClient(wsClient)

본인은 로깅을 위해 HttpLoggingInterceptor를 추가적으로 사용했다.

val okHttpClient = OkHttpClient.Builder()
	.addInterceptor(
		HttpLoggingInterceptor().apply {
			level = HttpLoggingInterceptor.Level.BODY
        }
    )
    .build()

Json 파싱 라이브러리 설정

본인은 Moshi를 사용함

val moshi: Moshi = Moshi.Builder()
	.addLast(KotlinJsonAdapterFactory())
    .build()

STOMP Connect

우선 connect 함수에 대해 알아보자.

suspend fun connect(
        url: String,
        login: String? = null,
        passcode: String? = null,
        host: String? = DefaultHost,
        customStompConnectHeaders: Map<String, String> = emptyMap(),
        sessionCoroutineContext: CoroutineContext = EmptyCoroutineContext,
    ): StompSession

Connects to the given WebSocket [url] and to the STOMP session, and returns after receiving the CONNECTED frame.
=> 주어진 WebSocket [url]에 연결하고 STOMP 세션에 연결한 후, CONNECTED 프레임을 수신한 후에 반환합니다.

파라미터를 하나씩 알아보자

  • url : end point의 url.
    이때 https://velog.com/postend point 라면
    wss://velog.com/post/websocket 으로 사용해야 한다.
    httpws로, httpswss로 바꾸고, url 마지막에 /websocket을 추가해야한다.

  • login, passcode : 우선 나는 2개의 파라미터는 사용하지 않았다, websocket 연결이 성립되고 stomp level 연결에서 사용된다고 한다. 또한 Spring STOMP Authentication 문서에서도

    STOMP 프로토콜에는 CONNECT 프레임에 로그인 및 패스코드 헤더가 있습니다. 이러한 헤더는 원래 STOMP over TCP를 위해 설계되었으며 필요합니다. 그러나 WebSocket을 통한 STOMP의 경우 기본적으로 Spring은 STOMP 프로토콜 수준에서 인증 헤더를 무시하고, 사용자가 HTTP 전송 수준에서 이미 인증되었음을 가정합니다.

    라고 말하고 있다. 추가적인 인증이 필요할 때 사용하는 것 같다.

  • host : STOMP의 host 헤더는 클라이언트가 연결하려는 STOMP 서버의 호스트 이름을 지정하는 데 사용된다. 제공된 URL의 호스트로 기본 설정된다. 위에서도 DefaultHost로 기본값을 지정하고 있다.

  • customStompConnectHeaders : 커스텀 헤더를 포함할 수 있다. 이때 주의할 것은 Http Header가 아닌 STOMP Frame에 헤더를 추가하는 것이기 때문에 헤더 추가를 꼭 이 부분에서 해야 한다. 나도 처음에는 OkHttpClient에 헤더를 추가했는데 서버측에서 받지 못하는 것을 확인했다. 헤더는 Map을 사용해서 Key-Value 형태로 사용한다.

  • sessionCoroutineContext : Coroutine Context를 재정의 할 수 있다. 기본값으로는 Dispatchers.Default를 사용한다.


    파라미터가 다양하지만, 특별한 경우가 아니라면 urlcustomHeader 정도만 사용할 것이다.

    위에서 설정한 STOMP ClientMoshi를 사용해서 연결해보자

    val stompSession = client.connect(
                   "${your endpoint}",
                   customStompConnectHeaders = mapOf(
                       TOKEN to ${TOKEN_VALUE}
                   )
               ).withMoshi(moshi)

    나는 JWT AccessToken을 헤더에 담아서 보내야했다.
    연결에 성공한다면 성공 프레임이 올 것이다.

    아! 물론 connect 함수는 suspend function이기 때문에 Coroutine Scope 내에서 사용해야 한다.

마무리

여기까지 성공했다면 70%는 성공했다고 봐도 된다. 라이브러리 교체, 레퍼런스 부족, 서버 에러(서버에서 Nginx를 사용하고 있다면 추가적으로 해야할 설정이있다.) 등등으로 인해 연결에만 한 3일은 걸린 것 같다.

새로운 기술을 학습하고 사용하는 것은 재미있었지만, 서버 에러와 안드로이드 에러가 모두 쏟아졌기 때문에 3일 정도 백엔드 개발자와 마치 신혼 부부가 된 것 같이 붙어다녔다.(둘다 남자임)

글이 길어지니 구독, 메세지 전송, 연결 중지 등은 다음 글에서 다루겠다.

추가적으로 백엔드 개발자의 블로그가 포스팅 된다면 링크를 걸어둘 예정이니, 같이 참고하면 더 좋을 듯 하다.

profile
인간은 망각의 동물이라지만 이건 너무한 거 아니냐고

2개의 댓글

comment-user-thumbnail
2024년 6월 28일

잘 읽었습니다~~ 다음편은 없나요??

1개의 답글