- websocket2024년 11월 18일
- chantleman
- 작성자
- 2024.11.18.:06
아래 axios 파트 읽어보깅!!
2024.10.29 - [js] - 비동기 ajax, fetch, axios, async/await, promise
HTTP는 stateless로 클라이언트에서 요청(request)하면 서버에서 응답(response)하는 구조로
클라이언트에서 요청을 해야지만 서버에서 응답을 할 수 있는 단방향구조였지만,
websocket는 stateful 프로토콜로 지속적으로 connection을 유지하기때문에
클라이언트의 요청으로 최초 접속 후 한 번 연결하면 서버에서도 언제든지 요청하며 통신할 수 있는 양방향 통신이 가능해진당
↓ SuminSocketConfig.java ↓
public class SuminSocketConfig implements WebSocketConfigurer { @Autowired private SampleSocketIntercepter sampleSocketIntercepter; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { log.debug("체킁"); registry .addHandler(webSocketHandler(), "/chat") //endpoint .setAllowedOriginPatterns("*") //origin. *은 다 풀어준 것 .addInterceptors(sampleSocketIntercepter); } @Bean protected WebSocketHandler webSocketHandler() { log.debug("체로롱"); return new SampleWebSocketHandler(); } }
/chat ← 이런걸 endpoint 라고 함
/chat 서버가 중개소 역할을 함
A와 B, C가 통신할 때 chat이라는 중개소를 통해 통신함 (broadcast)
A와 B가 직접 통신하는 건 p2p
↓ SampleSocketIntercepter.java ↓
@Component public class SampleSocketIntercepter implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { HttpServletRequest req = ((ServletServerHttpRequest)request).getServletRequest(); HttpSession session = req.getSession(false); //세션이 없으면 만들어줌 // HttpSession에 뭐가 들어있다면 담자 if(session != null) { Enumeration<String> attNames = session.getAttributeNames(); while(attNames.hasMoreElements()) { String attName = attNames.nextElement(); Object attValue = session.getAttribute(attName); attributes.put(attName,attValue); } } // 그냥 계속 진행 return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { // 지금은 할 일이 생각 안남 } }
로그인 세션 정보가 httpsession에 담기는데
그걸 handshake interceptor로 중간에 가로채서 세션정보를 webSocketSession에 담는당
↓ SampleWebSocketHandler.java ↓
@Slf4j public class SampleWebSocketHandler extends TextWebSocketHandler { private static List<WebSocketSession> list = new ArrayList<WebSocketSession>(); @Override //처음 접속됐을떄 자동 실행 public void afterConnectionEstablished(WebSocketSession session) throws Exception { log.info("## 누군가 접속"); list.add(session); } @Override //여기에 모든 처리 로직이 들어감 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String uMsg = message.getPayload(); log.debug("클라이언트가 보낸 메시지 {}"+ uMsg); //접속한 사람들 모두에게 전달, (broadcast) for (WebSocketSession webSocketSession : list) { webSocketSession.sendMessage(message); } } @Override //접속 해제 됐을때 자동 실행 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { log.info("## 누군가 떠남"); list.remove(session); } }
↓ index.html ↓
<script> //클라이언트 사이드 웹소켓 //ws: 웹소켓 프로토콜. http와 https처럼 ws와 wss가 있음 let socket = new WebSocket("ws://localhost:8080/chat"); //자동으로 접속시도 //접속에 성공하면 자동으로 onopen 이벤트 발생 socket.onopen = ()=>{ console.log("웹 소켓 연결 되었습니다."); socket.send("연결 완료"); //send()로 메소드로 중개소 에 메시지 보낼 수 있음 } //서버에서 메시지가 도착하면 자동으로 onmessage 이벤트 발생 socket.onmessage = ()=>{ console.log("서버로부터 왔어용", event.data); let rDiv = document.createElement("div"); rDiv.style.border="1px solid orange"; rDiv.innerHTML=event.data; jcChat.appendChild(rDiv); } socket.onclose=() =>{ console.log("웹소켓이 닫히면 자동으로 실행됨"); alert("접속이 끊겼습니당"); } socket.onerror=()=>{ console.log("에러 발생"); } </script>
↓ SampleWebSocketHandler.java ↓
@Slf4j public class SampleWebSocketHandler extends TextWebSocketHandler { private static List<WebSocketSession> list = new ArrayList<WebSocketSession>(); @Override //처음 접속됐을떄 자동 실행 public void afterConnectionEstablished(WebSocketSession session) throws Exception { log.info("## 누군가 접속"); list.add(session); } @Override //여기에 모든 처리 로직이 들어감 //session : 누가 보냈는지, message : 메시지 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String uMsg = message.getPayload(); log.debug("클라이언트가 보낸 메시지 {}", uMsg); //jackson 라이브러리 직접 사용하여 변환! ObjectMapper objMapper = new ObjectMapper(); //변환기 생성 //readValue: json문자열을 자바 객체로 변환 MsgVO msgvo = objMapper.readValue(uMsg, MsgVO.class); log.info("변환 성공? {}", msgvo); msgvo.setMsg("메시지: " + msgvo.getMsg()); //writeValueAsString: 자바 객체를 json 문자열로 바꿔줌 String jsonMsg = objMapper.writeValueAsString(msgvo); TextMessage txtMsg = new TextMessage(jsonMsg); //접속한 사람들 모두에게 전달, (broadcast) for (WebSocketSession webSocketSession : list) { if(session != webSocketSession) { // 보낸 사람이 아닐 때만! if(webSocketSession.isOpen()) { webSocketSession.sendMessage(txtMsg); } } } } @Override //접속 해제 됐을때 자동 실행 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { log.info("## 누군가 떠남"); list.remove(session); } }
↓ MsgVO ↓
@Getter @Setter @ToString public class MsgVO { private String sender; private String receiver; private String kind; private String msg; }
↓ index.html ↓
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>웹 소켓 클라이언트</title> <style> #jcChat{ width:400px; height:400px; overflow:scroll; border: 5px groove gold; } </style> </head> <body> <h1>SOCKET</h1> <div id="jcChat"></div> <input type="text" name="" id="jcTxt" value="" onkeydown="enter()"> <button onclick="send()" >전송</button> <br> id: <input type="text" id="jcId" value=""> <button onclick="conn()">소켓연결</button> <script> const jcChat = document.querySelector("#jcChat"); const jcTxt= document.querySelector("#jcTxt"); function enter(){ if(window.event.keyCode==13){ send(); } } function send(){ if(!socket){ alert("연결 먼저 눌러 바보얌"); return; } let sDiv = document.createElement("div"); sDiv.innerHTML=jcTxt.value; sDiv.style.textAlign="right"; jcChat.appendChild(sDiv); //데이터 구조 설계가 중요! let MsgVO = { sender : "수민6", receiver: "all", //받는 사람을 all로 하면 broadcast kind: "chat", msg: jcTxt.value }; socket.send(JSON.stringify(MsgVO)); jcTxt.value="" ; //다시 입력 가능하도록 jcTxt.focus(); } let socket = null; function conn(){ //클라이언트 사이드 웹소켓 //localhost 혹은 ip주소 let hostName = location.href.split("/")[2]; //ws: 웹소켓 프로토콜. http와 https처럼 ws와 wss가 있음 socket = new WebSocket(`ws://${hostName}/chat`); //자동으로 접속시도 //접속에 성공하면 자동으로 onopen 이벤트 발생 socket.onopen = ()=>{ console.log("웹 소켓 연결 되었습니다."); //socket.send("연결 완료"); //send()로 메소드로 중개소에 메시지 보낼 수 있음 } //서버에서 메시지가 도착하면 자동으로 onmessage 이벤트 발생 socket.onmessage = ()=>{ console.log("서버로부터 왔어용", JSON.parse(event.data)); let rJson = JSON.parse(event.data); if(rJson.sender == "수민6"){ alert(`${rJson.sender}님이 잠에서 깨셨습니당`); } let rDiv = document.createElement("div"); rDiv.style.textAlign="left"; rDiv.innerHTML = rJson.msg; jcChat.appendChild(rDiv); jcChat.scrollTo(0,jcChat.scrollHeight); //스크롤바를 항상 바닥에 } socket.onclose=() =>{ console.log("웹소켓이 닫히면 자동으로 실행됨"); alert("접속이 끊겼습니당"); } socket.onerror=()=>{ console.log("에러 발생"); } } </script> </body> </html>
filter와 interceptor 차이
filter : Tomcat에서 dispatcher servlet에 접근할 때 이전에 가로채서 처리
interceptor : 스프링에서 Controller에 접근하기 이전에 가로채서 처리
filter는 톰캣과 스프링 사이에서 처리하는 반면, interceptor는 스프링 내부에서 처리
dispatcher: 서블릿 컨테이너의 가장 앞단에서 http 프로토콜로 들어오는 모든 요청을 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러
웹소켓에서 연결하기 이전에 http 통신을 통해 핸드쉐이크 과정이 일어나는데
이때 핸드쉐이크 인터셉터를 통해 세션정보를 가져올 수 있음
728x90'spring' 카테고리의 다른 글
react, spring boot 연동 (0) 2024.11.26 파일업로드 (0) 2024.11.14 @RequestParam, @RequestBody, @ResponseBody (6) 2024.11.12 spring 설치 및 초기 설정 (3) 2024.11.08 h2 DB (0) 2024.10.24 다음글이전글이전 글이 없습니다.댓글