inflearn logo
강의

Khóa học

Chia sẻ kiến thức

채팅에 대해서 질문이 있습니다. ㅠㅠ

65

dbekdms1473339

49 câu hỏi đã được viết

0

...

  // WebSocket 연결 및 STOMP 클라이언트 설정
  useEffect(() => {
    const token = sessionStorage.getItem("accessToken"); // 세션에서 액세스 토큰을 가져옴
    const socket = new SockJS(`http://localhost:8080/ws/chat`); // WebSocket 연결
    const client = Stomp.over(socket);
    stompClientRef.current = client;

    // STOMP 연결
    client.connect(
      { Authorization: `Bearer ${token}` },
      (frame) => {
        setConnected(true);
        console.log("STOMP 연결 성공", frame);

        // 해당 채팅방에 대한 메시지 구독
        client.subscribe(`/exchange/chat.exchange/room.${roomId}`, (msg) => {
          const receivedMessage = JSON.parse(msg.body);
          if (receivedMessage.type === "PLACE") {
            const place = JSON.parse(receivedMessage.message);
            setMessages((prev) => [...prev, { type: "PLACE", place }]);
          } else {
            setMessages((prev) => [...prev, receivedMessage]);
          }
        });
      },
      (error) => {
        console.error("STOMP 연결 실패:", error);
        alert("STOMP 연결 실패! 서버가 실행 중인지 확인하세요.");
      }
    );

    // 채팅방 목록 가져오기
    fetch(`${BASE_URL}/chat/rooms`, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
      .then((res) => res.json())
      .then((data) => setChatRooms(data))
      .catch((error) => console.error("채팅방 목록 가져오기 실패:", error));

    return () => {
      if (client.connected) client.disconnect();
    };
  }, [roomId]);

  // 메시지 보내는 기능
  const sendMessage = (message, type = "TALK") => {
    const token = localStorage.getItem("accessToken");
    stompClientRef.current?.send(
      `/pub/chat.message.${roomId}`,
      { Authorization: `Bearer ${token}` },
      JSON.stringify({
        sender: currentUser,
        message,
        roomId,
        type,
        timestamp: new Date().toISOString(),
      })
    );
    if (type === "TALK") setNewMessage("");
  };

 ...

리액트는 다음과 같이 구성하고

@Controller @RequiredArgsConstructor @Log4j2 public class ChatController implements ChatControllerDocs { private static final String CHAT_EXCHANGE_NAME = "chat.exchange"; private final static String CHAT_QUEUE_NAME = "chat.queue"; private final ChatService chatService; private final RabbitTemplate rabbitTemplate; @Override // 클라이언트에서 서버로 보낸 메시지를 메시지를 라우팅 // @MessageMapping("chat.message")로 설정하여 클라이언트로부터 /pub/chat.message 목적지로 전송된 STOMP 메시지를 처리한다. /*RabbitMQ*/ @MessageMapping("chat.message.{roomId}") /*STOMP*/ // @MessageMapping("/{roomId}") // 구독한 클라이언트에게 response를 제공할 url 정의 // @SendTo("/topic/{roomId}") public ResponseEntity<?> sendMessage( // @Payload: 메시지의 body를 정의한 객체에 매핑합니다. @Payload ChatMessageDTO message, // @DestinationVariable: 구독 및 메시징의 동적 url 변수를 설정. RestAPI의 @PathValue와 같다. @DestinationVariable int roomId) { try { ChatMessageDTO msg = chatService.sendMessage(message); log.info("Sent message: {}", msg); if (msg != null) { // RabbitMQ으로 메시지 전송 // template.convertAndSend() 메소드를 사용하여 메시지를 RabbitMQ로 전송한다. // 메시지는 chat.exchange로 전송되며, 라우팅 키는 room. + 메시지의 방 ID로 구성된다. rabbitTemplate.convertAndSend(CHAT_EXCHANGE_NAME, "room." + roomId, message); } else { log.error("Failed to create chat message. User might not be in the chat room. User: {}, Room: {}", message.getSender(), message.getRoomId()); } return ResponseEntity.ok().body(msg); } catch (Exception e) { log.error("Error processing message: ", e); throw new ChatException(e.getMessage()); } }

 


@Configuration
@EnableRabbit
@RequiredArgsConstructor
public class RabbitConfig {
    // Queue (큐): RabbitMQ에서 메시지를 저장하는 장소
    private static final String CHAT_QUEUE_NAME = "chat.queue";
    // Exchange (교환기): 메시지를 Queue로 라우팅(보내는) 역할
    private static final String CHAT_EXCHANGE_NAME = "chat.exchange";
    // Routing Key (라우팅 키): Exchange가 메시지를 어떤 Queue로 보낼지를 결정하는 데 사용
    private static final String ROUTING_KEY = "room.*";
    @Value("${spring.rabbitmq.host}")
    private String host;
    @Value("${spring.rabbitmq.port}")
    private int port;
    @Value("${spring.rabbitmq.username}")
    private String userName;
    @Value("${spring.rabbitmq.password}")
    private String password;

    // Queue 등록
    @Bean
    public Queue queue() {
        return new Queue(CHAT_QUEUE_NAME, true);
    }

    // Exchange 등록
    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(CHAT_EXCHANGE_NAME);
    }

    // Exchange와 Queue바인딩
    @Bean
    public Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with(ROUTING_KEY);
    }

    // RabbitMQ와의 메시지 통신을 담당하는 클래스
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setMessageConverter(jsonMessageConverter());
        rabbitTemplate.setRoutingKey(ROUTING_KEY);
        return rabbitTemplate;
    }

    // RabbitMQ와의 연결을 관리하는 클래스
    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost(host);
        factory.setPort(port);
        factory.setVirtualHost("/");
        factory.setUsername(userName);
        factory.setPassword(password);
        return factory;
    }

    // Queue를 구독(Subscribe)하는 걸 어떻게 처리하느냐에 따라 필요함. 당장은 없어도 됨.
    @Bean
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory,
                                                                                     MessageConverter messageConverter) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(messageConverter);
        return factory;
    }

    @Bean
    public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
        return new RabbitMessagingTemplate(rabbitTemplate);
    }

    // 메시지를 JSON형식으로 직렬화하고 역직렬화하는데 사용되는 변환기
    // RabbitMQ 메시지를 JSON형식으로 보내고 받을 수 있음
    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        //LocalDateTime serializable을 위해
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
        objectMapper.registerModule(dateTimeModule());

        Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(objectMapper);

        return converter;
    }

    @Bean
    public Module dateTimeModule() {
        return new JavaTimeModule();
    }
}
...

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
                .setErrorHandler(stompExceptionHandler)
                // 소켓 연결 URI다. 소켓을 연결할 때 다음과 같은 통신이 이루어짐
                .addEndpoint("/ws/chat")
                .setAllowedOriginPatterns("http://localhost:5173")
                // SocketJS를 통해 연결 지원
                .withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        log.info("--------------");
        log.info("동작함");
        registration.interceptors(stompHandler);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        // url을 chat/room/3 -> chat.room.3으로 참조하기 위한 설정
        registry.setPathMatcher(new AntPathMatcher("."));
        registry.setUserDestinationPrefix("/sub");           // 클라이언트 구독 경로

        // RabbitMQ 브로커 리레이 설정
        registry.enableStompBrokerRelay("/exchange", "/queue", "/topic")
                .setRelayHost(host)
                .setRelayPort(61613)
                .setClientLogin(userName)
                .setSystemPasscode(password)
                .setSystemLogin(userName)
                .setSystemPasscode(password);
    }
