Wynn 호텔에 들러 식사를 하고나서 Area51 을 모티브로 만들어진 라스베가스의 Area15 을 다녀왔다...! 이상한 물건들 투성이었다.

 

긴 터널을 통과하면서 숨겨진 구역들을 구석구석 다녀볼 수 있었다.

 

 

 

 

 

- TIL -

구현 목표)

API 통신을 통해 채팅방 값을 획득

 

웹 소켓과 STOMP 를 사용하여 방 번호에 대한 채팅을 제어

 

WebSocketSTOMP 를 활용하여 랜덤한 유저끼리 1:1 채팅을 맺어주는 서비스를 개발할 것이다.

이때 A, B, C, D 순으로 웹소켓에 접근하게되면 접근 순서대로 A, B 를 매칭시키고 C, D 를 매칭시키는 형식으로 개발을 진행할 것이며

프론트에서 먼저 채팅 시작 버튼을 누르게되면 Spring 서버로 API 요청을 보내게 되며 서버는 이 요청을 통해 DB 에 매칭된 방에 대한 데이터를

저장한 뒤 랜덤하게 생성된 방번호의 ID 를 반환하게 되며 프론트에서는 이 ID 를 받아 해당 ID 를 붙인 STOMP 서버를 구독하게 만들 것이다.

 

 

 

- Spring 서버 코드 -

 

 

 

의존성 추가)

더보기
// Websocket 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-websocket'

// STOMP 가 포함된 Spring-Boot-Starter-Web 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-web'

spring-boot-starter-web 의존성은 기본적으로 스프링 서버를 구현할때 추가하는 의존성이므로 이미 존재한다면 추가하지 않아도 된다.

 

 

 

WebSocketConfig.class 작성)

더보기
package com.randomchat.main.config.chat;

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
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // WebSocket 의 엔드포인트 설정
        registry.addEndpoint("/chat") // 클라이언트의 연결 엔드포인트
                .setAllowedOrigins("*"); // 모든 도메인에서의 연결을 허용
    }
}

클라이언트의 웹소켓 접근 엔드포인트를 설정

 

 

 

ChatController.class 작성)

더보기
package com.randomchat.main.controller.chat;

import com.randomchat.main.service.chat.MatchingService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {

    private final MatchingService matchingService;

    @PostMapping("/enterChatRoom")
    public ResponseEntity<Map<String, Long>> enterChatRoom(@RequestBody String nickname) {
        Long chatUUID = matchingService.enterChatRoom(nickname);
        Map<String, Long> response = new HashMap<>();
        response.put("roomId", chatUUID);
        return ResponseEntity.ok(response); // OK 상태코드와 함께 생성된 방의 UUID 를 반환한다.
    }

}

 클라이언트는 최초 채팅방 접속 시도 시 서버에 API 요청을 통해 자신의 닉네임 정보를 전송하고 서버는 이를 받아 DB 에 새로운 채팅방 정보를

저장한 뒤 생성된 방번호의 ID 정보를 클라이언트에게 리턴한다.

( 이후 클라이언트는 이 방번호로 채팅 채널을 구독한다. )

 

 

 

MatchingService.class 작성)

더보기
package com.randomchat.main.service.chat;

import com.randomchat.main.domain.chat.ChatRoom;
import com.randomchat.main.repository.chat.ChatRoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor
public class MatchingService {

    private final ChatRoomRepository chatRoomRepository;
    private final Map<String, Long> chatRoomList = new ConcurrentHashMap<>();

    public Long enterChatRoom(String nickname) {
        Optional<ChatRoom> findRoom = chatRoomRepository.findFirstBySecondUserIsNullAndOpenTrue();

        if(findRoom.isPresent()) {
            ChatRoom chatRoom = findRoom.get();
            ChatRoom changeChatRoom = chatRoom.addUser(nickname);
            chatRoomRepository.save(changeChatRoom);
            chatRoomList.put(nickname, chatRoom.getId()); // 채팅방 관리 Map 에 닉네임과 채팅방 ID 등록
            return chatRoom.getId();
        }else {
            // 매칭 가능한 방이 없는 경우 새 방 생성
            ChatRoom chatRoom = new ChatRoom(nickname);
            chatRoomRepository.save(chatRoom);
            chatRoomList.put(nickname, chatRoom.getId()); // 채팅방 관리 Map 에 닉네임과 채팅방 ID 등록
            return chatRoom.getId();
        }
    }

