본문 바로가기
IT/web

[http] If-Modified-Since, If-None-Match

by 내일은교양왕 2024. 5. 13.

개념

캐시된 데이터를 사용해도 되는지 확인 하는 작업

 

어떻게?

서버에 재검사 요청을 보내고, 서버는 캐시를 써도 되면 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