Chef.Yeon
Code Cook
Chef.Yeon
전체 방문자
오늘
어제
  • 분류 전체보기 (230)
    • 게임 개발 (1)
      • Unity (1)
    • Android (27)
      • Kotlin (19)
      • 우아한테크코스 5기 (4)
    • Language (11)
      • 파이썬 (3)
      • Java (7)
    • DB (2)
      • SQL (16)
    • Spring (25)
    • 코딩테스트 (56)
    • Git (1)
    • TIL (85)
    • DevOps (6)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 우아한테크코스
  • 내림차순
  • kibana
  • 프로그래머스
  • SQL
  • 레포지토리
  • java
  • 파이썬
  • Android
  • Wil
  • Docker
  • ec2
  • 코딩테스트
  • 문자열
  • 코틀린
  • MariaDB
  • 코틀린 인 액션
  • elasticsearch
  • 안드로이드
  • 다이나믹 프로그래밍
  • webflux
  • grafana
  • 에라토스테네스의 체
  • 프리코스
  • spring
  • enum
  • 백준
  • rsocket
  • til
  • kotlin

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Chef.Yeon

Code Cook

[TIL - 20230606] Webflux + Websocket 전체 채팅
TIL

[TIL - 20230606] Webflux + Websocket 전체 채팅

2023. 6. 7. 04:10

 

💻문제점

흠... webflux에서 kafka랑 rsocket 으로 채팅하는 예제를 찾으며 시도, 실패를 계속 반복하다가 webflux에서 websocket과 kafka를 적용하는 것까지 와버렸다.

 

다른 사람 레퍼런스를 봐도 이해가 안 가는 부분이 많아서... 일단 빈 프로젝트에 kafka producer, consumer를 만들어 테스트하고, websocket 연결도 따로 테스트해서 합쳐보자..!라고 생각했다. 내가 직접 안 해본 것이니 이해가 안되는 거라는 생각이 들어서...

 

그렇게 kafka 테스트를 따로 끝내고, websocket 전체 채팅을 우선 만들었다.

 

테스트를 하려고 websocket test client 크롬 확장 프로그램을 열어 url에 ws://localhost:8080/ws-chat을 입력했는데, 연결이 자꾸 실패했다.


🔍해결

build.gradle에 webflux와 websocket에 대한 종속성이 있었는데, 이게 문제였다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-websocket' //이거!
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    
    //lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    //json
    implementation 'org.json:json:20210307'
}

 

websocket 구성에 있어서 webflux랑 사용하려면 websocket 의존성을 추가할 필요는 없었다.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

 

Spring Webflux에서는 Reactor Netty를 사용하기 때문에 이미 websocket에 대한 것이 포함되어 있다고 한다.

 

톰캣을 사용하는 서블릿 기반, Spring MVC에서 사용해야 하는 것 같다.

implementation 'org.springframework.boot:spring-boot-starter-websocket'

💻구현

WebSocketConfig

@Configuration
public class WebsocketConfig {

    @Bean
    public SimpleUrlHandlerMapping handlerMapping(WebSocketHandler wsh) {
        return new SimpleUrlHandlerMapping(Map.of("/ws-chat", wsh), 1);
    }

    @Bean
    public WebSocketHandlerAdapter webSocketHandlerAdapter() {
        return new WebSocketHandlerAdapter();
    }

    @Bean
    public Sinks.Many<String> sink() {
        return Sinks.many().multicast().directBestEffort();
    }

}

 

CustomWebsocketHandler

JSON 형태로 메시지가 들어오기 때문에 username과 message를 key값으로 value값을 가져왔다.

username이 공백이라면 익명으로 변경했다.

@Component
@Slf4j
public class CustomWebSocketHandler implements WebSocketHandler {

    private final Sinks.Many<String> sink;

    public CustomWebSocketHandler(Sinks.Many<String> sink) {
        this.sink = sink;
    }

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        var output = session.receive()
                // 메시지를 JSON 객체로 변환
                .map(e -> e.getPayloadAsText())
                .map(e -> {
                    try {
                        // 메시지를 파싱
                        JSONObject json = new JSONObject(e);
                        String username = json.getString("username");
                        if (username.equals("")) username = "익명";
                        String message = json.getString("message");
                        return username + ": " + message;
                    } catch (JSONException ex) {
                        ex.printStackTrace();
                        return "메시지 처리 중 오류 발생";
                    }
                });

