diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 7639ab3..852a32e 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -34,6 +34,9 @@ map $http_referer $log_referer { # 새 메일 확인(GET /api/mail-state — 화면이 보이는 동안 탭마다 1분에 한 번)은 성공(2xx·304)하면 접근 로그를 남기지 않는다 — # 로깅 규칙 "반복되는 정상 흐름(헬스체크, 폴링)은 로그를 남기지 않는다". 실패(401·5xx 등, 오류용 이름 있는 location 포함)는 남긴다. # 0 이면 server 단계 access_log 의 if= 가 그 줄을 쓰지 않는다. $uri 는 쿼리를 뺀 경로다 (이 요청은 내부 이동이 없다) +# 실시간 알림(GET /api/mail-events, SSE)은 성공도 남긴다 — 줄은 연결이 끝날 때 하나(브라우저마다 대략 1시간에 한 번 — 1분 확인의 수십분의 1)이고 +# rt= 가 연결이 이어진 시간이다. 스트림이 시작된 뒤에는 어떻게 끝나도(서버의 end · 탭 닫기 · 백엔드가 중간에 끊김) 상태가 200 이라, +# 200 을 빼면 일찍 끊기는 연결의 흔적까지 사라진다 (2026-10-28 확인). 연결 전 실패(401·429·503·502 등)는 물론 남는다. map "$request_method:$uri:$status" $log_access { "~^GET:/api/mail-state:(?:2[0-9][0-9]|304)$" 0; default 1; @@ -228,6 +231,32 @@ server { proxy_read_timeout 90s; proxy_pass $backend_upstream; } + + # 실시간 알림(GET /api/mail-events, 계약 2절 "실시간 알림") — SSE(text/event-stream). 브라우저마다 연결 하나가 액세스 토큰이 끝날 때까지 + # (최대 1시간 — 그 30초 전에 서버가 end 를 보내고 닫는다) 열려 있고, 서버가 25초마다 ping 을 보낸다. 아래 동작은 2026-10-28 가짜 백엔드로 확인했다. + # - 버퍼링을 끈다: 켜 두면 머리글을 첫 이벤트가 올 때까지 붙잡고, 백엔드가 청크 없이 연결을 닫아 끝내는 응답을 보내면 끝날 때까지 전부 붙잡는다. + # 백엔드가 보내는 X-Accel-Buffering: no 로도 꺼지지만 그 머리글에 기대지 않는다. 캐시는 지금 어디에도 없다 — 나중에 /api 에 생겨도 이 경로는 빼 둔다. + # - 압축을 끈다: gzip_types 에 text/event-stream 이 없어 지금도 압축하지 않지만, 목록을 넓혀도 이 경로는 압축하지 않게 + # (백엔드가 Cache-Control: no-transform 으로 바꾸지 말라고 해도 nginx 의 gzip 은 압축한다). + # - 읽기 시간 제한 90s(다른 90s 경로와 같은 값)는 응답 전체가 아니라 백엔드에서 "두 번 읽는 사이"의 시간이다 — 연결이 1시간 이어져도 + # 25초마다 오는 ping 이 매번 새로 세게 한다. 서버 ping 25초 · 화면 감시 75초보다 길어 정상 연결은 끊지 않는다 (ping 이 멈추면 화면이 먼저 끊는다). + # 머리글을 기다릴 때도 같은 값이라, 백엔드가 머리글을 보내지 않으면 90초에 504 로 끊는다 (@api_unavailable 의 JSON SYS_001). + # ⚠️ 서버의 ping 간격(계약 2절)을 바꾸면 이 값도 함께 본다 — ping 간격보다 넉넉히 길어야 한다. + # 클라이언트 쪽 쓰기 제한(send_timeout 60s)은 쓰기가 막혔을 때만 세므로 그대로 둔다. + # - X-Accel-Buffering 을 넘긴다: nginx 는 이 머리글을 기본으로 지우는데, 외부 리버스 프록시가 nginx 계열이면 이것을 보고 그쪽도 이 응답만 + # 버퍼링을 끈다 — 기본 설정의 nginx 가 머리글을 첫 이벤트까지 붙잡지 않고 바로 보낸다 (docker 스킬 "외부 리버스 프록시 요구사항"). + # 브라우저는 이 머리글을 쓰지 않는다. + # 머리글(proxy_set_header — 여기 쓰지 않는다) · 오류 응답(@api_unavailable 등) · 접근 로그는 위 /api 와 server 단계 것을 그대로 물려받는다. + # 열린 연결 하나가 nginx 연결 둘(클라이언트 쪽 · 백엔드 쪽)을 1시간까지 잡는다 — 수용량은 이미지 기본 nginx.conf 를 따른다 + # (워커마다 worker_connections 1024, 워커 수 auto). + location ^~ /api/mail-events { + proxy_buffering off; + proxy_cache off; + gzip off; + proxy_read_timeout 90s; + proxy_pass_header X-Accel-Buffering; + proxy_pass $backend_upstream; + } } # /api 의 nginx 오류 응답 — message 는 비운다 (문구는 클라이언트가 code 로 사전에서 찾는다, 레지스트리와 겹치지 않게).