    public void closeChatRoom(String channel) {
        String[] parts = channel.split("/");
        Long roomId = Long.parseLong(parts[parts.length - 1]);
        ChatRoom room = chatRoomRepository.findById(roomId).get();
        ChatRoom ClosedChatRoom = room.closeRoom();
        chatRoomRepository.save(ClosedChatRoom);
    }
}

 사용자가 접속 시 채팅방을 생성 및 접속 해제 시 채팅방을 닫는 메소드

( chatRoomList 는 추후에 토큰 값으로 유저를 관리하려고 생성해둔 부분 )

 

 

 

ChatRoomRepository.class 작성)

더보기
package com.randomchat.main.repository.chat;

import com.randomchat.main.domain.chat.ChatRoom;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ChatRoomRepository extends JpaRepository<ChatRoom, Long> {
    Optional<ChatRoom> findFirstBySecondUserIsNullAndOpenTrue();
}

Spring Data JPA 구현체를 활용하여 채팅방을 탐색하여 두 번째 유저가 null 이며 채팅방 상태가 활성화(Open == true)인 데이터를 가져오는

메소드 생성

 

 

 

ChatRoom.class 작성)

더보기
package com.randomchat.main.domain.chat;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
public class ChatRoom {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String firstUser;

    @Column
    private String secondUser;

    @Column(nullable = false)
    private boolean open;

    public ChatRoom(String nickname) {
        this.firstUser = nickname;
        this.open = true;
    }

    public ChatRoom addUser(String nickname) {
        this.secondUser = nickname;
        return this;
    }

    public ChatRoom closeRoom() {
        this.open = false;
        return this;
    }
}

채팅방 유저 둘과 채팅방 아이디, 채팅방 활성화 상태를 관리하는 ChatRoom 도메인

 

 

 

ChannelEventListener.class 작성)

더보기
package com.randomchat.main.config.chat;

import com.randomchat.main.dto.chat.SendMessageDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@RequiredArgsConstructor
public class ChannelEventListener {
    // 매칭 이벤트를 담당하는 클래스
    // 2명이 같은 채널에 입장 시 그 채널로 메세지 발송

    private final ConcurrentHashMap<String, AtomicInteger> channelSubscribers = new ConcurrentHashMap<>(); // 채널별 구독자 수를 관리하기 위한 맵
    private final SimpMessagingTemplate simpMessagingTemplate;
    public final Map<String, String> sessionRoomNumberList = new ConcurrentHashMap<>(); // 세션과 채널을 매칭하는 맵

    // 구독 이벤트 처리
    @EventListener
    public void handleSubscribeEvent(SessionSubscribeEvent event) {
        SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
        String destination = headers.getDestination(); // 구독 경로
        String sessionId = headers.getSessionId(); // 세션 아이디

        if (destination != null) {
            // 채널의 구독자 수 증가
            int subscriberCount = channelSubscribers
                    .computeIfAbsent(destination, key -> new AtomicInteger(0))
                    .incrementAndGet();
            sessionRoomNumberList.put(sessionId, destination);

            if (subscriberCount == 2) {
                // 구독자가 2명이 되면 매칭 안내
                sendMatchingMessage(destination);
            }else {
                // 구독자가 1명이면 기다리는 중 안내
                sendWaitingMessage(destination);
            }
        }
    }

    public void sendWaitingMessage(String destination) {
        // 구독 시간 대기 0.3초
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        SendMessageDTO message = new SendMessageDTO("waiting", "", "상대방을 기다리는 중입니다.");
        simpMessagingTemplate.convertAndSend(destination, message);
    }

    public void sendMatchingMessage(String destination) {
        // 구독 시간 대기 0.3초
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        SendMessageDTO message = new SendMessageDTO("matched", "", "매칭되었습니다.");
        simpMessagingTemplate.convertAndSend(destination, message);
    }
}

 채널을 구독하게 되면 이벤트 리스너를 통해 감지하게 되며 구독 경로와 경로의 접속인원 수를 ConcurrentHashMap 객체를 사용해서 체크한 뒤

1명만 접속 중이라면 "상대방을 기다리는 중입니다." 메세지를 전송, 2명이 접속되었다면 "매칭되었습니다." 메세지를 전송

 

 또한, 접속한 Socket 세션 아이디와 방번호를 ConcurrentHashMap 객체를 사용해서 저장한 뒤 소켓 연결이 끊어지면 해당 방 번호에 상대방이

떠났음을 안내하는 용도로 사용

 

 

 