        output.subscribe(s -> sink.emitNext(s, Sinks.EmitFailureHandler.FAIL_FAST));

        return session.send(sink.asFlux().map(session::textMessage));
    }
}

 

HomeController

@Controller
public class HomeController {
    @GetMapping("/")
    public Mono<Rendering> home() {
        return Mono.just(Rendering.view("index").build());
    }
}

 

index.html

style 부분은 길어서 생략했다.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Commeow Chat</title>
  <style>
	// 생략
  </style>
</head>
<body>
<div id="chat-container">
  <div class="chat-box">
    <ul id="message-list"></ul>
    <div id="message-input">
      <input type="text" id="inputMessage" placeholder="메시지를 입력하세요...">
      <button onclick="sendMessage()">전송</button>
    </div>
  </div>
  <div id="nickname-input">
    <input type="text" id="username" placeholder="닉네임을 입력하세요...">
    <button id="confirmUsername" onclick="confirmUsername()">완료</button>
  </div>
</div>
<script>
  const chatWebSocket = new WebSocket('ws://' + location.host + '/ws-chat');
  const inputMessage = document.getElementById('inputMessage');
  const messageList = document.getElementById('message-list');

  chatWebSocket.onmessage = function(event) {
    const li = document.createElement('li');
    li.innerText = event.data;
    messageList.appendChild(li);
  };

  chatWebSocket.onclose = function(event) {
    alert('웹소켓이 닫혔습니다! 다시 연결하려면 페이지를 새로고침 해주세요.');
  };

  function sendMessage() {
    if (inputMessage.value.trim() === '') {
      return;
    }

    // chatWebSocket.send(inputMessage.value);
    const username = document.getElementById("username").value.trim();
    const messageObject = {
      username: username,
      message: inputMessage.value,
    };
    chatWebSocket.send(JSON.stringify(messageObject));
    inputMessage.value = '';
  }

  function confirmUsername() {
    const usernameInput = document.getElementById("username");
    const confirmButton = document.getElementById("confirmUsername");

    if (usernameInput.value.trim() !== '') {
      usernameInput.disabled = true;
      confirmButton.disabled = true;
      confirmButton.style.backgroundColor = "gray";
    }
  }
</script>
</body>
</html>

 

풀코드는 깃허브에 있다.

https://github.com/O-Wensu/webflux-websocket-chat

 

GitHub - O-Wensu/webflux-websocket-chat: webflux기반 websocket을 사용한 전체 채팅

webflux기반 websocket을 사용한 전체 채팅. Contribute to O-Wensu/webflux-websocket-chat development by creating an account on GitHub.

github.com

 

 

 

728x90

'TIL' 카테고리의 다른 글

[TIL - 20230612] OBS 방송 시작 관련 문제 해결  (0) 2023.06.13
[TIL - 20230608] Webflux + Websocket + Kafka  (0) 2023.06.08
[TIL - 20230605] RSocketRequester Redis 저장 실패  (0) 2023.06.05
[TIL - 20230603] Webflux, Mock 사용한 Channel 도메인, ChannelService 테스트 케이스 작성  (0) 2023.06.03
[TIL - 20230602] RSocket 채팅 전송 시 토큰 검사, 토큰에서 데이터 추출  (0) 2023.06.03
    'TIL' 카테고리의 다른 글
    • [TIL - 20230612] OBS 방송 시작 관련 문제 해결
    • [TIL - 20230608] Webflux + Websocket + Kafka
    • [TIL - 20230605] RSocketRequester Redis 저장 실패
    • [TIL - 20230603] Webflux, Mock 사용한 Channel 도메인, ChannelService 테스트 케이스 작성
    Chef.Yeon
    Chef.Yeon
    보기 좋고 깔끔한 코드를 요리하기 위해 노력하고 있습니다.

    티스토리툴바