내 ETag로 조건부 요청 비교하기

ETag를 바꾸며 어떤 조건이 일치하는지 확인합니다. 선택된 현재 표현이 존재하고, 다른 조건에서는 정상 200이 되는 GET/HEAD 요청을 가정합니다. 입력 문자열을 비교하는 연습이며 서버에 HTTP 요청을 보내거나 실제 캐시 적중을 검사하지 않습니다.
세 입력을 바꿔 보세요
- 요청을
"v2"하나로 바꾸면 현재"v1"과 불일치합니다. - 현재 태그를
W/"v1", 요청을"v1"로 바꿔도 약한 비교는 일치합니다. "a,b"는 쉼표가 포함된 태그 하나입니다. 목록을 단순히 쉼표로 쪼개면 잘못 셉니다.*는 현재 표현이 있다는 이 연습의 가정에서 일치합니다.
RFC 9110 §13.1.2의 If-None-Match는 약한 비교를 사용합니다. 이 연습은 PUT·삭제 요청, 인증 실패, 다른 선행 조건, 캐시 보관 정책을 판정하지 않습니다. 약한 일치가 두 본문의 바이트가 완전히 같다는 뜻도 아닙니다.
태그만 입력하면 됩니다. 입력은 브라우저 메모리에서 처리하며 저장·전송하지 않습니다. 실제 Nginx 응답은 이 글의 별도 격리 실험과 원자료에서 확인할 수 있습니다.
네트워크 도구에 304가 표시되고 응답 본문이 비어 있으면 파일 전송이 실패한 것처럼 보일 수 있다. 이번 실험은 같은 정적 파일을 두 번 요청하되, 두 번째 요청에만 첫 응답의 ETag를 넣었다. 다른 캐시 계층이나 브라우저 저장소를 끼우지 않고 HTTP 응답만 비교했다.
파일은 그대로 두고 요청 헤더만 바꿨다
sample.txt에는 짧은 테스트 문자열을 저장했다. 첫 요청에서 받은 본문 크기와 ETag를 기록한 다음, If-None-Match에 그 ETag를 그대로 넣어 다시 요청했다. 실험 도중 파일 내용이나 수정 시각은 바꾸지 않았다.
GET /sample.txt
# 첫 응답에서 받은 ETag 값을 그대로 사용
GET /sample.txt
If-None-Match: "FIRST_RESPONSE_ETAG"| 요청 | HTTP 상태 | 응답 본문 |
|---|---|---|
| 첫 요청 | 200 | 22바이트 |
| ETag를 보낸 재요청 | 304 | 0바이트 |
0바이트는 파일이 사라졌다는 뜻이 아니었다