package com.randomchat.main.dto.chat;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class SendMessageDTO {
    private String type;
    private String sender;
    private String message;
}

SendMessageDTO.class 의 구조

 

 

 

ChatMessageController.class 작성)

더보기
package com.randomchat.main.controller.chat;

import com.randomchat.main.dto.chat.ReceiveMessageDTO;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class ChatMessageController {

    @MessageMapping("/send/{roomId}")
    @SendTo("/room/{roomId}")
    public ReceiveMessageDTO sendMessage(ReceiveMessageDTO receiveMessageDTO) {
        return receiveMessageDTO;
    }

}

클라이언트가 메세지 전송 채널로 메세지를 전달하면 해당 내용을 서버에서 받아 다시 구독 채널로 뿌려준다.

 

 

 

package com.randomchat.main.dto.chat;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class ReceiveMessageDTO {
    private String sender;
    private String message;
}

RecieveMessageDTO.class 의 구조

( 클라이언트는 메세지를 전송할 때 자신의 닉네임을 sender 로, 채팅 내용을 message 로 JSON 형태로 매핑하여 전송한다. )

 

 

 

 

 

 

 

 

 

 

- React 코드 -

 

 

 

randomchat_info.js 작성)

더보기
import './../css/randomchat_info.css';
import { useContext, useEffect, useState } from 'react';
import { Client } from '@stomp/stompjs'; // STOMP 클라이언트
import { MyContext } from '../../App';
import { IoMdPerson } from "react-icons/io";
import { FiSearch } from "react-icons/fi";
import { GiSouthAfrica } from 'react-icons/gi';

const Randomchat_info = () => {
    const { api } = useContext(MyContext);
    const [stompClient, setStompClient] = useState(null); // STOMP 클라이언트 상태
    const [input, setInput] = useState(''); // 메시지 입력 상태
    const [messages, setMessages] = useState([]); // 메시지 목록
    const [nickname, setNickname] = useState(''); // 닉네임 상태
    const [roomId, setRoomId] = useState(null); // 구독할 채널 번호 상태
    const [isComposing, setIsComposing] = useState(false); // IME 조합 상태 관리

    // STOMP 클라이언트 초기화
    useEffect(() => {
        const client = new Client({
            brokerURL: '{서버 주소}', // STOMP 서버 WebSocket URL
            reconnectDelay: 5000, // 재연결 간격
            debug: (str) => console.log(str), // 디버그 로그
        });

        client.onConnect = () => console.log("STOMP connected");
        client.onStompError = (error) => console.error("STOMP error:", error);

        client.activate(); // 연결 시작
        setStompClient(client);

        return () => client.deactivate(); // 컴포넌트 언마운트 시 연결 종료
    }, []);

    // 닉네임 설정 후 채팅방 연결
    const handleConnect = () => {
        if (nickname.trim()) {
            fetch('{서버주소}/chat/enterChatRoom', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ nickname }), // 닉네임 전송
            })
                .then((response) => response.json())
                .then((data) => {
                    setRoomId(data.roomId); // 응답값에서 채널 번호 추출
                    subscribeToRoom(data.roomId); // 채널 구독
                })
                .catch((error) => console.error('Error:', error));
        }
    };

    // 채널 구독
    const subscribeToRoom = (roomId) => {
        if (stompClient) {
            stompClient.subscribe(`/room/${roomId}`, (message) => {
                const msg = JSON.parse(message.body); // 서버로부터 메시지 수신

                console.log("msg 내용입니다 >>>>> ", msg)

                setMessages((prevMessages) => [
                    ...prevMessages,
                    { type: msg.type, sender: msg.sender, message: msg.message }, // 메시지 목록 업데이트
                ]);
            });
            console.log(`Subscribed to room ${roomId}`);
        }
    };

    // 메시지 전송
    const sendMessage = () => {
        if (input.trim() && stompClient && roomId) {
            const message = { message: input };
            stompClient.publish({
                destination: `/send/${roomId}`, // 메시지 전송 경로
                body: JSON.stringify({"sender" : nickname, "message" : message.message}),
            });
            console.log("메세지 전송 완료!!! >>> " , message.message);
            setInput('');
        }
    };

    // 디버그용 메시지 로그
    useEffect(() => {
        console.log("Messages:", messages);
    }, [messages]);

    // 메세지별 css 클래스네임 정의
    const getMessageClassName = (msg) => {
        console.log("메세지 내용 >>>>> " , msg)
        if(msg.type !== undefined) return 'randomchat_info_text_alert';
        else if(msg.sender === nickname) return 'randomchat_info_text_me';
        else return 'randomchat_info_text_other';
    }

    return (
        <div className='randomchat_info_container'>
            {!roomId ? (
                <div className='randomchat_waiting_screen'>
                    <div className='randomchat_nickname_div'>
                        <input
                            className='randomchat_nickname_input_box'
                            type="text"
                            placeholder="닉네임을 입력하세요"
                            value={nickname}
                            onChange={(e) => setNickname(e.target.value)}
                        />
                        <button onClick={handleConnect}>연결</button>
                    </div>
                </div>
            ) : (
                <>
                    <div className='randomchat_info_title_content'>
                        <div className='randomchat_info_title_icon_content'>
                            <IoMdPerson className='randomchat_info_title_icon'></IoMdPerson>
                        </div>
                        <div className='randomchat_info_title_text'>채팅방</div>
                        <div className='randomchat_info_title_search'>
                            <FiSearch />
                        </div>
                    </div>
                    <div className='randomchat_info_content'>
                        {messages.length === 0 ? (
                            <div className='randomchat_info_none_content'>대화 내용이 없습니다.</div>
                        ) : (
                            <div className='randomchat_info_text_container'>
                                {messages.map((msg, index) => (
                                    <div
                                        key={index}
                                        className={getMessageClassName(msg)}
                                    >
                                        {msg.message}
                                    </div>
                                ))}
                            </div>
                        )}
                    </div>
                    <div className='randomchat_info_input_content'>
                    <input
                        value={input}
                        onChange={(e) => setInput(e.target.value)}
                        onKeyDown={(e) => {
                            if (e.key === 'Enter' && !isComposing) { 
                                // IME 조합 중이 아닐 때만 실행
                                e.preventDefault();
                                if (input.trim()) {
                                    sendMessage();
                                }
                            }
                        }}
                        onCompositionStart={() => setIsComposing(true)} // IME 입력 시작
                        onCompositionEnd={() => setIsComposing(false)}  // IME 입력 완료
                        className='randomchat_info_input'
                    />
                        <div onClick={sendMessage} className='randomchat_info_input_btn'>
                            Send
                        </div>
                    </div>
                </>
            )}
        </div>
    );
};

