базовые знания
- Запрашивающая сторона называется клиентской, а отвечающая сторона называется серверной.
- Общение через запросы и ответы
- HTTP — это протокол, который не сохраняет состояние
сообщение запроса
Общие методы:
- ПОЛУЧИТЬ, чтобы получить ресурс
- Сообщение отправляет данные в тело транспортного объекта сервера
- ПОСТАВИТЬ файл передачи
- HEAD получает заголовок сообщения
- УДАЛИТЬ удалить файл
- ВАРИАНТЫ Запросить поддерживаемые методы
- TRACE путь трассировки
ответное сообщение
код состояния
категория | иллюстрировать |
---|---|
1XX | Информационная (информационный код статуса |
2XX | Успех (код состояния успеха) |
3XX | перенаправление |
4XX | Ошибка клиента (код состояния ошибки клиента) |
5XX | Ошибка сервера (статус ошибки сервера) |
2ХХ успехов
- 200 (ОК. Данные, отправленные клиентом, обрабатываются нормально
- 204 (Нормальный ответ Not Content, нет объекта
- 206 (запрос диапазона частичного содержимого, возвращает частичные данные, содержимое объекта, указанное Content-Range в ответном сообщении
3XX редирект
- 301 (перемещено навсегда) постоянное перенаправление
- 302 (найдено) временное перенаправление, спецификация требует, чтобы имя метода осталось неизменным, но это изменится
- 303 (см. Другое) 302 и т.п., но необходимо использовать метод GET
- 304 (Не изменено) Состояние не изменилось с помощью (If-Match, If-- Modified-Since, If-None_Match, If-Range, If-Unmodified-Since)
- 307 (Temporary Redirect) Временное перенаправление, метод запроса менять не следует.
4XX Ошибка клиента
- 400 (Bad Request) Ошибка синтаксиса сообщения запроса
- 401 (неавторизованный) Требуется аутентификация
- 403 (Запрещено) Сервер отказывается обращаться к соответствующему ресурсу
- 404 (Not Found) не удается найти ресурсы на сервере
5XX ошибка на стороне сервера
- 500 (внутренняя ошибка сервера) сбой сервера
- 503 (Служба недоступна) Сервер перегружен или отключен для обслуживания.
headers
Общие поля заголовка
имя поля заголовка | иллюстрировать |
---|---|
Cache-Control | Управление поведением кэша |
Connection | управление ссылками |
Date | дата сообщения |
Pragma | команда сообщения |
Trailer | заголовок в конце сообщения |
Trasfer-Encoding | Указывает метод кодирования передачи тела сообщения. |
Upgrade | Обновление до других протоколов |
Via | информация о прокси-сервере |
Warning | уведомление об ошибке |
поля заголовка запроса
имя поля заголовка | иллюстрировать |
---|---|
Accept | типы мультимедиа, которые может обрабатывать пользовательский агент |
Accept-Charset | Предпочтительный набор символов |
Accept-Encoding | приоритетное кодирование |
Accept-Langulage | предпочтительный язык |
Authorization | Информация о веб-аутентификации |
Expect | Ожидайте определенного поведения от сервера |
From | адрес электронной почты пользователя |
Host | Сервер, на котором находится запрошенный ресурс |
If-Match | Сравните теги сущностей |
If-Modified-Since | Сравните время обновления ресурсов |
If-None-Match | Сравните теги сущностей |
If-Range | Отправить запрос диапазона для байта сущности, когда ресурс не обновлен |
If-Unmodified-Since | Сравните время обновления ресурсов (в отличие от If-Modified-Since) |
Max-Forwards | Максимальное количество переходов передачи |
Proxy-Authorization | Прокси-сервер требует аутентификации клиента |
Range | Запрос диапазона байтов сущности |
Referer | Оригинальный геттер URI в запросе |
TE | Приоритет кодирования передачи |
User-Agent | Информация о клиентской программе HTTP |
поля заголовка ответа
имя поля заголовка | иллюстрировать |
---|---|
Accept-Ranges | принимать ли диапазоны байтов |
Age | когда ресурс был создан |
ETag | информация о сопоставлении ресурсов |
Location | Клиент перенаправляет на указанный URI |
Proxy-Authenticate | Информация об аутентификации прокси-сервера для клиента |
Retry-After | Когда отправлять запрос повторно |
Server | информация о сервере |
Vary | Информация управления, кэшированная прокси-сервером |
www-Authenticate | Аутентификация между сервером и клиентом |
поле заголовка сущности
имя поля заголовка | иллюстрировать |
---|---|
Allow | Методы HTTP, поддерживаемые ресурсом |
Content-Encoding | как закодирована сущность |
Content-Language | Естественный язык сущности |
Content-Length | Размер содержимого объекта (в байтах) |
Content-Location | Подставьте URI соответствующего ресурса |
Content-MD5 | Дайджест сообщения сущности |
Content-Range | диапазон местонахождения сущности |
Content-Type | Тип носителя тела объекта |
Expires | Срок действия сущности |
Last-Modified | Время последнего изменения ресурса |
Лично я думаю, что вам нужно понимать только заголовок запроса и заголовок ответа. Среди них диапазон в заголовке ответа (его можно возобновить с помощью точки останова, о которой будет сказано ниже) и кеш (ETag) — это те знания, которые необходимо освоить.
создать http
- метод первый:
const http = require('http');
const server = http.createServer(function(req,res){
res.end(123);
});
server.listen(8080);
- Способ второй:
const http = require('http');
const server = http.createServer();
// req是请求 是一个可读流 = socket
// res是响应 是一个可写流
server.on('request',function(req,res){
let method = req.method;
let httpVersion = req.httpVersion;
let url = req.url;
let headers = req.headers; // 请求头的名字都是小写的
console.log(method,httpVersion,url,headers);
// 如果数据 大于64k data事件可能会触发多次
let buffers = [];
// 如果没有请求体 不会走on('data'),没有请求体也会触发end事件
req.on('data',function(data){
console.log(1)
buffers.push(data);
});
req.on('end',function(){
console.log(Buffer.concat(buffers).toString());
// socket.write socket.end
res.write('hello');
res.end('world');
});
});
// 监听请求的到来
server.on('connection',function(socket){
console.log('建立连接');
});
server.on('close',function(){
console.log('服务端关闭')
})
server.on('error',function(err){
console.log(err);
});
server.listen(8080);
Просмотрев исходный код, мы обнаружим согласованность двух приведенных выше способов написания:
URL-модуль
Вы можете использовать его для обработки запрошенного URL.Простая демонстрация выглядит следующим образом:
let url = require('url');
let u = 'http://www.baidu.com:80/abc/index.html?a=1&b=2#hash';
// 可以将查询字符串转化成对象
let urlObj = url.parse(u,true);
console.log(urlObj.query); // 查询字符串
console.log(urlObj.pathname); // 路径
res
В последнем содержании предыдущей статьи упоминались некоторые свойства req, так что давайте взглянем на методы res.
write && end
Вы можете использовать метод записи для отправки содержимого ответа
response.write(chunk,[encoding]);
response.end([chunk],[encoding]);
writeHead
res.writeHead(statusCode, [headers]);
setHeader
res.setHeader(key, value);
Разница между ним и writeHead заключается в том, что он фактически не записывает заголовок ответа клиенту.
То есть после writeHeader выполнение setHeader сообщит об ошибке.
заявление
Подключение клиента
// server端
const http = require('http');
const server = http.createServer(function(req,res){
let contentType = req.headers['content-type'];
let buffers = [];
req.on('data',function(chunk){
buffers.push(chunk);
});
req.on('end',function(){
let content = Buffer.concat(buffers).toString();
if(contentType === 'application/json'){
console.log(JSON.parse(content).name)
}else if(contentType === 'application/x-www-form-urlencoded'){
let queryString = require('querystring');
console.log(queryString.parse(content).age)
}
res.end('hello');
});
});
server.listen(4000);
// client
const http = require('http');
const options = {
hostname:'localhost',
port:4000,
path: '/',
method:'get',
// 告诉服务端我当前要给你发什么样的数据
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'Content-Length':5
}
}
const req = http.request(options, function(res) {
res.on('data',function(chunk){
console.log(chunk.toString());
});
});
req.end('age=1');
Следует отметить, что клиентский запрос должен передавать Content-Length, иначе будут проблемы.
Конечно, в реальной работе следующие два пакета npm могут использоваться напрямую (обычно тот, у которого есть промис):
Многоязычный переключатель
Язык браузера можно определить с помощью Accept-Language.
- Формат заголовка запроса Accept-Language: Accept-Language:zh-CN,zh;q=0.9
- Формат заголовка ответа Content-Language:zh-CN
const pack = {
'en': { title: 'english' },
'zh-CN': { title: '中文' }
}
const http = require('http');
const server = http.createServer(function (req, res) {
let lan = 'en';
let language = req.headers['accept-language'];
if (language) {
lan = language.split(',').map(function (item) {
let values = item.split(';');
return {
name: values[0],
q: values[1] ? parseInt(values[1]) : 1
}
}).sort((lang1, lang2) => lang2.q - lang1.q).shift().name;
console.log(lan)
}
res.end(pack[lan] ? pack[lan].title : pack['en'].title);
}).listen(4000);
Картинка противоугонная цепочка
Это космическое изображение QQ относительно распространено, и оно станет разделенным изображением после того, как его цитировали в прошлом.
Принцип реализации:
- При переходе с веб-сайта или ссылке на веб-страницу в файл ресурсов HTTP-запрос содержит Referer для указания URL-адреса исходной веб-страницы.
- Определите доменное имя исходной страницы, проверив Referer в заголовке запроса.
- Если исходного доменного имени нет в белом списке, будет возвращено сообщение об ошибке. Нет реферера для прямого доступа к адресу изображения с помощью браузера.
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const root = path.join(__dirname, 'public');
function removePort(host) {
return host.split(':')[0]
}
function getHostName(urlAddr) {
const { host } = url.parse(urlAddr);
return removePort(host);
}
function request(req, res) {
let refer = req.headers['referer'] || req.headers['referrer'];
if (refer) {
let referHost = getHostName(refer);
let host = removePort(req.headers['host']);
if (referHost != host) {
sendForbidden(req, res);
} else {
serve(req, res);
}
} else {
serve(req, res);
}
}
function serve(req, res) {
let {
pathname
} = url.parse(req.url);
let filepath = path.join(root, pathname);
console.log(req.url, filepath);
fs.stat(filepath, (err, stat) => {
if (err) {
res.end('Not Found');
} else {
fs.createReadStream(filepath).pipe(res);
}
});
}
function sendForbidden(req, res) {
res.end('防盗链');
}
const server = http.createServer();
server.on('request', request);
server.listen(8080);
прокси-сервер
Прямой прокси и обратный прокси [Обзор]
let httpProxy = require('http-proxy');
let http = require('http');
let proxy = httpProxy.createProxyServer();
http.createServer(function (req, res) {
proxy.web(req, res, {
target: 'http://localhost:8000'
});
proxy.on('error', function (err) {
console.log('出错了');
res.end(err.toString());
});
}).listen(8080);
Приведенный выше код представляет собой запросlocalhost:8080
при пересылкеhttp://localhost:8000
. Модули запроса пересылки, такие как webpack-dev-server:http-proxy-middleware, используетсяhttp-proxy
.
веб хостинг
Через Host несколько веб-сайтов используют один порт, а несколько веб-сайтов используют один сервер.
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();
let hosts = {
'www.test1.com': 'http://localhost:8000',
'www.test2.com': 'http://localhost:9000'
}
http.createServer(function (req, res) {
let host = req.headers['host'];
host = host.split(':')[0];
let target = hosts[host];
proxy.web(req, res, {
target
});
}).listen(80);
Range
Когда пользователь слушает песню, если слышна половина песни (сеть загружена наполовину), сеть отключена, и пользователю необходимо продолжить прослушивание, если файловый сервер не поддерживает точки останова, пользователю необходимо скачайте файл еще раз. Если Range поддерживает его, клиент должен записать диапазон файлов, который был прочитан ранее.После восстановления сети он отправит запрос на сервер для чтения оставшегося Range.Серверу нужно отправить только часть запрошенного контента клиентом, а не весь файл, отправленный обратно клиенту, что экономит пропускную способность сети.
Запрос диапазона для получения частичного контента
Чтобы реализовать требование прерванной и возобновленной загрузки, необходимо иметь возможность загружать диапазон объектов указанной загрузки.
- Диапазон в заголовке запроса для указания диапазона байтов ресурса
- Ответ вернет ответ ответ с кодом состояния 206
- Для запроса диапазона с несколькими диапазонами в ответе будут указаны multipart/byteranges в поле заголовка Content-Type.
Как подать заявку
// 服务端
const http = require('http');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const stat = promisify(fs.stat);
// 客户端要发一个头Range:bytes=0-10
// 服务端返回一个头
// Accept-Ranges:bytes
// Content-Range:0-10/总大小
const server = http.createServer(async function (req, res) {
let p = path.join(__dirname, 'content.txt');
// 判断当前文件的大小
let statObj = await stat(p);
let start = 0;
let end = statObj.size - 1; // 读流是包前又包后的
let total = end
let range = req.headers['range'];
if (range) {
// 告诉它支持范围请求
res.setHeader('Accept-Ranges','bytes');
// ['匹配的字符串','第一个分组']
let result = range.match(/bytes=(\d*)-(\d*)/);
start = result[1]?parseInt(result[1]):start;
end = result[2]?parseInt(result[2])-1:end;
// 获取成功并且文件总大小是多少
res.setHeader('Content-Range',`${start}-${end}/${total}`)
}
res.setHeader('Content-Type', 'text/plain;charset=utf8');
fs.createReadStream(p, { start, end }).pipe(res);
});
server.listen(3000);
// 客户端
const options = {
hostname: 'localhost',
port: 3000,
path: '/',
method: 'GET'
}
const fs = require('fs');
const path = require('path');
const http = require('http');
const ws = fs.createWriteStream('./download.txt');
let pause = false;
let start = 0;
// 监听键盘事件,如果有输入p,则暂停
process.stdin.on('data', function (chunk) {
chunk = chunk.toString();
if (chunk.includes('p')) {
pause = true
} else {
pause = false;
download();
}
});
function download() {
options.headers = {
Range: `bytes=${start}-${start + 10}`
}
start += 10;
// 发请求
// 0-10
http.get(options, function (res) {
let range = res.headers['content-range'];
let total = range.split('/')[1];
let buffers = [];
res.on('data', function (chunk) {
buffers.push(chunk);
});
res.on('end', function () {
//将获取的数据写入到文件中
ws.write(Buffer.concat(buffers));
setTimeout(function () {
if (pause === false && start < total) {
download();
}
}, 1000)
})
})
}
download();
компрессия
узлапочтовый файл
const fs = require('fs');
const path = require('path')
const zlib = require('zlib');
// 压缩
function zip(src){
// 压缩流 转化流
const gzip = zlib.createGzip();
fs.createReadStream(src).pipe(gzip).pipe(fs.createWriteStream(src+'.gz'))
}
// zip(path.join(__dirname,'./1.txt'));
// 解压
function unzip(src){
const gunzip = zlib.createGunzip();
fs.createReadStream(src)
.pipe(gunzip)
.pipe(fs.createWriteStream(path.basename(src,'.gz')));
}
// unzip(path.join(__dirname,'./1.txt.gz'));
В HTTP мы можем судить о том, следует ли сжимать передаваемый контент, по заголовку запроса.
const http = require('http');
const path = require('path');
const fs = require('fs');
const zlib = require('zlib');
http.createServer(function (req, res) {
const p = path.join(__dirname, '1.txt');
// Accept-Encoding: gzip, deflate, br 客户端
const header = req.headers['accept-encoding'];
res.setHeader('Content-Type','text/html;charset=utf8');
if (header) {
if (header.match(/\bgzip\b/)) {
const gzip = zlib.createGzip();
res.setHeader('Content-Encoding','gzip');
fs.createReadStream(p).pipe(gzip).pipe(res);
} else if (header.match(/\bdeflate\b/)) {
const deflate = zlib.createDeflate();
res.setHeader('Content-Encoding','deflate')
fs.createReadStream(p).pipe(deflate).pipe(res);
}else{
fs.createReadStream(p).pipe(res);
}
}else{
fs.createReadStream(p).pipe(res);
}
}).listen(8080);
шифрование
crypto
crypto — это модуль, который реализует шифрование и дешифрование в node.js.В node.js библиотека классов OpenSSL используется в качестве внутреннего средства шифрования и дешифрования.OpenSSL — это тщательно протестированный и надежный инструмент реализации алгоритмов шифрования и дешифрования.
Хэш (хэш) алгоритм
Алгоритм хэширования, также называемый хеш-алгоритмом, используется для преобразования ввода любой длины в вывод фиксированной длины.Обычными являются md5, sha1 и т. д. Он имеет следующие характеристики:
- Тот же вход дает тот же результат
- Разные результаты дают разные результаты
- Произвольная длина ввода длина вывода одинакова
- Значение ввода не может быть выведено из вывода
Пример
var crypto = require('crypto');
var md5 = crypto.createHash('md5');//返回哈希算法
var md5Sum = md5.update('hello');//指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
var result = md5Sum.digest('hex');//摘要输出,在使用digest方法之后不能再向hash对象追加摘要内容。
console.log(result);
несколько обновлений
const fs = require('fs');
const shasum = crypto.createHash('sha1');//返回sha1哈希算法
const rs = fs.createReadStream('./readme.txt');
rs.on('data', function (data) {
shasum.update(data);//指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
});
rs.on('end', function () {
const result = shasum.digest('hex');//摘要输出,在使用digest方法之后不能再向hash对象追加摘要内容。
console.log(result);
})
Алгоритм HMAC
Алгоритм HMAC сочетает в себе алгоритм хэширования с секретным ключом для предотвращения нарушения целостности подписи.
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const m = crypto.createHmac('sha1', fs.readFileSync(path.join(__dirname,'./content.txt'))); //将content文件中的内容作为一个密钥
m.update('ok');
console.log(m.digest('hex'));
Соответствующее шифрование
Алгоритм Blowfish является симметричным алгоритмом шифрования, что означает, что для шифрования и дешифрования используется один и тот же ключ.
Метод генерации закрытого ключа:
openssl genrsa -out rsa_private.key 1024
const crypto = require('crypto');
const fs = require('fs');
let str = 'hello';
const cipher = crypto.createCipher('blowfish', fs.readFileSync(path.join(__dirname, 'rsa_private.key')));
let encry = cipher.update(str, 'utf8','hex');
encry += cipher.final('hex');
console.log(encry);
// 对称解密
const deciper = crypto.createDecipher('blowfish', fs.readFileSync(path.join(__dirname, 'rsa_private.key')));
let deEncry = deciper.update(encry, 'hex','utf8');
deEncry += deciper.final('utf8');
console.log(deEncry);
Асимметричное шифрование
- Алгоритм асимметричного шифрования требует два ключа: открытый ключ и закрытый ключ.
- Открытый ключ и закрытый ключ представляют собой пару. Если данные зашифрованы с помощью открытого ключа, для их расшифровки можно использовать только соответствующий закрытый ключ. Если закрытый ключ зашифрован, расшифровать можно только открытый ключ.
- Поскольку для шифрования и дешифрования используются два разных ключа, этот алгоритм называется алгоритмом асимметричного шифрования.
Создать открытый ключ для закрытого ключа
openssl rsa -in rsa_private.key -pubout -out rsa_public.key
const crypto = require('crypto');
const fs = require('fs');
let key = fs.readFileSync(path.join(__dirname, 'rsa_private.key'));
let cert = fs.readFileSync(path.join(__dirname, 'rsa_public.key'));
let secret = crypto.publicEncrypt(cert, buffer);//公钥加密
let result = crypto.privateDecrypt(key, secret);//私钥解密
console.log(result.toString());
подписать
В сети владелец закрытого ключа может подписать фрагмент данных перед его отправкой для получения подписи. После отправки данных получателю данных по сети получатель данных может проверить подпись с помощью открытого ключа. гарантировать, что эти данные являются исходными данными, отправленными владельцем закрытого ключа, и не были изменены во время передачи в сети.
let private = fs.readFileSync(path.join(__dirname, 'rsa_private.key'), 'ascii');
let public = fs.readFileSync(path.join(__dirname, 'rsa_public.key'), 'ascii');
let str = 'hello';
let sign = crypto.createSign('RSA-SHA256');
sign.update(str);
let signed = sign.sign(private, 'hex');
let verify = crypto.createVerify('RSA-SHA256');
verify.update(str);
let verifyResult = verify.verify(public,signed,'hex'); //true
тайник
Классификация кэша
Принудительно кэшировать
Принудительное кэширование, если кешированные данные не являются недействительными, кешированные данные можно использовать напрямую, так как же браузер определяет, являются ли кешированные данные недействительными? Мы знаем, что при отсутствии кэшированных данных, когда браузер запрашивает данные с сервера, сервер возвращает данные вместе с правилами кэширования, а информация о правилах кэширования включается в заголовок ответа.
// 当访问 localhost:8080/a.js
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const server = http.createServer(function(req,res){
let {pathname} = url.parse(req.url);
console.log(pathname);
let p = path.join(__dirname,'public','.'+pathname);
// 这里可能会存在问题,p有可能是个目录
fs.stat(p,function(err,stat){
if(!err){
sendFile(req,res,p);
}else{
sendError(res);
}
})
});
function sendError(res){
res.statusCode = 404;
res.end();
}
function sendFile(req,res,p){
let date = new Date(Date.now()+10*1000);
// res.setHeader('Expires',date.toUTCString());
res.setHeader('Cache-Control','max-age=10');
res.setHeader('Content-Type',mime.getType(p)+';charset=utf8')
fs.createReadStream(p).pipe(res);
}
server.listen(8080);
Обычно существует два типа результатов кэширования: кэш памяти и дисковый кэш. Первый читается из памяти, второй читается с диска, в отличие от последнего займет немного времени.
Проведя некоторые исследования, я пришел к выводу, что:
Simple Test: Open Chrome Developper Tools / Network. Reload a page multiple times. The table column "Size" will tell you that some files are loaded "from memory cache". Now close the browser, open Developper Tools / Network again and load that page again. All cached files are loaded "from disk cache" now, because your memory cache is empty.
такой жеэта статьяВывод: В случае сильного попадания в кеш первая отрисовка процесса будет считывать ресурсы кеша с диска. Chrome сохранит некоторые ресурсы в памяти
Сравнить кеш
- Кэш сравнения, как следует из названия, необходимо сравнивать, чтобы определить, можно ли использовать кеш.
- Когда браузер запрашивает данные в первый раз, сервер возвращает идентификатор кеша и данные клиенту, а клиент создает их резервную копию в базе данных кеша.
- При повторном запросе данных клиент отправляет резервный идентификатор кеша на сервер, и сервер выносит решение на основе идентификатора кеша. После успешного решения он возвращает код состояния 304, чтобы уведомить клиента об успешном сравнении. и кэшированные данные могут быть использованы.
Сравните по времени последнего изменения:
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const server = http.createServer(function (req, res) {
let { pathname } = url.parse(req.url);
let p = path.join(__dirname, 'public', '.' + pathname);
fs.stat(p, function (err, stat) {
// 根据修改时间判断
// if-modified-since Last-Modified
if (!err) {
let since = req.headers['if-modified-since'];
if(since){
if(since === stat.ctime.toUTCString()){
res.statusCode = 304;
res.end();
}else{
sendFile(req,res,p,stat);
}
}else{
sendFile(req,res,p,stat);
}
} else {
sendError(res);
}
})
});
function sendError(res) {
res.statusCode = 404;
res.end();
}
function sendFile(req, res, p,stat) {
res.setHeader('Cache-Control','no-cache')
res.setHeader('Last-Modified',stat.ctime.toUTCString());
res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
fs.createReadStream(p).pipe(res);
}
server.listen(8080);
Последнее решение времени модификации не очень надежно, оно имеет следующие проблемы:
- Некоторые серверы не могут точно получить время последнего изменения файла, поэтому невозможно определить, был ли файл обновлен по времени последнего изменения.
- Некоторые файлы изменяются очень часто и изменяются за доли секунды. Last-Modified может быть точным только до секунды.
- Изменилось время последней модификации некоторых файлов, но содержимое не изменилось. Мы не хотим, чтобы клиенты думали, что этот файл был изменен.
- Если один и тот же файл находится на нескольких CDN-серверах, несмотря на то, что содержимое одинаковое, время модификации разное.
В связи с этим мы можем использовать схему 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');
// 根据的是最新修改时间 这回根据的是文件的内容
// ETag:md5加密 / if-none-match
const server = http.createServer(function (req, res) {
let { pathname } = url.parse(req.url);
let p = path.join(__dirname, 'public', '.' + pathname);
fs.stat(p, function (err, stat) {
let md5 = crypto.createHash('md5');
let rs = fs.createReadStream(p);
rs.on('data',function(data){
md5.update(data);
});
rs.on('end',function(){
let r = md5.digest('hex'); // 当前文件的唯一标识
// 下次再拿最新文件的加密值 和客户端请求来比较
let ifNoneMatch = req.headers['if-none-match'];
if(ifNoneMatch){
if(ifNoneMatch === r){
res.statusCode = 304;
res.end();
}else{
sendFile(req,res,p,r);
}
}else{
sendFile(req,res,p,r);
}
});
})
});
function sendError(res) {
res.statusCode = 404;
res.end();
}
function sendFile(req, res, p,r) {
res.setHeader('Cache-Control','no-cache')
res.setHeader('Etag',r);
res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
fs.createReadStream(p).pipe(res);
}
server.listen(8080);
Конечно, если файл относительно большой, например 1G, производительность будет относительно низкой при каждом выполнении этой операции, поэтому вы также можете рассмотреть для сравнения stat.ctime+stat.size или сложить две предыдущие схемы вместе.
процесс запроса
Первый запрос:
Второй запрос:
Cache-Control
- частные клиенты могут кэшировать
- И общедоступные клиенты, и прокси-серверы могут кэшировать
- max-age=60 содержимое кеша истечет через 60 секунд
- no-cache должен использовать кеш сравнения для проверки данных, снова принудительно выполняя проверку на исходном сервере.
- no-store Весь контент не будет кэшироваться, и ни принудительный кеш, ни кеш сравнения не будут активированы
Скаффолдинг для реализации статического сервера
В соответствии с приведенным выше кодом можно создать простую версию. Вероятно, это следующие части:
- Определите, является ли URL-адрес папкой или файлом. Если это файл, перейдите к следующему шагу. Если это папка, отобразите список.
- Определить, был ли файл закэширован, если есть кеш, прочитать кэшированное содержимое напрямую, если нет (необходимо установить заголовок, например, etag и т. д.), то перейти к следующему шагу
- сжатый файл gzip
- Функции, поддерживающие диапазон
В процессе написания лесов, есть два инструмента, которые необходимо упомянуть.
Вышеупомянутый вариант — это некоторые варианты создания параметров и справки, которые могут помочь пользователям быстро понять, что делает этот каркас, а следующий — интерактивная командная строка вопросов и ответов.
последний из последних
Чтобы рекламировать мой блог, пожалуйста, посетите:передняя часть винглета