Внешний интерфейс также должен понимать механизм HTTP-кэширования.

внешний интерфейс сервер HTTP браузер

Когда я недавно читал вопросы интервью, я всегда видел какие-то темы про Http-кеширование, но я всегда мало знал и плохо в этом разбирался, тем более, что в Http-заголовке много полей с информацией, что если-модифицировано-с , а если-ничего не совпадает, какая головная боль. Позже я вдруг подумал, что было бы лучше, если бы я мог собрать сервер самостоятельно, добавить информацию в заголовок и увидеть результат. Просто делайте то, что вы говорите, находите всевозможную информацию в Интернете, а затем используйте expressjs для добавления различной информации в заголовок, вы хорошо понимаете кэширование Http.

Личный блог для пониманияБлог Се Сяофэй

Введение в HTTP

  Связь между браузером и сервером осуществляется через протокол HTTP.Протокол HTTP всегда означает, что клиент инициирует запрос, а сервер отправляет ответ. Модель выглядит следующим образом:

http-modal

  HTTP-сообщение — это блок данных, который отправляется и на который отвечается во время связи между браузером и сервером. Браузер запрашивает данные с сервера и отправляет сообщение запроса, сервер возвращает данные браузеру и возвращает ответное сообщение. Информация сообщения в основном делится на две части:

  1. Заголовок пакета: некоторая дополнительная информация (файлы cookie, информация о кеше и т. д.), информация о правилах, связанных с кешем, включается в заголовок.
  2. Часть тела данных: содержимое данных, которое HTTP-запрос действительно хочет передать.

   В этой статье используются следующие заголовки:

Имя поля поле принадлежит
Pragma Универсальный заголовок
Expires заголовок ответа
Cache-Control Универсальный заголовок
Last-Modified заголовок ответа
If-Modified-Sice заголовок запроса
ETag заголовок ответа
If-None-Match заголовок запроса

Классификация HTTP-кеша

  Кэш HTTP можно разделить на две категории: принудительный кеш (также называемый сильным кешем) и согласованный кеш. Эти два типа правил кэширования различны.Принудительное кэширование не требует взаимодействия с сервером, если кэшированные данные не являются недействительными.Кэширование с согласованием, как следует из названия, необходимо сравнить, чтобы определить, можно ли использовать кэш.

Два типа правил кэширования могут существовать одновременно, и обязательный кеш имеет более высокий приоритет, чем согласованный кеш.То есть, когда выполняется правило обязательного кеша, если кеш действителен, кеш будет использоваться напрямую , и согласованное правило кэширования не будет выполняться.

оригинальная модель

   Давайте просто создадим сервер Express без добавления каких-либо заголовков кеша.

const express = require('express');
const app = express();
const port = 8080;
const fs = require('fs');
const path = require('path');

app.get('/',(req,res) => {
    res.send(`<!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Document</title>
    </head>
    <body>
        Http Cache Demo
        <script src="/demo.js"></script>
    </body>
    </html>`)
})

app.get('/demo.js',(req, res)=>{
    let jsPath = path.resolve(__dirname,'./static/js/demo.js');
    let cont = fs.readFileSync(jsPath);
    res.end(cont)
})

app.listen(port,()=>{
    console.log(`listen on ${port}`)    
})

   Мы видим, что результат запроса выглядит следующим образом:

no-cache

Процесс запроса    выглядит следующим образом:

  • Браузер запрашивает статический ресурс demo.js
  • Сервер считывает файл demo.js с диска и возвращает его браузеру.
  • Браузер снова запрашивает, и сервер перечитывает файл a.js на диске и возвращает его браузеру.
  • Циклический запрос. .

   Видно, что трафик данного метода запроса связан с количеством запросов, в то же время очевидны и недостатки:

  • пустая трата пользовательского трафика
  • Пустая трата ресурсов сервера, сервер читает файл с диска, а затем отправляет файл в браузер
  • Браузер должен дождаться загрузки и выполнения js перед отображением страницы, что влияет на взаимодействие с пользователем.

   Затем мы начинаем добавлять информацию о кеше к информации заголовка.

1. Принудительное кэширование

  Принудительное кэширование делится на два случая: Expires и Cache-Control.

Expires

Значение   Expires — это время истечения кэша, которое сервер сообщает браузеру (значение — время по Гринвичу, то есть среднее время по Гринвичу), то есть когда будет сделан следующий запрос, если текущее время на стороне браузера не изменилось. достигнуто время истечения срока действия, кэшированные данные будут использоваться напрямую. Давайте установим информацию заголовка ответа Expires через наш сервер Express.