첫 응답은 파일 본문을 포함했고 두 번째 응답은 본문 없이 304로 끝났다. 요청자가 기존 표현을 가지고 있다는 조건을 헤더로 전달했기 때문이다. 이 결과를 처음 방문한 사용자에게 아무 파일도 보내지 않는 동작과 혼동하면 안 된다. 실험에서 처음 요청한 경우에는 200과 본문을 받았다.
응답 본문 크기 0과 통신량 0도 다르다. 두 번째 요청과 응답 헤더의 왕복은 남아 있다. 여기서 기록한 bytes는 본문 길이이며 패킷 전체 크기나 사용자의 체감 로딩 시간은 측정하지 않았다.
갱신이 안 보일 때 확인할 순서
무조건 캐시를 끄기 전에 파일이 배포된 위치와 실제 응답을 비교한다. 로컬 파일만 바꾸고 웹 서버가 다른 디렉터리를 보고 있으면 새로고침으로 해결되지 않는다. 반대로 정상적인 304를 오류로 집계하면 캐시가 잘 동작할수록 실패 수가 늘어나는 잘못된 대시보드가 만들어질 수 있다.
| 같이 볼 증거 | 판정 | 다음 확인 |
|---|---|---|
| 조건 없는 요청은 새 본문, 조건부 요청은 304 | 서버가 해당 validator로 재사용을 허용 | 이전 ETag와 새 파일의 ETag·mtime·크기 대조 |
| 조건 없는 요청도 이전 본문 | 단순한 304 처리만으로 설명할 수 없음 | 요청 도메인·배포 경로·중간 캐시 계층 확인 |
| 200과 새 본문인데 화면은 이전 상태 | 문서 요청 외 다른 자원이 관여할 수 있음 | 브라우저가 읽은 자산 URL과 캐시 상태 확인 |
표는 원인을 확정하는 규칙이 아니라 다음 확인 지점을 좁히는 순서다. 아래 후속 실험은 첫 번째 경우를 의도적으로 만들었다. 운영에서 조건 없는 요청을 보낸다고 CDN의 저장본까지 반드시 우회하는 것은 아니다.
- 원본이 503이어도 화면은 200: 오래된 캐시를 구분하는 법
응답 본문 자체가 이전 값이면 저장본을 반환하는 계층도 확인한다.
이번 기록을 적용할 범위
작은 정적 파일을 대상으로 검증 요청을 이해하는 데 사용할 수 있다. 브라우저의 강제 새로고침 동작이나 CDN의 캐시 정책은 따로 확인해야 한다. 이 글은 ETag를 보안 해시로 검증하거나 파일의 모든 변경을 감지하는 방식을 시험한 기록은 아니다.
추가 확인: ETag가 내용 전체의 해시라고 가정하면 안 된다
304 응답을 이해할 때는 서버가 ETag를 어떤 정보로 만드는지도 중요하다. 추가 실험에서는 정적 파일을 version-a에서 version-b로 바꿨다. 두 문자열은 모두 9바이트다. 파일 크기를 같게 유지하고 수정 시각(mtime)도 같은 값으로 되돌린 뒤, 최초 응답의 ETag를 If-None-Match로 보냈다.
| 파일과 요청 조건 | HTTP 상태 | 본문 바이트 |
|---|---|---|
| 최초 요청 | 200 | 9 |
| 내용 변경·크기와 mtime 유지·이전 ETag | 304 | 0 |
| 같은 파일을 조건 없이 요청 | 200 | 9 |
| mtime도 변경·이전 ETag | 200 | 9 |
내용이 바뀌었는데도 이 조건부 요청은 304와 빈 본문을 반환했다. 같은 파일을 조건 헤더 없이 요청하면 200과 version-b가 나왔다. 이어 mtime을 2초 뒤로 바꾸고 최초 ETag로 요청하자 200과 새 본문을 받았다. 응답을 바꾼 조건이 무엇인지 파일 내용, 크기, 시각을 분리해 관찰한 것이다.
이번 결과가 의미하는 배포상의 주의점
여기서 시험한 Nginx 1.18.0의 정적 파일 응답에서는 파일 내용이 다르다는 사실만으로 ETag가 달라지지 않았다. 모든 서버나 애플리케이션의 ETag가 같게 동작한다는 뜻은 아니다. 이 결과만으로 브라우저 화면이 실제로 얼마나 오래 남는지 판단할 수도 없다. 재현 스크립트는 브라우저와 CDN을 사용하지 않고 조건 헤더를 직접 보냈다.
실제 배포에서 오래된 본문이 보인다면 먼저 조건 없는 응답에도 새 내용이 없는지, 조건부 요청에서만 304가 나오는지 구분한다. 전자는 배포 경로나 중간 캐시를 볼 이유가 되고, 후자는 응답 검증자와 파일 메타데이터를 살펴볼 단서다. 이 구분은 원인을 확정하는 공식이 아니라 조사 범위를 줄이기 위한 순서다.
# 본문은 파일에 저장하고 ETag와 Last-Modified를 비교한다.
curl -sS -D - -o first-body.txt https://example.com/sample.txt
# 실제 첫 응답의 ETag를 정확히 넣어 비교한다.
curl -sS -D - -o conditional-body.txt -H 'If-None-Match: "실제-ETag"' https://example.com/sample.txt정적 자산의 내용이 바뀌면 URL도 바꾸는 빌드 방식은 동일 URL의 재검증 조건에만 기대는 상황을 줄여준다. 다만 HTML이 참조하는 이전 자산을 언제까지 남길지는 별도 문제다. 관련 배포 글에서는 HTML을 먼저 받은 방문자의 자산 요청이 실패하는 순서를 직접 확인했다.
편집자 의견
ETag가 같다는 결과는 서버가 정의한 검증자의 일치로만 읽겠습니다. 추가 실험에서 검증자 생성 조건을 바꾸면 본문의 차이와 ETag 비교의 의미가 달라질 수 있어, 무조건 전체 내용 해시라고 취급하는 방식은 피하고 싶습니다.
배포 직후 특정 본문이 반영됐는지 확인할 목적이라면 별도의 GET 본문 검사를 함께 두겠습니다. 전송량 절감의 확인과 최신 릴리스의 확인에 같은 통과 조건을 쓰지 않는 편이 실수를 줄입니다.
판단 근거: 추가 확인: ETag가 내용 전체의 해시라고 가정하면 안 된다
의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단
실험 환경과 원자료
nginx version: nginx/1.18.0 (Ubuntu) · Linux · Python 3.10.12
로컬 임시 디렉터리 · 단일 실행 · 실제 운영 트래픽 제외
관찰 시각: 2026-09-12T13:53:51.407530+00:00
Python 3과 Nginx가 있는 별도 실험 환경에서 실행합니다. 스크립트는 임시 폴더와 로컬 포트를 만들며, 종료할 때 자신이 시작한 프로세스만 정리합니다.
python3 run-lab.py --output observations.json참고한 공식 문서
기존 자료 확인: 2026-10-18 · 원고 수정: 2026-10-24. 잘못된 내용은 정정 요청으로 알려주세요.
추가 실험의 환경과 원자료
관찰 시각: 2026-09-12T15:07:44.600003+00:00
os: Linux · python: 3.10.12 · nginx: nginx version: nginx/1.18.0 (Ubuntu)
별도 임시 디렉터리, 루프백 Nginx와 합성 응답 서버에서 순차 요청. 운영 설정과 외부 API는 사용하지 않음.
확인하지 않은 범위: 단일 실행. CDN, 브라우저 캐시, 동시 부하, 인증, NFS, 디스크 장애, 운영 데이터는 시험하지 않음.
Python 3이 있는 별도 Linux 실험 환경에서 실행합니다. Nginx 실험에는 Nginx도 필요합니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.
python3 lab-nginx-boundaries.py --output new-observations.json이 글의 보완 기록
- · 조건부/일반 요청의 결과별 진단표와 캐시 후속 확인을 추가했다.
- · 중복 진단표를 줄이고 조건 없는 응답·조건부 응답·화면의 차이를 한 표에서 판단하도록 정리했습니다. 기존 측정값은 유지했습니다.
- · 내 ETag와 If-None-Match 목록을 브라우저에서 비교하는 연습을 추가했습니다. 기존 Nginx 측정값과 관찰 날짜는 유지했습니다.
- · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
- · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.