feat(docker): 임시 저장 본문 4MB·/api 접근 로그에서 쿼리 빼기

임시 저장(POST·PUT /api/drafts)은 발송과 같은 본문이라 nginx 도 4MB·90초로 받는다(백엔드도 같은 파서).
/api 접근 로그는 쿼리 없이 남겨 메일 검색·받는 사람 추천의 검색어가 요청마다 남지 않게 한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 19:23:22 +09:00
parent 5710151c0b
commit ff6f757fa4
+21 -3
View File
@@ -14,6 +14,10 @@
log_format main_rid '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http_x_forwarded_for" rid=$request_id rt=$request_time';
# /api 용 — main_rid 와 같되 요청 줄에서 쿼리를 뺀다: 검색어(q) 등이 요청마다 남지 않게 (계약 2절 "받는 사람 추천" — 로그에는 개수만)
log_format api_rid '$remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$http_x_forwarded_for" rid=$request_id rt=$request_time';
server {
# 비-root(nginx-unprivileged) 구동이라 1024 미만 포트를 쓸 수 없다
listen 8080;
@@ -21,7 +25,7 @@ server {
access_log /var/log/nginx/access.log main_rid;
# 기본 요청 본문 한도 — 백엔드 JSON 파서 한도(1mb)와 같다. 업로드·발송 경로만 아래 /api 에서 늘린다
# 기본 요청 본문 한도 — 백엔드 JSON 파서 한도(1mb)와 같다. 업로드·발송·임시 저장 경로만 아래 /api 에서 늘린다
client_max_body_size 1m;
# ----------------------------------------
@@ -132,6 +136,8 @@ server {
# API 응답의 보안 헤더는 백엔드가 정한다 — 화면용 CSP 등을 겹쳐 붙이지 않는다
add_header_inherit off;
proxy_pass $backend_upstream;
# 쿼리 없는 로그 형식 — 하위 location 은 물려받는다. 오류로 넘어가는 이름 있는 location(아래 @api_*)은 따로 적는다
access_log /var/log/nginx/access.log api_rid;
# nginx 가 백엔드에 닿기 전에 끝내는 요청도 표준 실패 형식(JSON)으로 답한다 — 계약 머리말 "프록시가 직접 내는 오류"
# (= 없이 쓰면 상태 코드는 원래 값 그대로다. 백엔드가 보낸 4xx·5xx 는 proxy_intercept_errors 가 꺼져 있어 손대지 않는다)
@@ -168,7 +174,7 @@ server {
proxy_pass $backend_upstream;
}
# 메일 발송(POST /api/messages)만 JSON 본문 4MB — 서식 본문·긴 인용 때문 (계약 2절 413 표, 백엔드도 이 경로만 4MB 파서).
# 메일 발송(POST /api/messages)은 JSON 본문 4MB — 서식 본문·긴 인용 때문 (계약 2절 413 표, 백엔드도 이 경로와 아래 임시 저장만 4MB 파서).
# 계약이 정한 고정값이라 env 로 바꾸지 않는다 (나머지 /api 의 1m 도 백엔드 JSON_BODY_LIMIT 고정값과 같다 — 값이 같아 여유를 두지 않는다).
# 정확히 이 경로만 — /api/messages/{id}(PATCH·DELETE·첨부 내려받기)는 위 location 의 1m 그대로다. 같은 경로의 GET(목록)은 본문이 없다.
location = /api/messages {
@@ -176,13 +182,24 @@ server {
proxy_read_timeout 90s;
proxy_pass $backend_upstream;
}
# 메일 임시 저장 — 처음 저장(POST /api/drafts)·바꿔 저장(PUT /api/drafts/{id})도 JSON 본문 4MB. 본문이 발송과 같은 필드·한도라서다
# (계약 2절 413 표·"임시 저장", 백엔드도 이 두 요청은 발송과 같은 4MB 파서). 발송과 같은 이유로 env 로 바꾸지 않는다.
# 조회(GET /api/drafts/{id})는 본문이 없어 같은 블록에 둔다. 저장도 메일 엔진에 만들고·다시 읽고·옛것을 지우는 요청이 이어져 90s 를 기다린다
location ^~ /api/drafts {
client_max_body_size 4m;
proxy_read_timeout 90s;
proxy_pass $backend_upstream;
}
}
# /api 의 nginx 오류 응답 — message 는 비운다 (문구는 클라이언트가 code 로 사전에서 찾는다, 레지스트리와 겹치지 않게).
# traceId 는 이 nginx 의 요청 ID(로그의 rid=)다. 이름 있는 location 은 server 단계라 화면용 헤더 상속을 끈다.
# traceId 는 이 nginx 의 요청 ID(로그의 rid=)다. 이름 있는 location 은 server 단계라 화면용 헤더 상속을 끄고 로그 형식(api_rid)도 다시 정한다
# (내부 이동 뒤에는 이 location 의 access_log 가 쓰인다).
# return 200 은 본문만 정한다 — error_page 를 = 없이 썼으므로 상태 코드는 원래 값(413·502·504 등)이 그대로 나간다 (2026-10-27 확인)
location @api_payload_too_large {
add_header_inherit off;
access_log /var/log/nginx/access.log api_rid;
default_type application/json;
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;
@@ -192,6 +209,7 @@ server {
location @api_unavailable {
add_header_inherit off;
access_log /var/log/nginx/access.log api_rid;
default_type application/json;
add_header Cache-Control "no-store" always;
add_header X-Content-Type-Options "nosniff" always;