개념
캐시된 데이터를 사용해도 되는지 확인 하는 작업
어떻게?
서버에 재검사 요청을 보내고, 서버는 캐시를 써도 되면 304 Not Modified로 응답, 캐시를 쓰면 안될 경우 200 OK 와 함께 새로운 값 반환 (서버에 요청을 보내야 하니 느리다.)
If-Modified-Since, GET 요청에 이 해더를 추가하면 캐시된 시간 이후에 변경된 경우에만 사본을 보낸다. 응답에 Last-Modifed와 같이 쓰일 수 있다. 응답에서 최근 변경된 날짜를 내려주면 해당 값으로 만료여부를 판단 할 수 있다.
시간단위로 검사하다보니 특정상황에서는 이 방법이 적절하지 않다.
- 1초 미만으로 캐쉬할 경우 (요청 응답 딜레이로 적절히 캐쉬가 안됨)
- 클라이언트나 서버가 시간 계산을 정확히 못할 경우 (GMT를 로컬 시간대로 옮기는 상황)
- 같은 데이터이지만 문서가 계속 갱신되는 경우
- 주석 변경으로 문서가 다시 만들어지는 경우
GET /index.html
If-Modified-Since: Mon, 13 May 2024, 16:65:00 GMT
// 변경되지 않았을 때
// 만료되었더라도 변경되었다는 의미는 아니기 때문에 재검사 필요
304 Not Modified
Expires: Fri, 17 May 2024, 12:00:00 GMT
// 변경되었을 때
// Cache-Control 또는 Exipres 중 하나를 포함하여 새로운 데이터의 유효기간도 함께 명시
200 OK
new full content
Cache-Control: max-age=484200
Expires: Fri, 17 May 2024, 12:00:00 GMT
// 삭제되었을 때
404 Not Found
If-None-Match, 버전으로 비교. 살짝 달라도 같다고 넘어가고 싶을 땐 접두사로 'W/' 를 추가하자
GET /index.html
If-None-Match: "v1.0"
304 Not Modified
Etag: "v1.0"
Date: Mon, 13 May 2024, 17:32:00 GMT
Real World Example
var express = require('express');
var router = express.Router();
let data = 'Hello, world!';
let etag = '12345';
/* GET home page. */
router.get('/', function(req, res, next) {
// 요청에서 If-None-Match 헤더를 확인
const ifNoneMatch = req.headers['if-none-match'];
if (ifNoneMatch && ifNoneMatch === etag) {
// ETag가 일치하면 304 Not Modified로 응답
res.status(304).end();
} else {
// ETag가 일치하지 않으면 데이터와 함께 200 OK 응답
res.setHeader('ETag', etag);
res.send(data);
}
});
module.exports = router;
// Request
GET / HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ko,en-US;q=0.9,en;q=0.8,ar;q=0.7,ko-KR;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Cookie: _ga=GA1.1.733788546.1701255981; _trmccid=1798465114d7b068; _ga_7Z7EEBRDDV=GS1.1.1706605296.3.1.1706605353.0.0.0; _ga_HDV8N4J6TV=GS1.1.1710223314.18.0.1710223372.0.0.0; Idea-b438443d=4a928ca0-45a7-4109-b48b-d6199823cf0a
Host: localhost:3000
If-None-Match: 12345
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
// Response
HTTP/1.1 304 Not Modified
X-Powered-By: Express
Date: Mon, 13 May 2024 08:45:46 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Week validation
Weak validation (W/ 접두사를 사용하는 ETag)은, 서버와 클라이언트 간에 전송되는 데이터의 완벽한 일치보다는 어느 정도의 유사성을 허용하는 것을 의미합니다. 이는 리소스의 핵심적인 부분은 변경되지 않았으나, 마지막 수정일이나 길이 같은 비핵심적인 부분이 바뀔 수 있다는 것을 나타낼 때 사용됩니다. 예를 들어, 댓글 수가 달린 블로그 포스트에서 댓글 수는 자주 바뀔 수 있지만, 본문 내용이 변경되지 않았다면 weak validation이 적용될 수 있습니다.
아래는 weak validation을 구현하는 Node.js 코드의 예입니다. 이 예제에서는 데이터의 핵심 부분만을 기준으로 ETag를 생성하고, 클라이언트의 요청에 따라 304 Not Modified 또는 200 OK를 응답합니다.
metaData가 변해도 main이 변하지 않으므로 계속 304 Not-Modified로 노출되는 코드
var express = require('express');
var router = express.Router();
const crypto = require('crypto'); // 해시를 생성하기 위해 crypto 모듈 사용
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
function generateWeakETag(content) {
// 메인 콘텐츠를 기반으로 Weak ETag 생성
let hash = crypto.createHash('sha256').update(content).digest('base64');
return `W/"${hash}"`;
}
let mainContent = 'Hello, world! This is the main content.';
let weakEtag = generateWeakETag(mainContent);
/* GET home page. */
router.get('/', function(req, res, next) {
// 요청에서 If-None-Match 헤더를 확인
const ifNoneMatch = req.headers['if-none-match'];
if (ifNoneMatch && ifNoneMatch === weakEtag) {
// ETag가 일치하면 304 Not Modified로 응답
res.status(304).end();
} else {
// ETag가 일치하지 않으면 데이터와 함께 200 OK 응답
res.setHeader('ETag', weakEtag);
let metaData = `Metadata that changes frequently. ${getRandomInt(40)}`;
res.send(`${mainContent}\n${metaData}`);
}
});
module.exports = router;
'IT > web' 카테고리의 다른 글
[http] cache-control: max-age (0) | 2024.05.14 |
---|---|
[http] no-store, no-cache (0) | 2024.05.13 |
[http] turnnel (0) | 2024.05.10 |
[http] MIME (0) | 2024.05.10 |
[FE] async / await (0) | 2024.04.29 |