//其他代码...
const moment = require('moment');

app.get('/demo.js',(req, res)=>{
    let jsPath = path.resolve(__dirname,'./static/js/demo.js');
    let cont = fs.readFileSync(jsPath);
    res.setHeader('Expires', getGLNZ()) //2分钟
    res.end(cont)
})

function getGLNZ(){
    return moment().utc().add(2,'m').format('ddd, DD MMM YYYY HH:mm:ss')+' GMT';
}
//其他代码...

   Мы добавили заголовок ответа Expires в demo.js, но так как это GMT, нам нужно преобразовать его через momentjs. Когда будет сделан первый запрос, запрос все равно будет отправлен на сервер, и время истечения срока действия и файл будут возвращены нам одновременно; но когда мы обновимся, настал момент стать свидетелем чуда:

expires-cache

   Видно, что файл читается напрямую из кеша (кеша памяти), и никакого запроса не делается. Мы установили время истечения здесь на две минуты.После двух минут вы можете обновить страницу, чтобы увидеть, что браузер снова отправляет запрос.

  Хотя этот метод добавляет контроль кеша и экономит трафик, все равно остаются следующие проблемы:

  • Поскольку время браузера и время сервера не синхронизированы, если браузер устанавливает очень позднее время, время истечения срока действия не имеет смысла.
  • По истечении срока действия кеша, независимо от того, изменился ли файл, сервер снова прочитает файл и вернет его в браузер

   Но Expires — это вещь HTTP 1.0, и теперь браузеры по умолчанию используют HTTP 1.1 по умолчанию, поэтому его роль в основном игнорируется.

Cache-Control

Добавлена ​​новая схема кэширования для синхронизации времени между браузером и сервером, на этот раз сервер не сообщает браузеру время истечения срока действия напрямую, а сообщает относительное время Cache-Control=10 секунд, что означает, что в течение 10 секунд, используйте кеш браузера напрямую.

app.get('/demo.js',(req, res)=>{
    let jsPath = path.resolve(__dirname,'./static/js/demo.js');
    let cont = fs.readFileSync(jsPath);
    res.setHeader('Cache-Control', 'public,max-age=120') //2分钟
    res.end(cont)
})

cache-control

2. Согласовать кеш

Недостаток принудительного кеширования очевиден, то есть каждый раз определяется, истекает ли срок действия кеша по времени, но когда время истечения достигнуто, если файл не изменился, это пустая трата ресурсов сервера получить файл снова. Кэш согласования используется в сочетании с двумя группами пакетов:

  1. Last-Modified и If-Modified-Since
  2. ETag и If-None-Match

diagram-http

Last-Modified

   В целях экономии ресурсов сервера схема снова улучшена. Браузер и сервер договариваются друг с другом, и каждый раз, когда сервер возвращает файл, он сообщает браузеру время последней модификации файла на сервере. Процесс запроса выглядит следующим образом:

  • Браузер запрашивает статический ресурс demo.js
  • Сервер считывает файл demo.js с диска и возвращает его браузеру со временем последней модификации файла Last-Modified (стандартный формат GMT)
  • Когда срок действия кэшированного файла в браузере истекает, браузер возвращает заголовок запросаIf-Modified-Since(равно Last-Modified последнего запроса), запрашивающего сервер
  • Сервер сравнивает заголовок запросаIf-Modified-Sinceи время последнего изменения файла. Если он соответствует, продолжайте использовать локальный кеш (304), если нет, снова верните содержимое файла и Last-Modified.
  • Циклический запрос. .

Процесс реализации    кода выглядит следующим образом:

app.get('/demo.js',(req, res)=>{
    let jsPath = path.resolve(__dirname,'./static/js/demo.js')
    let cont = fs.readFileSync(jsPath);
    let status = fs.statSync(jsPath)

    let lastModified = status.mtime.toUTCString()
    if(lastModified === req.headers['if-modified-since']){
        res.writeHead(304, 'Not Modified')
        res.end()
    } else {
        res.setHeader('Cache-Control', 'public,max-age=5')
        res.setHeader('Last-Modified', lastModified)
        res.writeHead(200, 'OK')
        res.end(cont)
    }
})

   Мы несколько раз обновили страницу и видим, что результаты запроса следующие:

