Я все еще нахожусь в процессе изучения узла. Недавно я узнал о знаниях, связанных с http. Конечно, я поделюсь ими с вами, как только узнаю. Сегодня я научу вас, как использовать модуль http в узле для реализовать различные стратегии кэширования. ! ! !
Все мы знаем, что для нашей фронтенд-разработки кэширование — очень важная вещь, то есть мы надеемся, что пользователи не смогут повторно загружать содержимое нашей страницы каждый раз, когда они запрашивают, надеемся сэкономить трафик для пользователей и улучшить плавный просмотр нашего веб-сайта. страниц. Но в то же время, когда мы исправляем ошибку, мы надеемся, что онлайн может быть обновлен вовремя. В это время мы просим нашего дедушку сообщить нашей бабушке и попросить брата по эксплуатации и обслуживанию обновить кеш для нас. .Есть ли для нас лучшая стратегия кэширования?Мы можем изменять ошибки без хлопотной эксплуатации и обслуживания, чтобы своевременно обновляться.Сегодня мы будем использовать узел, чтобы увидеть, как установлена политика кэширования в бэкэнде.
Принудительно кэшировать
Обычно наша настройка для принудительного кэширования заключается в том, что сервер сообщает клиенту, что вы только что запросили один раз.Мы согласны, что вы будете напрямую читать кеш, если вы вернетесь и запросите в течение десяти минут, что означает, что когда клиент запрашивает несколько раз в течение десяти минут Если вы запросите, содержимое страницы будет загружено только в первый раз, а другие запросы будут идти непосредственно в кеш.Независимо от того, изменится ли наша страница в течение этого периода, это не повлияет на кеш чтения клиента. Тогда давайте посмотрим на реализацию кода
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
// 创建一个服务
let server = http.createServer();
// 监听请求
server.on('request',(req,res)=>{
// 获取到请求的路径
let {pathname,query} = url.parse(req.url,true);
// 将路径拼接成服务器上对应得文件路径
let readPath = path.join(__dirname, 'public',pathname);
console.log(readPath)
try {
// 获取路径状态
let statObj = fs.statSync(readPath);
// 服务端设置响应头 Cache-Control 也就是缓存多久以秒为单位
res.setHeader('Cache-Control','max-age=10');
// 服务器设置响应头Expires 过期时间 获取当前时间加上刚刚设置的缓存秒数
res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString());
//判断如果路径是一件文件夹 就默认查找该文件下的index.html
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
console.log(p);
// 判断是否有index.html 没有就返回404
fs.statSync(p);
// 创建文件可读流 并且pipe到响应res可写流中
fs.createReadStream(p).pipe(res)
}else{
// 如果请求的就是一个文件 那么久直接返回
fs.createReadStream(readPath).pipe(res)
}
} catch (error) {
// 读取不到 返回404
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未发现文件`)
}
})
// 监听3000端口
server.listen(3000)
С помощью приведенного выше теста кода мы обнаружим, что когда мы запрашиваем тот же файл в течение 10 секунд, наш браузер переходит непосредственно к кешу.На приведенном выше рисунке мы видим, что когда мы повторяем запрос, мы видим, что css становится из кеша памяти, мы также видим, что наш только заголовок ответа также установлен
Согласовать кеш
Мы обнаружили, что приведенный выше принудительный кеш является одной из причин, по которой нам обычно приходится ждать, пока ошибка не выйдет в сеть. Есть ли какой-либо другой хороший метод обработки кеша? Давайте представим, можем ли мы узнать, был ли изменен наш файл, если мы изменим сервер и вернуть последний контент. Если нет изменений, он всегда будет кэшироваться по умолчанию. Звучит здорово! Затем мы думаем, что если мы сможем узнать время последней модификации файла, это может быть достигнуто!
Кэшировать по времени последнего изменения файла
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let server = http.createServer();
server.on('request',(req,res)=>{
// 获取到请求的路径
let {pathname,query} = url.parse(req.url,true);
// 将路径拼接成服务器上对应得文件路径
let readPath = path.join(__dirname, 'public',pathname);
try {
// 获取路径状态
let statObj = fs.statSync(readPath);
// 为了方便测试 我们告诉客户端不要走强制缓存了
res.setHeader('Cache-Control','no-cache');
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
let statObj = fs.statSync(p);
// 我们通过获取到文件状态来拿到文件的最后修改时间 也就是ctime 我们把这个时间通过响应头Last-Modified来告诉客户端,客户端再下一次请求的时候会通过请求头If-Modified-Since把这个值带给服务端,我们只要判断这两个值是否相等,假如相等那么也就是说 文件没有被修改那么我们就告诉客户端304 你直接读缓存吧
res.setHeader('Last-Modified',statObj.ctime.toGMTString());
if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
res.statusCode = 304;
res.end();
return
}
// 修改了那么我们就直接返回新的内容
fs.createReadStream(p).pipe(res)
}else{
res.setHeader('Last-Modified',statObj.ctime.toGMTString());
if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
res.statusCode = 304;
res.end();
return
}
fs.createReadStream(readPath).pipe(res)
}
} catch (error) {
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未发现文件`)
}
})
server.listen(3000)
Мы можем видеть через запрос, что когда мы делаем первый запрос, независимо от того, как мы обновляем запрос, это кеш, который напрямую читается 304. Если мы изменим этот файл на стороне сервера, мы сможем увидеть и запросить последний 1. Содержание, это то, с чем мы имеем дело, согласовывая кеш. Мы получаем время последней модификации файла, получая статус файла, то есть ctime. Мы сообщаем клиенту это время через заголовок ответа Last-Modified, а клиент попробует снова в следующий раз.При запросе это значение будет доведено до сервера через заголовок запроса If-Modified-Since.Нам нужно только судить, равны ли два значения.Если они равны, это означает, что файл не был изменен, то мы сообщим клиенту 304, что вы его прочитали напрямую.
Кэшировать по содержимому файла
Снова и снова, если мы удаляем символ a в файле, а затем восстанавливаем его, то время модификации сохранения нашего файла фактически изменилось, но на самом деле реальное содержание нашего файла не изменилось, поэтому в это время Фактически, также возможно, что клиент продолжит использовать кеш.Давайте посмотрим, как реализуется такая стратегия кеширования.
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let crypto = require('crypto');
let server = http.createServer();
server.on('request',(req,res)=>{
// 获取到请求的路径
let {pathname,query} = url.parse(req.url,true);
// 将路径拼接成服务器上对应得文件路径
let readPath = path.join(__dirname, 'public',pathname);
try {
// 获取路径状态
let statObj = fs.statSync(readPath);
// 为了方便测试 我们告诉客户端不要走强制缓存了
res.setHeader('Cache-Control','no-cache');
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
let statObj = fs.statSync(p);
// 我们通过流把文件读取出来 然后对读取问来的内容进行md5加密 得到一个base64加密hash值
let rs = fs.createReadStream(p);
let md5 = crypto.createHash('md5');
let arr = [];
rs.on('data',(data)=>{
arr.push(data);
md5.update(data);
})
rs.on('end',(data)=>{
let r = md5.digest('base64');
// 然后我们将这个hash值通过响应头Etag传给客户端,客户端再下一次请求的时候会把上一次的Etag值通过请求头if-none-match带过来,然后我们就可以继续比对文件生成的hash值和上次产生的hash是否一样 如果一样说明文件内容没有发生变化 就告诉客户端304 读取缓存
res.setHeader('Etag',r);
if(req.headers['if-none-match']===r){
res.statusCode=304;
res.end();
return;
}
res.end(Buffer.concat(arr))
})
}else{
let rs = fs.createReadStream(readPath);
let md5 = crypto.createHash('md5');
let arr = [];
rs.on('data',(data)=>{
arr.push(data);
md5.update(data);
})
rs.on('end',(data)=>{
let r = md5.digest('base64');
res.setHeader('Etag',r);
if(req.headers['if-none-match']===r){
res.statusCode=304;
res.end();
return;
}
res.end(Buffer.concat(arr))
})
}
} catch (error) {
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未发现文件`)
}
})
server.listen(3000)
Через консоль мы видим, что заголовок запроса и заголовок ответа имеют соответствующие значения, о которых мы упоминали выше, но мы также можем видеть из кода, что мы будем читать все файлы каждый раз, когда приходит запрос и Шифрование производственного хэша а затем сравнение его на самом деле очень требовательно к производительности, поэтому у этого метода кэширования также есть свои недостатки.
Суммировать
Мы лично реализовали три метода кэширования через узел, и мы можем обобщить соответствующую реализацию каждого метода кэширования:
- Принудительное кэширование Сервер устанавливает заголовок ответа Cache-Control:max-age=xxx и устанавливает время истечения срока действия заголовка ответа Expires, а клиент сам определяет, читать ли кэш
- Согласовать кеш Скажите клиенту перейти к кешу через код состояния 304
- Время модификации: судите, следует ли читать кеш или нет, основываясь на времени последнего изменения файла, сервер устанавливает заголовок ответа Last-Modified, а клиент передает значение Last-modified в последнем заголовке ответа сервера службе. через if-modified-since Сервер сравнивает время модификации текущего файла с временем последней модификации (значение, переданное клиенту в последний раз), если они равны, то это означает, что время модификации файла не изменилось.
- Содержимое файла: Содержимое файла используется для определения необходимости чтения кэша.Сервер считывает содержимое файла и шифрует его с помощью base64 через md5 для получения хеш-значения.Установите это значение в Etag заголовка ответа, а следующий запрос клиента будет пройден. Если нет совпадения, сервер сравнивает значение хеш-функции, полученное путем шифрования текущего содержимого файла, такое же, как и в прошлый раз, если то же самое означает, что содержимое файла не изменилось, этот метод самый точный метод, но это и самое дорогое исполнение