[트러블슈팅] SSE Broken pipe error

JeongHun·2023년 10월 6일
post-thumbnail

SSE 구현

front-end 참고
back-end 참고

front-end 추가 설정
: EventSourcePolyfill 설치
: 45초 -> 60분으로 설정

  // sse function
  const sse = () => {
    console.log("permission", Notification.permission);

    const eventSource = new EventSourcePolyfill(
      `${process.env.REACT_APP_API_URL}/alarm`,
      {
        headers: {
          accessToken: `Bearer ${accessToken}`,
        },
      }
    );

    eventSource.addEventListener("sse", async (event) => {
    const data = JSON.parse((event as MessageEvent).data);

현상

로컬에서는 원활하게 작동하는데
서버에 올리기만 하면 오작동해서 로그를 찍어봤더니
client가 alarm을 막았다.

2023-10-04 05:13:10.014  INFO 1 --- [   scheduling-1] com.stn.hpdp.service.alarm.AlarmService  : SSE 연결
오류 발생

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:309) ~[tomcat-embed-core-9.0.79.jar!/:na]
at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:271) ~[tomcat-embed-core-9.0.79.jar!/:na]
at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:120) ~[tomcat-embed-core-9.0.79.jar!/:na]
at

해결방안 3가지

1. request 후 response 기다리기

: Client에서 보낸 데이터를 받고 리턴할 때 까지 응답을 기다리도록 설정

2. Exception 무시하기

: Client가 비정상적인 종료를 했을시 Broken pipe signal이 발생하고

Client의 종료를 서버에서 제어할 수 없기 때문에 해당 시그널을 무시하게 해준다.

3. 중복 요청 막기

: 연속적인 버튼 클릭을 방지하거나 execption 처리 부분에서 오류를 뱉지 않도록 처리한다.

설정

back-end controller -> AuthenticationPrincipal 추가

    @GetMapping(produces = "text/event-stream") // produces = MediaType.TEXT_EVENT_STREAM_VALUE
    public SseEmitter alarm(@AuthenticationPrincipal UserDetails userDetails,
            @RequestHeader(value = "Last-Event-ID", required = false, defaultValue = "") String lastEventId,
                            HttpServletResponse response) {
        return alarmService.alarm(userDetails, lastEventId, response);
    }

back-end Alarm service -> header 추가

        response.setHeader("X-Accel-Buffering", "no"); // NGINX PROXY 에서의 필요설정 불필요한 버퍼링방지
        response.setHeader("Connection", "keep-alive");
        response.setHeader("Cache-Control", "no-cache");

emitter exception 무시

try {
        emitter.send(SseEmitter.event()
                .id(eventId)
                .name("sse")
                .data(data));
    } catch (IOException exception) {
        if (exception.getMessage().contains("Broken pipe")) {
            log.warn("SSE 연결이 끊어짐. 무시됨.", exception); // 경고 메시지로 로그를 기록
        } else {
            log.info("SSE 연결 오류 발생", exception);
            emitterRepository.deleteById(emitterId);
            throw new CustomException(SSE_CONNECTED_FAIL);
        }
    }

nginx 설정 추가

location /api {
			proxy_pass [http://127.0.0.1:8080](http://127.0.0.1:8080/);     
			proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_set_header Connection '';
            proxy_http_version 1.1;
            proxy_buffering off;
            chunked_transfer_encoding off;

이렇게 설정해도 종종 client가 pipe를 끊어서
더미 데이터가 프론트에서 종종 확인되고 백엔드에서는 "SSE 연결이 끊어짐. 무시됨" 설정해준 로그가 계속 확인 되었다.


나중에 설정할 것들 :

아마 백엔드에서 더미를 시간마다 보내줘서 연결이 끊기지 않도록 조취를 취해줘야 할거 같다.

그리고 alarm 서비스가 백엔드 스케쥴링하고 트랜잭션으로 연결되어 있어서 로직을 조금 더 수정해야 할거 같다.

profile
coding study

0개의 댓글