본문 바로가기
개발 이야기

SSE 연결 문제 해결과 Spring 컨트리뷰터 도전기

by 신재권 2024. 8. 3.

문제 상황

실시간 주문 알림 기능을 구현하면서 SSE(Server-Sent Events) 기술을 사용했는데, SSE 연결이 지속되지 않고 첫 연결 후 바로 끊어지는 문제가 발생했었다.

원인 분석

SSE는 연결이 지속되어야 하는데, Content-Length 헤더가 설정되면 연결이 유지되지 않는 문제가 있었다.
원인을 요약하면 다음과 같다.

  1. ContentCachingResponseWrapper#copyBodyToResponse 호출 시 Content-Length가 설정되고 flush가 실행됨
  2. Http11Processor#prepareResponse() 실행
  3. 길이가 존재하므로 chunked 헤더가 할당되지 않음
  4. 클라이언트는 chunked 헤더가 없어서 이벤트로 인식하지 않고 연결을 종료

RFC7230에 따르면 발신자는 Transfer-Encoding 헤더 필드가 포함되어 있다면 Content-Legnth 헤더를 동시에 사용하면 안 된다고 써있다. 

  1. Transfer-Encoding 헤더가 있는 경우, Content-Length 헤더는 무시되어야 합니다.
  2. 두 헤더가 동시에 존재하는 경우, 이는 잘못된 요청 또는 응답으로 간주될 수 있습니다.

문제의 근본 원인

로깅을 위해 사용한 ContentCachingResponseWrapper가 문제의 원인이였다.

해당 래퍼는 로그를 남기기 위해(재사용 하기 위해) response를 캐싱하는데, copyBodyToResponse 메서드에서 Content-Length를 설정하면서 문제가 발생하였다.

protected void copyBodyToResponse(boolean complete) throws IOException {
    // ... (코드 생략)
    if (rawResponse.getHeader(HttpHeaders.TRANSFER_ENCODING) == null) {
        rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
    }
    // ... (코드 생략)
}

Spring PR 도전

이 문제를 해결하기 위해 Spring Framework에 첫 PR을 제출하였다. Content-Type이 text/event-stream인 경우 Content-Length를 설정하지 않도록 수정하는 PR이였다.

 

PR 링크: https://github.com/spring-projects/spring-framework/pull/33285

 

Added TextEventStreamType check before response caching by jkshin0602 · Pull Request #33285 · spring-projects/spring-framework

When using SSE (Server-Sent Events) technology in Spring, the response typically includes a Chunked header and the text/event-stream content type. An issue arises when response caching occurs befor...

github.com

 

하지만 안타깝게도 PR은 거절되었다.

메인테이너는 래핑 수행 전에 스트리밍 타입인지 먼저 검사하라고 조언해줬다.

해결 방법

결국 스트리밍 요청 시 로깅 필터를 거치지 않도록 수정하여 문제를 해결했다.

이를 통해 RFC7230에 명시된 것처럼, Transfer-Encoding 헤더와 Content-Length 헤더를 동시에 사용하지 않아야 한다는 원칙을 따르게 되었다.

후기

비록 Spring 컨트리뷰터의 꿈은 이루지 못했지만, 이 경험을 통해 많은 것을 배웠다.

  1. 디버깅 과정에서 Spring의 내부 동작을 깊이 이해
  2. 스트리밍 타입에서 Content-Length 설정의 문제점을 인식
  3. Spring 공식 문서를 더 열심히 읽게 되는 계기

이번 경험을 통해 Spring에 대한 이해도를 높이고, 스프링 컨트리뷰터가 되고 싶다는 욕심이 생겼다.

꼭 꿈을 이루고 싶다.