본문 바로가기
프로젝트/FitTrip

[트러블슈팅] SSE 적용시 게이트웨이와 유저 서비스 연동간 발생 오류 해결

by 진꿈청 2024. 6. 25.

🐞 버그 설명

 

상황

 

알림 서버에서 SSE를 사용함에 따라 NGINX에 다음과 같은 설정을 추가로 작업했습니다.

 

 

추가된 NGINX 설정

    location /api/notice {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' $allowed_origin always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Allow-Credentials' 'true';
            return 204;
        }
        add_header 'Access-Control-Allow-Origin' $allowed_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true';
#        add_header 'Content-Type' 'text/event-stream';
        add_header Cache-Control no-cache;

        proxy_pass http://localhost:8000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        proxy_buffering off;
        chunked_transfer_encoding off;
        keepalive_timeout 7200;
    }

여기서 proxy_buffering off 설정은 SSE는 즉시 이벤트를 처리해주는 것이 중요하기 때문에 필요합니다.

 

SSE에서 클라이언트와 서버의 SSE 연결은 계속 열려 있으며, 데이터는 스트림을 통해 전송됩니다.
따라서, NGINX의 프록시 버퍼링을 비활성화 함으로 서버로부터 클라이언트로 즉시 데이터를 전송합니다.

 

그러나, 이것 자체에 문제는 없었으나 문제는 유저 서비스 쪽에서 발생했습니다.
(물론, 유저 서비스 자체 문제는 아닙니다.)

 

유저 서비스에서 전달받은 오류는 HttpMediaTypeNotSupportedException::415 status code 오류였습니다.
갑자기 발생한 원인을 찾던 중 발견한 것은 API Gateway가 로그아웃 여부 확인을 위해 유저 서비스와 통신하는 부분이었습니다.

 

버그 해결

SSE는 'Content-Type' 'text/event-stream'을 사용하기에 관련 내용이 헤더에 담기게 됩니다.

 

이때, 유저 서비스는 해당 Content-Type과 관련된 설정이 없으므로,
해당 헤더가 전달된다면 당연하게도(?) 오류가 발생할 것이라는 생각이 들었습니다.

 

그래서, 기존에 API Gateway에서 유저 서비스와 통신할 때 헤더를 전부 다 담아서 보냈던 것이 생각났습니다.
(API Gateway에서 헤더를 전부 담는 코드 작성 시점에서는 SSE의 사용 여부를 몰라 혹시 모를 오류에 대비해 다 담았던 것 같습니다.)

 

 

변경 전 게이트웨이의 AuthorizationFilter.java

    private Mono<Boolean> doNotLogout(String accessToken, ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        // accessToken을 JSON 객체로 변환
        String requestBody = accessToken;
        return webClientBuilder.build().post()
                .uri("lb://USER-SERVICE/isLogin")
                .headers(httpHeaders -> httpHeaders.addAll(headers))
                .contentType(MediaType.APPLICATION_JSON) // JSON 컨텐츠 타입 명시
                .body(BodyInserters.fromValue(requestBody)) // JSON 객체로 변환된 본문 사용
                .retrieve()
                .bodyToMono(DataResponseDto.class)
                .map(response -> Boolean.TRUE.equals(response.getData()));
    }

 

변경 후 게이트웨이의 AuthorizationFilter.java

    private Mono<Boolean> doNotLogout(String accessToken, ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        // accessToken을 JSON 객체로 변환
        String requestBody = accessToken;
        return webClientBuilder.build().post()
                .uri("lb://USER-SERVICE/isLogin")
//                .headers(httpHeaders -> httpHeaders.addAll(headers))
                .contentType(MediaType.APPLICATION_JSON) // JSON 컨텐츠 타입 명시
                .body(BodyInserters.fromValue(requestBody)) // JSON 객체로 변환된 본문 사용
                .retrieve()
                .bodyToMono(DataResponseDto.class)
                .map(response -> Boolean.TRUE.equals(response.getData()));
    }

위와 같이 변경을 했을 때 유저 서비스에서 관련 오류가 발생하지 않았습니다.

 

만약, SSE를 다음에도 사용한다면 이 점 참고하시면 좋을 것 같습니다.