...
}

이렇게 구성했는데 rabbitMQ는 도커 컴포즈로 구성했습니다.
근데 다음과 같은 문제가 발생했는데

Chat.jsx:31
GET http://localhost:8080/ws/chat/285/4mxcvol4/jsonp?c=_jp.apvvzrr net::ERR_ABORTED 404 (Not Found)

chat:1 Refused to execute script from 'http://localhost:8080/ws/chat/285/4mxcvol4/jsonp?c=_jp.apvvzrr' because its MIME type ('') is not executable, and strict MIME type checking is enabled.
이렇게 계속해서 연결이 끊깁니다 어떻게 해야하나요?

rabbitmq react stomp websocket springboot restapi jwt socketjs

Câu trả lời 1

0

communityai8509

안녕하세요, 인프런 AI 인턴입니다.

유요한님, WebSocket 연결 문제를 해결하기 위해 아래 몇 가지 사항을 확인해보세요.

  1. WebSocket 경로 확인:

    • StompEndpointRegistry에서 설정한 경로 (/ws/chat)가 클라이언트에서 연결하려는 경로와 동일한지 확인하세요.
    • 클라이언트 측의 SockJS 경로와 서버의 경로가 /ws/chat으로 일치하는지 확인하세요.
  2. CORS 설정 확인:

    • WebSocket의 CORS가 허용되어 있는지 확인하세요. setAllowedOriginPatterns 메서드에 클라이언트 도메인이 포함되어 있는지 확인합니다.
  3. Spring Security 설정:

    • Spring Security를 사용하고 있다면, WebSocket 엔드포인트에 대한 접근 제어 설정이 되어 있는지 확인하세요.
  4. SockJS와 WebSocket 중복 설정 확인:

    • 서버에서 SockJS와 WebSocket 설정이 맞게 되어 있는지 확인하세요.
  5. RabbitMQ 설정:

    • RabbitMQ의 호스트, 포트, 사용자 인증 정보가 정확한지 확인하고 enableStompBrokerRelay 설정이 맞는지 점검합니다.
  6. Network 관련 사항:

    • Docker Compose로 RabbitMQ를 구성한 경우, 네트워크 설정이 올바르게 되어 있는지 확인하세요.
    • RabbitMQ가 제대로 실행되고 있고, 클라이언트에서 접근할 수 있는지를 확인합니다.