export default Randomchat_info;

 STOMP 라이브러리를 설치 한 이후 진행

useEffect 를 사용하여 메세지가 수, 발신 될때마다 메세지를 리렌더링

메세지 내용을 useState 를 사용하여 관리

 

 

 

.randomchat_info_container{
    width: 1280px;
    margin: auto;
}

/* 제목 */
.randomchat_info_title_content{
    height: 120px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid rgba(0,0,0,0.5);
}
.randomchat_info_title_icon_content{
    width: 90px;
    height: 90px;
    background-color: rgba(128,128,128,0.2);
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 50%;
}
.randomchat_info_title_icon{
    width: 80px;
    height: 80px;
    color: rgba(128,128,128,0.7);
    border-radius: 50%;
}
.randomchat_info_title_text{
    font-size: 20px;
    font-weight: bold;
    margin-left: 10px;

}
.randomchat_info_title_search{
    margin-left: 1060px;
    font-size: 50px;
    color: #3578FF;
    display: flex;
    align-items: center;
}

/* 채팅 정보 */
.randomchat_info_content{
    height: 600px;
    margin-top: 10px;
    overflow-y: auto;
}
/* 스크롤바를 완전히 숨기기 */
.randomchat_info_content::-webkit-scrollbar {
    display: none;  /* 스크롤바를 숨깁니다 */
} 
/* 없음 */
.randomchat_info_none_content{
    height: 600px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 25px;
    color: rgba(0,0,0,0.5);
}

.randomchat_info_text_container{
    /* display: flex; */
}

/* 아이콘 */
.randomchat_info_other_icon_container{
    display: flex;
    flex-direction: column;
}
.randomchat_info_other_icon_content{
    width: 40px;
    height: 40px;
    background-color: rgba(128,128,128,0.2);
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: auto;
}
.randomchat_info_other_icon{
    width: 30px;
    height: 30px;
    color: rgba(128,128,128,0.7);
    border-radius: 50%;
}
/* 텍스트 */
.randomchat_info_text_content{
    width: 1280px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}
