В этой истории я собираюсь повторно использовать пример, который я написал для Как кодировать ответ Node.js с нуля, чтение этой истории полезно только в том случае, если вы хотите научиться кодировать свои ответы с нуля без сторонних модулей. , Я использую его повторно, потому что хочу использовать заголовок Content-Encoding
из ответов.
Кеширование браузера идет прямо из коробки, сервер должен отправить правильную информацию о кешировании в заголовке ответа, чтобы браузеры могли кэшировать файлы на своей стороне.
В этом примере я буду использовать заголовки Cache-Control
, Vary
, If-None-Match
и ETag
.
Cache-Control
Содержит инструкции по кэшированию, его значение представляет собой список директив, разделенных запятыми; этот заголовок можно использовать в запросах и ответах, в этом примере он будет использоваться только в ответе.Vary
указывает, какие заголовки использовать для кэширования на стороне клиента, если ответ соответствует значениям из уже сохраненного кеша, он будет использовать файлы кеша вместо отправки запроса; этот заголовок предоставляется ответом.ETag
Содержит значение, однозначно определяющее тело / файл ответа; этот заголовок указан в ответе.If-None-Match
Содержит значение ETag кэшированного файла для конкретного запроса; заголовок указан в запросе.
Теперь, когда мы знаем, какие заголовки вызывают кеширование, давайте перейдем к коду.
В фрагменте ниже я покажу, как включаю кеширование для всех файлов, запрошенных по пути images/
, и просто чтобы освежить память, наш HTML-сайт читает с этого пути только тег <img>
, как показано ниже <img id=”ramboImg” alt=”Rambo Gif” src=”images/rambo.gif”/>
В http.createServer()
первое, что я делаю, - это определяю, исходит ли запрос от image/
, используя startsWith
в URL-адресе запроса, а затем в качестве дополнительной проверки я хочу принимать только запросы get
и head
для этого URL:
if (request.url.startsWith('/images/') && /(get|head)/.test(request.method.toLowerCase())) {
Затем, поскольку наши внутренние изображения не находятся на том же сервере, где запущен Node.js, мы должны исправить путь к нашему FTP-серверу, на котором находятся изображения:
let filePath = `uploads${request.url}`
Затем мы устанавливаем набор значений заголовка с информацией, которую мы уже знаем, мы устанавливаем значение Content-Type
, полученное из самого запроса, используя вспомогательную функцию, которая возвращает правильное Content-Type
для каждого расширения изображения, затем мы устанавливаем Cache-Control
на истечение через 1 год и чтобы всегда проверять содержимое с сервером, указывая no-cache
, наконец, мы устанавливаем заголовок Vary
для использования ETag
и Content-Encoding
в качестве заголовков, которые система кэширования должна использовать для сопоставления кэшированных файлов на стороне клиента, важно включить Content-Encoding
, потому что файл может содержат одинаковые ETag
с разными кодировками или вообще без кодировки:
response.setHeader('Content-Type', getContentType(filePath)) response.setHeader('Cache-Control', `max-age=31536000, no-cache`) response.setHeader('Vary', 'ETag, Content-Encoding')
Затем мы проверяем, содержит ли запрос if-none-match
заголовок, который может содержать значение ETag
из более ранних ответов сервера, мы собираемся использовать это позже, чтобы сравнить его с недавно вычисленным ETag
.
let ifNoneMatchValue = request.headers['if-none-match']
После того, как приложение успешно установило FTP-соединение, я получаю дату последнего изменения, хэширую ее с помощью SHA1 и отправляю в качестве значения ответа ETag
, в реальных приложениях я бы кэшировал вычисленную контрольную сумму байтов реального файла и сохранял ее в база данных для более быстрого поиска вместо того, чтобы рассчитывать ее для каждого запроса:
let lastMod = await ftpClient.lastMod(filePath) let lastModHash = getSHA1(lastMod.toString()) response.setHeader('ETag', lastModHash)
Затем я сравниваю предоставленное запросом значение ETag из if-non-match и сравниваю его с вновь вычисленным ETag, если они совпадают, мы устанавливаем код состояния ответа на 304 и завершаем ответ, не отправляя больше байтов:
if (ifNoneMatchValue && ifNoneMatchValue === lastModHash) { response.statusCode = 304 body.end() }
Если запрос является запросом HEAD, не отправляйте байты независимо от того:
if (request.method.toLowerCase() === 'head') { body.end() }
Если запрос не заканчивается ни в одном из двух вышеуказанных условий, мы должны отправить фактические байты изображения, я делаю это с помощью клиентского модуля ftp:
await ftpClient.downloadTo(body, filePath)
Вы можете использовать браузер Chrome, чтобы убедиться, что кеширование работает правильно, в Chrome выберите View ›Developer› Developer Tools.
Инструменты разработчика могут открываться справа или внизу, мне нравится внизу, потому что в нем больше места для просмотра деталей, поэтому я всегда помещаю его туда:
Далее Щелкните вкладку сети:
На изображении выше не отображается никакой информации, потому что панель была закрыта при загрузке страницы, вы можете нажать CMD + R, чтобы перезагрузить сайт, или щелкнуть адресную строку и нажать Enter, чтобы отобразить трафик, и для этого примера сфокусируйтесь на rambo.gif запрос выделен ниже:
Как вы можете видеть, размер rambo.gif составляет 1,5 МБ, а для загрузки изображения потребовалось 360 мс, я сделал принудительное обновление (CMD + Shift + R) на Chrome, чтобы заставить Chrome вытащить все ресурсы с сервера вместо кеша для приведенный выше снимок экрана.
При следующем посещении сайта или при обновлении сайта (CMD + R) вы увидите, что размер запроса rambo.gif составляет всего 260 Б, а для его выполнения потребовалось 74 мс, что меньше, чем значок избранного! ха-ха; но он все равно немного медленнее, чем favicon.ico, потому что сервер проверяет ETag, отправленный запросом.
Если вы нажмете на rambo.gif, он покажет вам заголовки запроса и ответа:
В общем разделе вы можете увидеть, что сервер вернул 304 Not Modified.
Проверьте полный исходный код ниже
Хорошо, на сегодня все, статья, которая мне очень помогла в понимании кеширования, находится здесь, я рекомендую проверить ее, а также Документы Mozilla.
Se y’all later ✌️