🐞 버그 설명
상황
알림 서버에서 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를 다음에도 사용한다면 이 점 참고하시면 좋을 것 같습니다.
'프로젝트 > FitTrip' 카테고리의 다른 글
[트러블슈팅] 각 서비스의 로그 확인을 위한 로깅 시스템 구축 (0) | 2024.06.26 |
---|---|
[트러블슈팅] 오픈바이두 클라이언트 연결 오류 해결 과정 (0) | 2024.06.25 |
[트러블슈팅] 무중단 배포간 Docker Compose 오류 해결 (2) | 2024.06.25 |
[트러블슈팅] JPA의 deleteAll() 대신 IN을 사용한 성능 최적화 (0) | 2024.06.25 |
[트러블슈팅] 서버 배포간 다양한 CORS 오류 (0) | 2024.06.25 |