소켓 프록시 수동 설정하기
http-proxy-middleware 설치하기
$ npm install http-proxy-middleware --save
$ # or
$ yarn add http-proxy-middleware
// ws 프로토콜을 사용해야하니 설정해주는 것!
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = (app) => {
app.use(
"ws",
createProxyMiddleware({
target: "http://localhost:8080",
ws: true, // 웹소켓을 사용하겠다!
})
);
};
STOMP
(SImple Text Orientated Messaging Protocol)
브로커(중개 서버)를 통해서 클라이언트간에 비동기적으로 메시지를 전송하기 위한 프로토콜
Websocket 위에서 동작하며 클라이언트와 서버가 전송할 메시지의 유형, 형식 내용들을 정의한다.
STOMP는 메시지의 헤더를 작성할 수 있어 통신 시에 인증 처리를 구현하는 것이 가능하다!
=> 우리 프로젝트에서 STOMP를 사용하는 가장 큰 이유이기도 함,,!
stomp 설치
$ npm install @stomp/stompjs --save
import
import * as StompJs from "@stomp/stompjs";
stomp의 흐름
1. 서버와 연결할 클라이언트를 Connect
2. Subscriber(경로 생성) 지정
3. Publisher(메시지 전송) 지정
1. 서버와 연결할 클라이언트 Connect
const clientdata = new StompJs.Client({
brokerURL: "ws://localhost:8080/chat",
connectHeaders: {
login: "",
passcode: "password",
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000, // 자동 재 연결
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
- 서버의 endpoint '/chat' 을 brokerURL 로 설정해준다.
- STOMP의 가장 큰 장점?? 인 connectHeaders를 통해서 서버에 데이터를 전송할 수 있다. 현재는 의미 없는 정보가 보내지고 있는 상태. 로컬스토리지에 있는 토큰을 받아 헤더에 실어서 보내면 사용자 인증 구현도 가능하다고 한다.
- reconnectDelay : 5초마다 자동 재연결을 시도한다.
2. Subscribe (구독하기)
// 구독
clientdata.onConnect = function () {
clientdata.subscribe("/sub/channels/" + chatroomId, callback);
};
const callback = function (message) {
if (message.body) {
let msg = JSON.parse(message.body);
setChatList((chats) => [...chats, msg]);
}
};
- 채팅방 번호가 담긴 주소로 구독요청. 구독과 동시에 실행할 콜백함수를 인자로 넘긴다.
- 우리는 채팅 배열에 새로 받은 메시지를 추가하는 동작을 하게 했다.
3. Publish (전송하기)
const sendChat = () => {
if (chat === "") {
return;
}
client.publish({
destination: "/pub/chat/" + chatroomId,
body: JSON.stringify({
type: "",
sender: userId,
channelId: "1",
data: chat,
}),
});
setChat("");
};
- destination과 body를 publish를 사용해 서버단에 보내준다.
이렇게만 쓰니까,, 굉장히 간편해 보이지만 삽질 좀 많이 했다..
내가 기억하려고 올리는,, 전체코드,,
import React, { useCallback, useRef, useState, useEffect } from "react";
import styles from "./styles/ChatRoom.module.css";
import { useSelector } from "react-redux";
import testImg from "../assets/images/testImg.jpg";
import { useNavigate, useParams } from "react-router-dom";
import * as StompJs from "@stomp/stompjs";
// heroicons
import {
CameraIcon,
ChevronLeftIcon,
MegaphoneIcon,
} from "@heroicons/react/24/outline";
import { ArrowUpCircleIcon } from "@heroicons/react/24/solid";
export default function ChatRoom() {
let navigate = useNavigate();
const param = useParams(); // 채널을 구분하는 식별자
const chatroomId = param.chatroomId;
const token = JSON.stringify(window.localStorage.getItem("token")); // 현재 로그인 된 사용자의 토큰
let [client, changeClient] = useState(null);
const [chat, setChat] = useState(""); // 입력된 chat을 받을 변수
const [chatList, setChatList] = useState([]); // 채팅 기록
// userSlice.js에 저장된 로그인된 유저의 코드를 받음
const userId = useSelector((state) => {
return state.user.userCode;
});
//컴포넌트가 변경될 때 객체가 유지되어야하므로 'ref'로 저장
// 내가 보낸 메시지, 받은 메시지에 각각의 스타일을 지정해 주기 위함
const msgBox = chatList.map((item, idx) => {
if (Number(item.sender)!== userId) {
return (
<div key={idx} className={styles.otherchat}>
<div className={styles.otherimg}>
<img src={testImg} alt="" />
</div>
<div className={styles.othermsg}>
<span>{item.data}</span>
</div>
<span className={styles.otherdate}>{item.date}</span>
</div>
);
} else {
return (
<div key={idx} className={styles.mychat}>
<div className={styles.mymsg}>
<span>{item.data}</span>
</div>
<span className={styles.mydate}>{item.date}</span>
</div>
);
}
});
const connect = () => {
// 소켓 연결
try {
const clientdata = new StompJs.Client({
brokerURL: "ws://localhost:8080/chat",
connectHeaders: {
login: "",
passcode: "password",
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000, // 자동 재 연결
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
// 구독
clientdata.onConnect = function () {
clientdata.subscribe("/sub/channels/" + chatroomId, callback);
};
clientdata.activate(); // 클라이언트 활성화
changeClient(clientdata); // 클라이언트 갱신
} catch (err) {
console.log(err);
}
};
const disConnect = () => {
// 연결 끊기
if (client === null) {
return;
}
client.deactivate();
};
// 콜백함수 => ChatList 저장하기
const callback = function (message) {
if (message.body) {
let msg = JSON.parse(message.body);
setChatList((chats) => [...chats, msg]);
}
};
const sendChat = () => {
if (chat === "") {
return;
}
client.publish({
destination: "/pub/chat/" + chatroomId,
body: JSON.stringify({
type: "",
sender: userId,
channelId: "1",
data: chat,
}),
});
setChat("");
};
useEffect(() => {
// 최초 렌더링 시 , 웹소켓에 연결
// 우리는 사용자가 방에 입장하자마자 연결 시켜주어야 하기 때문에,,
connect();
return () => disConnect();
}, []);
const onChangeChat = (e) => {
setChat(e.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
};
return (
<>
{/* {JSON.stringify(user)} */}
{/* <GlobalStyle/> */}
<div className={styles.container}>
{/* 상단 네비게이션 */}
<div className={styles.topbar}>
<ChevronLeftIcon
onClick={() => {
navigate("/chatlist ");
}}
/>
<span>상대방 이름</span>
<MegaphoneIcon onClick={() => navigate(`/report/1`)} />
</div>
{/* 채팅 리스트 */}
<div className={styles.chatbox}>{msgBox}</div>
{/* 하단 입력폼 */}
<form className={styles.sendzone} onSubmit={handleSubmit}>
{/* <input type="file" accept='image/*'/> */}
<CameraIcon className={styles.cameraicon} />
<div className={styles.inputbar}>
<div>
<input
type="text"
id="msg"
value={chat}
placeholder="메시지 보내기"
className={styles.input}
onChange={onChangeChat}
onKeyDown={(ev) => {
if (ev.keyCode === 13) {
sendChat();
}
}}
/>
</div>
<ArrowUpCircleIcon
value="전송"
className={styles.sendbtn}
onClick={sendChat}
/>
</div>
</form>
</div>
</>
);
}
실행 결과 괜히 뿌듯해서 올려보기,,
꽤 뿌듯하자나,, 히히 😊
'Project > zum:go' 카테고리의 다른 글
[React] axios로 다중 파일 전송 - 이미지, JSON (0) | 2023.02.07 |
---|---|
[React] 타이머 기능 구현하기 - setInterval (0) | 2023.02.05 |
[React] swiper로 이미지 슬라이드 구현하기 (0) | 2023.01.28 |
[React] 카카오 로그인 (0) | 2023.01.26 |
[React] React에서 Firebase 사용하기 - Google 로그인 (0) | 2023.01.25 |
댓글