만약 위의 방법으로 해결이 안 된다면, 서버 로그를 자세히 확인하여 추가적인 오류 메시지가 있는지 살펴보시고, 문제가 지속될 경우 더 많은 정보를 제공해 주시면 도움이 될 수 있습니다.

더 자세한 내용은 Spring Boot Websockets Documentation도 참고해 보시기 바랍니다.

안티그래비티 확장프로그램

0

3

1

cd 명령어가 안들어요

0

8

1

클로드 데스크앱과의 차이

0

10

1

nmp run build의 기능

0

5

1

plan mode 개발 계획안 확인 불가

0

13

2

명시적 타입 선언(콜론 타입 선언)과 as 타입 단언 차이

0

10

1

max x5 플랜을 결제했습니다.

0

17

1

클로드 초기 설정

0

16

1

사용자 스코프 설정 파일 적용 문제

0

10

1

클로드코드 유료플랜 할인 방법이 있을까요?

0

30

0

API Error : 400 에러의 원인과 해결방법이 궁금합니다!!

0

22

2

퍼미션 권한 설정 문의

0

25

2

커서에서 shift+enter가 안됩니다.

0

26

2

mcp 설치를 못하겠어요

0

37

2

라이브러리 관련 질문 있습니다!

0

28

2

output-styles은 Claude Code의 공식 기능이 아니라고 하는데 혹시 변경된걸까요?

0

28

2

클로드 변경

0

28

2

/config 에서 output-style 을 변경

0

27

1

한국어 문제

0

28

2

Node.js 관련 질문드립니다.

0

32

3

클로드 버전업 설치

0

28

2

쿠폰 문의 드립니다.

0

21

2

13강 프로젝트 생성 Next.js 설치이슈

0

28

3

Shell Command: Install 'cursor' command 진행에서 막혔습니다

0

29

3