.randomchat_info_text_me,
.randomchat_info_text_other,
.randomchat_info_text_alert {
    max-width: 600px;
    width: auto;
    min-height: 40px;
    color: white;
    font-size: 20px;
    background-color: #3578FF;
    display: flex;
    align-items: center;
    justify-content: center; /* 텍스트를 중앙 정렬 */
    border-radius: 8px;
    margin-top: 5px;
    padding-left: 10px;
    padding-right: 10px;
    word-wrap: break-word;
    white-space: normal;
    width: 100%; /* 한 줄을 차지하도록 설정 */
    box-sizing: border-box; /* padding 포함한 전체 크기 계산 */
}

.randomchat_info_text_other {
    background-color: rgba(128,128,128,0.5);
    margin-left: 0; /* 왼쪽으로 정렬 */
    margin-right: auto; /* 오른쪽 여유 공간 */
    text-align: left; /* 텍스트 왼쪽 정렬 */
    justify-content: left;
}

.randomchat_info_text_me {
    background-color: #3578FF;
    margin-left: auto; /* 왼쪽 여유 공간 */
    margin-right: 0; /* 오른쪽으로 정렬 */
    text-align: right; /* 텍스트 오른쪽 정렬 */
    justify-content: right;
}

.randomchat_info_text_alert {
    background-color: #fc0000;
    margin-left: auto; /* 왼쪽 여유 공간 */
    margin-right: auto; /* 오른쪽 여유 공간 */
    text-align: center; /* 텍스트 중앙 정렬 */
}

.randomchat_nickname_div {
    height: 600px;
    overflow-y: auto;
    display: flex; /* 플렉스 박스 활성화 */
    justify-content: center; /* 가로 정렬 */
    align-items: center; /* 세로 정렬 */
}

.randomchat_nickname_input_box {
    /* 닉네임 인풋박스 css*/
    border: 1px solid rgb(159, 153, 153);
    border-radius: 8px;
    text-align: center;
    height: 30px;
    width: 210px;
}

.randomchat_nickname_div > button {
    /* 연결 버튼 css */
    margin-left: 10px;
    background-color: #3578FF; /* 메인 색 배경 */
    color: white; /* 글자색 */
    font-size: 15px; /* 글자 크기 */
    height: 20px;
    width: 40px;
    border: none; /* 테두리 제거 */
    border-radius: 5px; /* 모서리 둥글게 */
    cursor: pointer; /* 마우스 포인터 변경 */
    box-shadow: 0 4px #184192; /* 그림자 */
    transition: background-color 0.3s ease; /* 배경색 변화 효과 */
}

.randomchat_nickname_div > button:hover {
    background-color: #143371;
}

.randomchat_nickname_div > button:active {
    box-shadow: 0 2px #184192; /* 그림자 줄이기 */
  transform: translateY(2px); /* 버튼 눌리는 효과 */
}



/* 나 */
.randomchat_info_me_content{

}
.randomchat_info_me_text_content{
    background-color: #3578FF;
    color: white;
}


/* 입력 */
.randomchat_info_input_content{
    margin-top: 10px;
    margin-bottom: 30px;
    display: flex;
}
.randomchat_info_input{
    width: 1190px;
    height: 50px;
    padding: 0px 0px 0px 10px;
    outline: none;
    font-size: 22px;
}
.randomchat_info_input_btn{
    width: 80px;
    height: 53px;
    background-color: #3578FF;
    color: white;
    font-size: 20px;
    font-weight: 500;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
}

randomchat_info.css 작성

 

 

 

Nginx 추가 설정)

더보기
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;

        server_name www.random-chat.site;
        location / {
            proxy_pass http://randomchat-react:80;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen 80;

        server_name api.random-chat.site;
        location / {
            proxy_pass http://randomchat-spring:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_cache_bypass $http_upgrade;
        }
    }
}

 현재 Nginx 의 리버스 프록시 기능을 사용하는데 웹 소켓 통신의 경우 헤더에 소켓 정보를 담아 보내기 때문에

필요한 헤더 정보를 추가하는 3줄의 코드를 추가해줬다.

 

proxy_set_header Uprade $http_upgrade;

proxy_set_header Connection 'upgrade';

 

proxy_cache_bypass $http_upgrade;

 

 

 

 

 

랜덤 채팅 작동 영상

랜덤채팅 사이트에 접속해서 확인가능합니다.

 

 

 

 

 

 

▼ 완성된 랜덤채팅 사이트 방문하기 ▼

 

React App

 

www.random-chat.site

 

728x90

+ Recent posts