개발일지

웹 소켓 핸들러 예외 처리

khw7385 2025. 6. 20. 14:49

목표

웹 소켓 사용 시 예외 처리하는 방법에 대해 학습한다.

학습 내용

예외 처리의 편리성을 위해서 언체크 루트 예외 객체인 ApplicationException 을 정의하여 해당 객체로 catch 부분을 구성한다.

웹 소켓 핸들러 내부

먼저 떠오르는 방법은 웹 소켓 핸들러 구현체에서 try~catch 문을 선언하는 방법이다.

 

public void afterConnectionEstablished(WebSocketSession session){
	try{
		Long themeId = Long.parseLong((String)session.getAttributes().get("theme"));
	
		log.info("Client WebSocket 연결 성공: session id = {}", session.getId());
		eventPublisher.publishEvent(new ClientWebSocketConnectedEvent(session.getId(), messageChannelFactory.create(session), themeId));
	}catch(ApplicationException e){
		...
	}
}

편리한 방법이긴 하지만 웹 소켓 핸들러가 예외 처리에 대한 책임까지 지닌다면 객체의 책임은 무너지고 가독성도 떨어진다.

웹 소켓 핸들러 데코레이터(Decorator) 활용

스프링에서는 웹 소켓 핸들러를 데코레이트할 수 있는 클래스를 제공한다.

여기서, 데코레이터(Decorator) 는 디자인 패턴 중 하나로 대상 객체에 대한 기능 확장이나 변경이 필요할 때 객체의 결합을 통해 서브클래싱 대신 사용할 수 있는 패턴이다.

스프링에서는 WebSocketHandler를 캡슐화한 WebSocketHandlerDecorator 를 제공한다. package org.springframework.web.socket.handler;

실제 구현은 기능 확장이 필요한 메서드를 오버라이딩하여 정의하고 웹 소켓 핸들러 설정 파일에서 기존 웹 소켓 핸들러가 아닌 데코레이터를 감싼(wrap) 핸들러를 등록한다.

// 데코레이터
public class ExceptionHandlingWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
    public ExceptionHandlingWebSocketHandlerDecorator(ClientWebSocketHandler delegate) {
        super(delegate);
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        try {
            super.afterConnectionEstablished(session);
        }catch(ApplicationException e){
            log.error(e.getMessage());
            session.close();
        }
    }    
}

// 웹 소켓 설정
public class WebSocketServerConfig implements WebSocketConfigurer {
    private final ClientWebSocketHandler clientWebSocketHandler;
    private final WebSocketInterceptor webSocketInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        WebSocketHandler handler = new ExceptionHandlingWebSocketHandlerDecorator(clientWebSocketHandler);

        registry.addHandler(handler, "/conversation/streaming")
                .addInterceptors(webSocketInterceptor);
    }
}

Spring AOP 활용

데코레이터를 이용하여 처리하는 방법이 스프링 문서에서도 추천하는 방법이지만 Spring AOP 를 활용하여 공통 관심사인 예외 처리를 구현하는 방법도 있다. Spring AOP 에 대해서 자세한 설명은 하지 않겠다.

어노테이션을 활용하여 부가 기능(예외 처리)가 필요한 클래스 혹은 메서드에 어노테이션을 추가한다.

// Aspect
@Aspect
@Component
@RequiredArgsConstructor
public class WebSocketErrorHandlingAspect {
    @Around("@annotation(me.khw7385.conversation.core.annotation.WebSocketErrorHandling) && args(session, ..)")
    public Object handleOnError(ProceedingJoinPoint point, WebSocketSession session) throws Throwable{
        try{
            return point.proceed();
        }catch (ApplicationException e){
            log.error(e.getMessage());
						session.close();
        }
        return null;
    }
}

// 웹 소켓 핸들러

@WebSocketErrorHandling
public void afterConnectionEstablished(WebSocketSession session){
		Long themeId = Long.parseLong((String)session.getAttributes().get("theme"));

    log.info("Client WebSocket 연결 성공: session id = {}", session.getId());
    eventPublisher.publishEvent(new ClientWebSocketConnectedEvent(session.getId(), messageChannelFactory.create(session), themeId));
}

참고 자료

https://inpa.tistory.com/entry/GOF-💠-데코레이터Decorator-패턴-제대로-배워보자

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/handler/WebSocketHandlerDecorator.html