ブラウザキャッシュメカニズムの理解と実装

ブラウザキャッシュメカニズム

Last-Modifiedによるキャッシュ検証

HTTPヘッダのLast-Modifiedフィールドを使用して、ブラウザが保存しているキャッシュが最新かを確認します。サーバー側でファイルの最終更新日時と比較し、変更がなければ304 Not Modifiedを返します。

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true);
  const filePath = path.join(__dirname, pathname);

  fs.stat(filePath, (err, stats) => {
    if (err) {
      return sendError(res);
    }

    const ifModifiedSince = req.headers['if-modified-since'];
    const lastModified = stats.ctime.toGMTString();

    if (ifModifiedSince === lastModified) {
      res.writeHead(304);
      res.end();
    } else {
      sendFile(req, res, filePath, stats);
    }
  });
}).listen(8080);

function sendError(res) {
  res.end('Not Found');
}

function sendFile(req, res, filePath, stats) {
  res.setHeader('Content-Type', mime.getType(filePath));
  res.setHeader('Last-Modified', stats.ctime.toGMTString());
  fs.createReadStream(filePath).pipe(res);
}

ETagによるキャッシュ検証

ETagは、リソースの内容に基づいた一意の識別子を生成し、キャッシュの検証に使用します。リソース内容が変更された場合のみETagが変更されるため、より正確なキャッシュ制御が可能です。

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const crypto = require('crypto');

http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true);
  const filePath = path.join(__dirname, pathname);

  fs.stat(filePath, (err, stats) => {
    if (err) {
      return sendError(res);
    }

    const ifNoneMatch = req.headers['if-none-match'];
    const readStream = fs.createReadStream(filePath);
    const hash = crypto.createHash('md5');

    readStream.on('data', (data) => {
      hash.update(data);
    });

    readStream.on('end', () => {
      const etag = hash.digest('hex');
      if (ifNoneMatch === etag) {
        res.writeHead(304);
        res.end();
      } else {
        sendWithETag(res, filePath, etag);
      }
    });
  });
}).listen(8080);

function sendError(res) {
  res.end('Not Found');
}

function sendWithETag(res, filePath, etag) {
  res.setHeader('Content-Type', mime.getType(filePath));
  res.setHeader('ETag', etag);
  fs.createReadStream(filePath).pipe(res);
}

Cache-Controlによるキャッシュ有効期限の設定

Cache-Controlヘッダを使用して、キャッシュの有効期間や振る舞いを指定します。max-ageで指定した秒数だけブラウザがキャッシュを保持できます。

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true);
  const filePath = path.join(__dirname, pathname);

  fs.stat(filePath, (err, stats) => {
    if (err) {
      return sendError(res);
    }
    sendWithCacheControl(res, filePath);
  });
}).listen(8080);

function sendError(res) {
  res.end('Not Found');
}

function sendWithCacheControl(res, filePath) {
  res.setHeader('Content-Type', mime.getType(filePath));
  res.setHeader('Cache-Control', 'max-age=30');
  fs.createReadStream(filePath).pipe(res);
}

Cache-Controlの主なディレクティブ:

  • private:クライアントのみがキャッシュ可能
  • public:クライアントとプロキシがキャッシュ可能
  • max-age:指定された秒数だけキャッシュが有効
  • no-cache:サーバーで再検証が必要
  • no-store:キャッシュ不可

タグ: HTTP キャッシュ制御 Node.js Webパフォーマンス

5月15日 02:30 投稿