ERR_INCOMPLETE_CHUNKED_ENCODING SSE 통신 끊어짐 에러 해결 (Nginx & Springboot 설정)

Damongsanga·2024년 3월 30일
0
post-thumbnail

프로젝트에서 Back과 Front에서 SSE 통신을 하던 중 발생한 에러의 트러블 슈팅 과정을 기록해보았다.

ERR_INCOMPLETE_CHUNKED_ENCODING

넌 또 뭐니..!


문제 상황

  • 로컬 → 로컬에서 SSE 통신이 잘 이루어졌으나, 로컬 → 서버로 요청시 SSE로 데이터가 전송이 안되는 문제가 발생하였다.
    SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);

  • SSE 커넥션 자체는 열렸으나. 데이터 전송이 되지 않고 약 1분이 지나자 연결이 끊어졌다.

  • Chunked Transfer Encoding 이란?

    • HTTP 프로토콜에서 사용되는 전송 인코딩 방식 중 하나로, HTTP 응답 메시지의 본문(body)을 여러 개의 조각(chunk)으로 나누어 전송하는 방식

    • 일반적으로 HTTP 응답에서 본문의 크기를 미리 알 수 없거나, 동적으로 생성되는 경우에 사용된다.

    • 예를 들어, 웹 서버가 대용량 파일을 전송하거나, 실시간으로 생성되는 데이터를 전송하는 경우에 Chunked Transfer Encoding이 유용하게 사용된다.

원인

  • Nginx의 proxy buffering 기능은 default 값이 ON
    → 실시간 전송이 되어야 하는 SSE에서 연결이 끊김.

  • 로컬에서는 Nginx을 통해서 진행되지 않기 때문에 해당 문제가 발생하지 않았다.

  • 여기서 프록시버퍼링이란?
    - 프록시 서버는 데이터를 실시간으로 전송하는 것이 아닌, 버퍼에 데이터를 일정량이 모이면 전송하는 방식 사용.
    → 네트워크 효율성 높이고, 빠르게 데이터 전달하는데 도움.

  • 스프링서버에서 Sse 연결시간을 길게 잡아도 Nginx 에서 prox_read_timeout동안 아무런 데이터 전송이 없으면 연결을 끊음.

해결방안

1. 스프링 서버

  • 응답 헤더를 아래와 같이 설정하면 된다.

    Content-Type: text/event-stream;
    Cache-Control: no-cache;
    X-Accel-Buffering: no;
  • 아래 코드와 같이 헤더에 캐시, 버퍼 설정해줄 수 있다.

    • headers.add("Cache-Control", "no-cache");
      → sse는 실시간 스트림이 업데이트 되어야 하나, 캐시를 사용하면 오래된 데이터가 전송 될 수 있다.
    • headers.add("X-Accel-Buffering", "no");
      → Nginx 서버에 응답을 버퍼링 하지 않고 전송하도록 지시한다.
@GetMapping(value = "/subscribe", produces = "text/event-stream")
public ResponseEntity<SseEmitter> subscribe() {
        System.out.println("sse 연결");
        Long userId = 1L;
    
        SseEmitter sseEmitter = sseEmitters.addEmitter(cacheService.cacheRequestIdWithUserId(userId));
    
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache");
        headers.add("X-Accel-Buffering", "no");
    
        return new ResponseEntity<>(sseEmitter, headers, HttpStatus.OK);
}
        

2. Nginx

  • sse통신을 하는 서버에서 오는 요청에 대해 buffering off 설정할 수 있다. (많은 블로그에서도 이 방법을 제시하고 있다)

  • 하지만 이는 proxy_buffering Nginx의 성능 최적화를 위한 것으로, 무조건 proxy_buffering off를 하는 것이 좋은 방법은 아닐 것이다.

  • 전체 proxy_buffering을 off하는 것 외에 다른 방법은 없을까?

  • 방법 1. 특정 엔드포인트만 개별적으로 proxy_buffering off한다

    • 특정 location 블록으로 들어오는 모든 요청의 proxy_buffering을 off한다.
        location /api { # /api 로 들어오는 요청은 개발용 서버로 
              rewrite ^/api/(.*)$ /$1 break;
              proxy_pass http://ssafy;
              proxy_pass_request_headers on;
              proxy_buffering off; # 이것 보다는 아래 방법으로 해보자
        }
  • 방법 2. prox_read_timeout 값을 Nginx에서 더 길게 설정한다! (이 방법을 더 추천한다)

    • nginx의 기본 read timeout은 60s이다.
    • 서비스의 특성을 고려하여 적절한 read timeout을 지정해주는 것이 좋다.
            location /api {
                  rewrite ^/api/(.*)$ /$1 break;
                  proxy_pass http://ssafy;
                  proxy_pass_request_headers on;
                  proxy_read_timeout 120s; # timeout default 60s에서 120s
            }

참고 자료

https://velog.io/@sally_devv/알람-서비스와-SSE
https://nginxstore.com/blog/nginx/가장-많이-실수하는-nginx-설정-에러-10가지/

profile
향유하는 개발자

1개의 댓글

comment-user-thumbnail
2025년 5월 27일

로컬환경에선 잘되던게 운영환경가니 안되길래
헤더추가해서 된건지 return type을 SseEmitter 에서 ResponseEntity 로 바꿔서 해결된건지 어쨌든 해결이 되었습니다.

답글 달기