last-modified-cache

  Хотя эта схема более оптимизирована, чем предыдущие три схемы, браузер определяет, был ли файл изменен, и если изменений нет, то не отправляет файл, но все же имеет следующие недостатки:

  • Поскольку время последней модификации — это время по Гринвичу, оно может быть точным только до секунды.Если файл был изменен несколько раз в течение 1 секунды, сервер не знает, что файл был изменен, и браузер не может получить последний файл.
  • Если файл на сервере был изменен несколько раз, но содержимое не изменилось, серверу необходимо снова вернуть файл.

ETag

   Чтобы решить проблему, вызванную неточным временем модификации файла, сервер и браузер снова договариваются, на этот раз вместо возврата времени возвращается уникальный идентификатор ETag файла. ETag изменяется только при изменении содержимого файла. Процесс запроса выглядит следующим образом:

  • Браузер запрашивает статический ресурс demo.js
  • Сервер читает файл demo.js на диске и возвращает его браузеру с уникальным идентификатором ETag файла.
  • Когда срок действия кэшированного файла в браузере истекает, браузер возвращает заголовок запросаIf-None-Match(равно ETag последнего запроса), запрашивающего сервер
  • Сервер сравнивает заголовок запросаIf-None-Matchи ETag файла. Если он соответствует, продолжайте использовать локальный кеш (304), если нет, снова верните содержимое файла и ETag.
  • Циклический запрос. .
const md5 = require('md5');

app.get('/demo.js',(req, res)=>{
    let jsPath = path.resolve(__dirname,'./static/js/demo.js');
    let cont = fs.readFileSync(jsPath);
    let etag = md5(cont);

    if(req.headers['if-none-match'] === etag){
        res.writeHead(304, 'Not Modified');
        res.end();
    } else {
        res.setHeader('ETag', etag);
        res.writeHead(200, 'OK');
        res.end(cont);
    }
})

Результат запроса    следующий:

etag-cache

что-то дополнительное

  В таблице заголовка сообщения мы видим, что есть поле под названием Pragma, которое представляет собой пыльную историю....

secret

  В «далекую» эру http1.0 задать метод кэширования для клиента можно через два поля — Pragma и Expires. Хотя эти два поля могут быть давно отброшены, чтобы сделать протокол HTTP обратной совместимостью, вы все еще можете видеть, что многие веб-сайты все еще содержат эти два поля.

О прагме

   Когда значение поляno-cache, он скажет браузеру не кэшировать ресурс, то есть он должен каждый раз отправлять запрос на сервер.

res.setHeader('Pragma', 'no-cache') //禁止缓存
res.setHeader('Cache-Control', 'public,max-age=120') //2分钟

  Запретите кеширование через Pragma и установите двухминутное кеширование через Cache-Control, но при повторном посещении мы обнаружим, что браузер снова инициирует запрос, что объясняетPragma的优先级高于Cache-Control

О Cache-Control

   Мы видим, что свойство в Cache-Control является общедоступным, так что же это значит? На самом деле Cache-Control не только имеет max-age, но и его общие значения: private, public, no-cache, max-age, no-store. Значение по умолчанию — private. Значение каждого значения следующее :

  • частный: клиент может кэшировать
  • общедоступный: и клиент, и прокси-сервер кэшируются
  • max-age=xxx: кешированный контент истечет через xxx секунд
  • без кеша: необходимо использовать кеш сравнения для проверки кешированных данных
  • no-store: весь контент не будет кэшироваться, принудительный кеш, кеш сравнения не будет запускаться

   Итак, когда мы обновим страницу, если мы просто нажмем F5, чтобы просто отправить запрос, нажмите Ctrl+F5, и мы обнаружим, что есть еще два поля Pragma: no-cache и Cache-Control: no-cache в заголовке запроса.

приоритет кеша

   Выше мы сказали, что приоритет принудительного кеша выше, чем у согласованного кеша, а приоритет Pragma выше, чем у Cache-Control, так что насчет порядка приоритетов других кешей? Проверил информацию в интернете и получил следующий заказ (PS: Заинтересованные детской обувью можете проверить правильность и сказать):

Pragma > Cache-Control > Expires > ETag > Last-Modified

Если вы думаете, что это хорошо написано, пожалуйста, следуйте за мнойДомашняя страница Наггетс. Для получения дополнительных статей, пожалуйста, посетитеБлог Се Сяофэй

Использованная литература:

проблема с приоритетом кеша http

Тщательно понимать механизм и принцип кэширования HTTP

Сводная информация об управлении кешем HTTP

Говоря о механизме кеширования браузера http

Простая практика нескольких настроек управления кэшем HTTP через экспресс-фреймворк