백엔드 3-1 과제

나래·2024년 7월 23일

실습 : 로그인 회원가입 api 설계, (Spring security 적용 및 JWT Token 발행) 가능하면 해보세요.

이론 : Websocket이란? 개념과 동작방식, Spring boot에서 사용 방법

1. Websocket

1) 웹 소켓이란?

  • WebSocket은 ws 프로토콜을 기반으로 클라이언트와 서버 사이에 지속적인 완전 양방향 연결 스트림을 만들어주는 기술이다.

= 서버와 클라이언트 간의 메시지 교환을 위한 통신규약(프로토콜)

2) 웹 소켓의 특징

  • 양방향 통신(Full-Duplex)
    = 데이터 송수신을 동시에 처리할 수 있는 방법으로, 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있다.

    통상적인 HTTP 통신은 Client가 요청을 보내는 경우에만 Server가 응답을 하는 단방향 통신이다.
  • 실시간 네트워킹(Real Time-Networking)
    = 웹 환경에서 연속된 데이터를 빠르게 노출이 가능하다.
    ex) 채팅, 주식, 비디오 데이터

3) 웹 소켓 동작 방법

= 웹 소켓도 핸드 쉐이킹이 필요하고, Socket 프로토콜이 아닌 HTTP 또는 HTTPS 프로토콜을 통해 이루어진다.

웹 소켓 요청

  • HTTP 버전은 1.1 이상
  • 반드시 GET 메서드를 사용해야 한다.
  • Upgrade 정보는 서버, 전송, 프로토콜 연결에서 다른 프로토콜로 업그레이드 또는 변경하기 위한 규칙이다.
  • Sec-Websocket-Key는 클라이언트가 요청하는 여러 서브 프로토콜을 의미한다

웹 소켓 응답

  • 101 Switching Protocols가 Response로 오면 웹 소켓이 연결된 것이다.
  • Sec-Websocket-Accept는 요청에서 Key값을 계산한 값으로 신원 인증에 필요한 헤더이다.

이렇게 위와 같이 핸드 쉐이크가 완료되면 프로토콜이 ws로 변경된다.
또는 wss와 같이 데이터보안을 위해 SSL을 적용한 프로토콜로 변경된다.

전체적인 흐름도는 이렇게 된다.

4) 웹 소켓 프로토콜 특징

  • 최초 접속시에만 http 프로토콜 위에서 handshaking을 하기 때문에 http header를 사용한다.
  • 웹소켓을 위한 별도의 포트는 없고, 기존 포트를 사용한다.
  • 프레임으로 구성된 메시지라는 논리적 단위로 송수신한다.
  • 메시지에 포함될 수 있는 교환 가능한 메시지는 텍스트와 바이너리 뿐이다.

출처 ) https://velog.io/@sj_yun/Web-Socket%EC%9D%B4%EB%9E%80


2. 스프링부트에서 사용법

1) 의존성 주입

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

2) 전반적인 설정 제공

= 어떤 요청에 대한 어떤 응답을 할 것인지에 대한 정의를 결정한다.

@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {

    private final WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/아무거나해요").setAllowedOrigins("*");
    }
}

➡️ /아무거나해요 이부분은 나중에 서버 세팅을 끝난 후 실제로 테스트해볼 때 정해지는 url이다.
ex) ws://127.0.0.1:PORT/아무거나해요

그리고 setAllowedOrigins("*")는 웹소켓 cors 정책으로 인해 허용 도메인으로 인해 허용 도메인을 지정해줘야 한다.

'*'는 와일드 카드로 모든 도메인을 열어준다. 실제 개발에는 사용하지 않지만(보안문제) 현재는 테스트이기 때문에 사용한다.

3) WebSocketHandler

@Component
public class WebSocketHandler extends TextWebSocketHandler {

    private static final ConcurrentHashMap<String, WebSocketSession> CLIENTS = new ConcurrentHashMap<String, WebSocketSession>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        CLIENTS.put(session.getId(), session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        CLIENTS.remove(session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String id = session.getId();  //메시지를 보낸 아이디
        CLIENTS.entrySet().forEach( arg->{
            if(!arg.getKey().equals(id)) {  //같은 아이디가 아니면 메시지를 전달합니다.
                try {
                    arg.getValue().sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

TextWebSocketHandler를 상속받게 되면, 3개의 메소드를 오버라이딩 해야한다.

  • ConcurrentHashMap()
private static final ConcurrentHashMap<String, WebSocketSession> CLIENTS = new ConcurrentHashMap<String, WebSocketSession>();

해당 코드는 CLIENTS라는 변수에 세션을 담아두기위한 맵 형식의 공간이다.

  • afterConnectionEstablished()
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        CLIENTS.put(session.getId(), session);
    }

사용자가 웹소켓 서버에 접속하게 되면 동작하는 메소드이다.
이때 WebSocketSession 값이 생성되는데 그 값을 위에서 미리 만들어준, CLIENTS 변수에 put으로 담아준다. (키 값은 세션의 고유값이다.)

  • afterConnectionClosed()
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
     CLIENTS.remove(session.getId());
   }

해당 코드는 웹소켓 서버접속이 끝났을 때 동작하는 메소드이다.
이때 CLIENTS 변수에 있는 해당 세션을 제거한다.

  • handleTextMessage()
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String id = session.getId();  //메시지를 보낸 아이디
        CLIENTS.entrySet().forEach( arg->{
            if(!arg.getKey().equals(id)) {  //같은 아이디가 아니면 메시지를 전달합니다.
                try {
                    arg.getValue().sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

해당 코드는 사용자의 메시지를 받게되면 동작한는 메소드이다.
CLIENTS 변수에 담긴 세션값들을 가져와서 반목문으로 돌려서, 위처럼 메세지를 발송해주면, 본인 이외의 사용자에게 메시지를 보낼 수 있는 코드가 된다.

출처 ) https://yjkim-dev.tistory.com/65

0개의 댓글