리소스의 특정 버전을 가리키는 식별자를 담은 HTTP 응답 헤더(entity tag). 클라이언트가 이 값을 다시 보내 "그사이 바뀌었는지"를 묻는 조건부 요청(Conditional Request)의 기준이 된다. RFC(Request for Comments) 9110에 정의된 표준 헤더다.
GET /style.css
→ 200 OK
ETag: "33a64df5"
GET /style.css
If-None-Match: "33a64df5"
→ 304 Not Modified (본문 없음, 캐시 재사용)- 같으면
304로 본문 전송을 생략해 대역폭을 아낀다. 다르면200과 새 본문, 새 ETag가 온다 → 브라우저 캐시 Last-Modified/If-Modified-Since와 같은 역할이지만 초 단위 시각이 아니라 버전 자체를 비교하므로 더 정확하다W/"..."는 약한 ETag다. 바이트가 똑같지 않아도 의미상 같으면 같은 값으로 친다(압축 방식만 다른 경우 등)
버전 토큰으로 다루기
표준은 "버전을 식별한다"는 역할만 정하고 만드는 방법은 정하지 않는다. AWS S3(Amazon Simple Storage Service)나 Cloudflare R2 같은 객체 저장소도 객체마다 ETag를 붙이는데, 단일 업로드에서는 MD5(Message-Digest Algorithm 5)처럼 보여도 멀티파트 업로드나 암호화 방식에 따라 MD5가 아니다. 그래서:
- ETag를 파일 내용 해시로 해석하지 않는다. "이 객체의 현재 버전 토큰"으로만 쓴다
- 낙관적 잠금(Optimistic Locking)에 쓰기 좋다. 읽을 때 받은 ETag를 저장할 때
If-Match로 보내면, 그사이 누가 바꿨을 경우412 Precondition Failed로 실패한다. 덮어쓰기 충돌을 막는다 → 멱등성 - 사용자에게 보여 줄 수정 시각은 ETag가 아니라
Last-Modified(또는 저장소의 수정 시각 필드)를 쓴다
조건부 헤더별 결과
| 헤더 | 비교 | 조건이 거짓일 때 |
|---|---|---|
If-None-Match | 약한 비교 | GET·HEAD는 304, 그 밖의 메서드는 412 |
If-Match | 강한 비교 | 412 |
If-Modified-Since | 수정 시각 | GET·HEAD에만 쓰고 304. 다른 메서드면 헤더를 무시한다 |
If-Match는 강한 비교라서W/"..."약한 ETag와는 일치하지 않는다. 덮어쓰기 방지에 쓰려면 강한 ETag를 내준다If-None-Match: *는 "아직 아무 표현도 없을 때만"이라는 뜻이다. PUT으로 처음 만들 때 붙이면 두 클라이언트가 동시에 만들다 한쪽을 덮어쓰는 일을 막는다- 압축(
Content-Encoding)이 다른 표현은 강한 ETag가 서로 달라야 한다. 이때 응답에Vary: Accept-Encoding을 붙여 캐시가 두 표현을 구분하게 한다 - 서버는 "ETag 비교 → 수정 → 새 ETag 발급"을 한 번에 처리해야 한다. 비교와 수정 사이에 다른 요청이 끼어들면 막은 의미가 없다. 예를 들어 ETag를 버전 번호에서 만든다면
UPDATE ... WHERE id = ? AND version = ?같은 조건부 업데이트나 트랜잭션으로 비교와 수정을 묶을 수 있다(낙관적 잠금과 비관적 잠금)
409·412·428
409 Conflict: 요청이 리소스의 현재 상태와 충돌한다. RFC는 버전이 엇갈린 PUT을 예로 들며, 이미 쓰는 이메일로 가입하는 경우처럼 조건부 헤더 없이 서버가 자기 규칙으로 판단한 충돌에도 쓸 수 있다. 사용자가 원인을 알고 고쳐서 다시 보낼 수 있게 충돌 정보를 본문에 담는다412 Precondition Failed: 클라이언트가 헤더로 건 조건(If-Match등)이 거짓이다. 판단 기준이 HTTP 조건부 헤더다428 Precondition Required: 서버가 조건부 요청을 요구하는데 클라이언트가 조건을 보내지 않았다.If-Match없이 덮어쓰는 요청을 거절할 때 쓴다
책: 4장. 동시성: 검색 결과가 뒤죽박죽 나오는 이유
명세: RFC 9110 §13.1.1 — If-Match · §13.1.2 — If-None-Match · §13.1.3 — If-Modified-Since · §8.8.3.3 — 인코딩별 ETag · §15.5.10 — 409 · §15.5.13 — 412 · RFC 6585 §3 — 428