From ff6f757fa460c8bb0309a45ac5e5de1242931512 Mon Sep 17 00:00:00 2001 From: ttipo Date: Tue, 27 Oct 2026 19:23:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(docker):=20=EC=9E=84=EC=8B=9C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EB=B3=B8=EB=AC=B8=204MB=C2=B7/api=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EB=A1=9C=EA=B7=B8=EC=97=90=EC=84=9C=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 임시 저장(POST·PUT /api/drafts)은 발송과 같은 본문이라 nginx 도 4MB·90초로 받는다(백엔드도 같은 파서). /api 접근 로그는 쿼리 없이 남겨 메일 검색·받는 사람 추천의 검색어가 요청마다 남지 않게 한다. Co-Authored-By: Claude Opus 5.5 --- frontend/nginx.conf | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 8b7bcf6..c0b7597 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -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;