Эта интерпретация основана на хроме 66. Заголовок HTTP играет большую роль в протоколе HTTP, который в основном состоит из пар ключ-значение.Например, Content-Type: text/html указывает, что данные отправляются в формате html, а Content-Encoding: gzip указывает, что содержимое должно быть отправлено с использованием сжатия gzip, Transfer-Encoding: chunked указывает, что он используетКодирование групповой передачи,так далее.
Скопируйте запрос из Chromeисходный заголовок запросаКак показано ниже, если вы посетите http://payment-admin.com/list, будет отправлено следующее сообщение запроса:
"GET /list HTTP/1.1\r\nHost: payment-admin.com\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3345.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: en-US,en;q=0.9\r\nIf-None-Match: W/\"68104920-260-\"2018-02-13T14:16:35.000Z\"\"\r\nIf-Modified-Since: Tue, 13 Feb 2018 14:16:35 GMT\r\n\r\n"
Это строка, объединенная в соответствии с форматом сообщения http, как показано на следующем рисунке:
Для каждого запроса Chrome автоматически устанавливает поле UA:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3345.0 Safari/537.36
Поле UA в Chrome пишется так:
Mozilla/5.0 ([os_info]) AppleWebKit/[webkit_major_version].[webkit_minor_version] (KHTML, like Gecko) [chrome_version] Safari/[webkit_major_version].[webkit_minor_version]
Исходный код выглядит следующим образом:
И мы видим, что комментарии в исходном коде также объясняют, почему UA привнес Safari — для того, чтобы отображать название продукта способом, максимально совместимым с Safari.
Текущий запрос получил следующеезаголовок ответа:
"HTTP/1.1 200 OK\0Server: nginx/1.8.0\0Date: Fri, 16 Feb 2018 03:31:51 GMT\0Content-Type: text/html; charset=UTF-8\0Transfer-Encoding: chunked\0Connection: keep-alive\0last-modified: Tue, 13 Feb 2018 14:16:35 GMT\0etag: W/\"68104920-260-\"2018-02-13T14:16:35.000Z\"\"\0cache-control: max-age=10\0Expires: Fri, 16 Feb 2018 03:32:01 GMT\0Content-Encoding: gzip\0\0"
Между этим заголовком запроса и заголовком ответа есть небольшая разница: разделитель между его полями равен \0, а не указанному выше \r\n.
Что касается поля заголовка запроса, мы сосредоточимся на следующих двух проблемах:
(1) Каково ключевое значение кеша, то есть как отличить два разных ресурса, и как организован и управляется браузер кеша?
(2) Как gzip сжимает и распаковывает, и почему объем, сжатый gzip, часто более чем наполовину меньше?
для кэша, Прежде всего, как установить время кеша ресурсов? Если вы используете nginx, вы можете сделать это:
server {
listen 80;
server_name www.rrfed.com;
# .json不要缓存,时间为0
location ~* \.sw.json$ {
expires 0;
}
# 如果是图片的话缓存30天
location ~* \.(jpg|jpeg|png|gif|webp)$ {
expires 30d;
}
# css/js缓存7天
location ~* \.(css|js)$ {
expires 7d;
}
}
Приведенный выше код устанавливает время кеша в соответствии с различными суффиксами имени файла, такими как кеш изображений на 30 дней и кеш js/css на 7 дней.
Если вы используете Node.js и т. д., чтобы добавить его отдельно в запрос, вы можете напрямую добавить заголовок Cache-Control:
// 设置30天=2592000s缓存
response.setHeader("Cache-Control", "max-age=2592000");
Таким образом, браузер получит кешированные заголовки http:
ТакКак браузер различает разные ресурсы для кэширования?Как вы могли догадаться, исходя из URL-адреса, это выглядит так:
Chrome использует функцию, которая генерирует ключ кэша, эта функцияИспользовать запрошенный URL в качестве кэшированного ключа.
Если да, то можно ли кэшировать такие запросы, как POST? На самом деле нет, потому что на него есть суждение, как показано на следующем рисунке:
Этот ShouldPassThrough будет судить о методе запроса:
Если это обычный POST/PUT, он вернет true, то есть такой запрос сразу вернет true, и его нужно пройти, не забирая кеш. Для DELETE и HEAD решение выносится в другом месте:
Если режим НЕТ, запрос будет отправлен. то естьПомимо GET, Chrome принципиально не кэширует другие методы запроса..
После выполнения запроса кэш будет сохранен, проверив точку останова, можно обнаружить, что он размещен по этому пути.
~/Library/Caches/Chromium/Default/Cache/
Как показано ниже:
Файл кеша в этом каталоге представляет собой хеш-значение SHA1 значения ключа (т.е. URL-адреса) в качестве имени файла:
Глядя на этот каталог Cache, вы можете обнаружить, что имя файла состоит из хеш-значения плюс суффикса 0 или 1, а 0/1 — это индекс файла (не обсуждается подробно), как показано на следующем рисунке:
Файл кеша не записывает содержимое файла на жесткий диск, а сериализует содержимое памяти экземпляра Entry, инкапсулированного Chrome, на жесткий диск, что является представлением переменных в памяти. Если вы откроете файл кеша в текстовом редакторе следующим образом:
Его можно напрямую считать в соответствующую переменную.
При этом эта Entry будет помещена в переменную memory entry_set_, которая является unordered_map, то есть обычной хеш-картой, значением ключа является значение sha1 url, а значением value является метаданные, в которых хранится некоторая информация, такая как размер файла, EntrySet Структура данных кода показана ниже:
using EntrySet = std::unordered_map<uint64_t, EntryMetadata>;
Основная функция этого entry_set_ заключается в записи значения ключа кеша, поэтому вместо карты задается его имя. Эта переменная сохранит свой сериализованный формат на диск, называемый индексным файлом index:
~/Library/Caches/Chromium/Default/Cache/index-dir/the-real-index
Когда запустится Chrome, он загрузит этот файл в entry_set_, а при загрузке ресурсов сначала будет искать в этом хэше Map:
Если вы можете найти его, вы можете напрямую загрузить файл жесткого диска без отправки запроса.
После того, как данные будут извлечены, он проверит, истек ли срок действия кеша:
Убедитесь, что срок его действия истекСначала необходимо рассчитать срок действия текущего кеша, как указано в следующем исходном коде:
// From RFC 2616 section 13.2.4:
//
// The max-age directive takes priority over Expires, so if max-age is present
// in a response, the calculation is simply:
//
// freshness_lifetime = max_age_value
//
// Otherwise, if Expires is present in the response, the calculation is:
//
// freshness_lifetime = expires_value - date_value
//
// Note that neither of these calculations is vulnerable to clock skew, since
// all of the information comes from the origin server.
//
// Also, if the response does have a Last-Modified time, the heuristic
// expiration value SHOULD be no more than some fraction of the interval since
// that time. A typical setting of this fraction might be 10%:
//
// freshness_lifetime = (date_value - last_modified_value) * 0.10
//
В сочетании с кодом для реализации логики этот шаг выглядит следующим образом:
(1) Если указан max-age, то периодом действия является время, указанное max-age:
cache-control: max-age=10
cache-control: no-cachecache-control: no-store
(2) Если max-age не указан, но задан expires, то используйте время, указанное в expires, минус текущее время, чтобы получить срок действия:
Expires: Wed, 21 Feb 2018 07:28:00 GMT
Эта дата находится в формате http-date с использованием времени по Гринвичу.
(3) Если max-age и expires отсутствуют, а must-revalidate не указан, в качестве периода действия используется текущее время минус время последнего изменения, умноженное на поправочный коэффициент 0,1:
last-modified: Tue, 13 Feb 2018 08:16:27 GMT
cache-control: max-age=10, must-revalidatecache-control: must-revalidate
Тогда нельзя напрямую использовать кеш, приходится отправлять запрос, и если сервис вернет 304, то использовать кеш.
После получения срока действия он сравнивается с текущим возрастом, если срок действия старше возраста, он считается действительным, в противном случае - недействительным. И этот возраст получается вычитанием времени отклика ресурса из текущего времени и добавлением времени корректировки:
// resident_time = now - response_time;
// current_age = corrected_initial_age + resident_time;
Потому что с учетом таких факторов, как время выполнения запроса, current_age необходимо скорректировать.
Давайте поговорим о кэшировании здесь, давайте обсудим его далееgzip-сжатие.
Сжатие gzip часто может сжать объем файла менее чем наполовину, напримерjquery-3.3.1.min.jsОсталось 85кб, после сжатия gzip осталось 35кб:
Уменьшен объем на 58%. Так как же сжимается gzip? Это вопрос, который меня всегда интересовал.
Вы часто можете видеть имена файлов с суффиксами .tar.gz в linux/mac. .tar означает, что он был введен в пакет tar, а .gz означает, что пакет tar сжат с помощью gzip. Вы можете использовать следующие команды для сжать и распаковать:
# 把html目录打包成一个压缩文件
tar -zcvf html.tar.gz html/
# 解压到当前目录
tar -zxvf html.tar.gz
gzip был стандартизирован дляRFC1952, nginx может включить gzip, добавив следующую конфигурацию:
server {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
# gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css application/x-httpd-php image/jpeg image/gif image/png;
}
Chrome использует стороннийбиблиотека zlibВ качестве библиотеки сжатия и распаковки файл библиотеки, используемый для распаковки,third_party/zlib/contrib/optimizations/inflate.c, этот код выглядит неясным, конкретный процесс может ссылаться на этотОписание дефляцииа такжеВот этот, gzip использует deflate, который сочетает в себе кодировку Хаффмана и сжатие LZ77. Проиллюстрируйте, сжав следующий текст:
"In the beginning God created the heaven and the earth. And the earth was without form, and void."
Сначала выполните сжатие LZ77, чтобы оно стало:
In the beginning God created<25, 5>heaven an<14, 6>earth. A<23, 12> was without form,<55, 5>void.
Среди них представляет , что означает строку "the", 25 - это расстояние, на 25 байт впереди текущей позиции, а затем возьмите длину length = 5, которая является первой " ". Точно так же следующие означают «d the».
Максимальное число, которое может быть представлено байтом с 8 битами, равно 255. Если предположить, что один байт представляет расстояние, а один байт представляет длину, приведенный выше текст изменится с 96 байт без сжатия на 76 байт, а степень сжатия достигнет 80 %. текст Чем он длиннее, тем выше вероятность повторения и выше степень сжатия. Стандарт рекомендует, чтобы максимальная длина блока составляла 32 КБ, то есть повторяющиеся символы начинают считаться после превышения 32 КБ.
Но есть проблема: как отличить пары нормального содержания и длины, представляющие ? Стандарт решается так, значение 0 ~ 255 — обычное содержимое, тогда как 256 указывает конец блока, а 257 ~ 285 указывает длину пары.
Для представления числа 285 требуется как минимум 9 бит, то есть значение может быть прочитано в 9 битах на 9 бит (аналогично сжатию в единицах 9 бит), что может решить проблему, но это будет тратить много места, потому что 9 бит Максимум может представлять 511. Поэтому вводится кодирование переменной длины, кодирование Хаффмана, и хранение данных больше не фиксированной длины (например, каждый байт представляет контент), а переменной длины. , а самым коротким может быть 1 бит для представления символов, длина которых может достигать 9 символов.
Но это может быть неразличимо Например, три символа A, B и C представлены как:
А: 0
Б: 1
С:01
Затем, когда вы встречаете 01, вы не знаете, C это или AB.
Таким образом, кодирование Хаффмана должно решить проблему обеспечения того, чтобы префикс не конфликтовал, как показано на следующем рисунке:
Сначала подсчитайте количество вхождений каждого символа, затем выберите два символа с наименьшим количеством раз каждый раз, чтобы сформировать левый и правый дочерние узлы, и их сумма используется в качестве родительского узла в качестве нового узла, пока все узлы не образуют дерево , Левое поддерево представляет 0, а правое поддерево представляет 1. Путь от корневого узла к конечному узлу является кодом текущего символа.Например, код z равен 001, а код e равен 1, так что коды символов, которые часто появляются, будут сравниваться Короткие, чтобы достичь цели сжатия. В то же время должна быть соответствующая связь кодировки записи таблицы, которую можно искать при распаковке. (Стандарт также оптимизирует этот алгоритм).
Только что упомянул, что диапазон пар длин 257~285, всего 29, этого недостаточно, потому что блок имеет максимальный размер 32кб (в зависимости от степени сжатия), а максимальное количество повторяющихся строк может быть только 29 или только вперед.Если вы найдете 29, то вы не можете достаточно сжать, поэтому стандарт также добавляет дополнительные биты для его увеличения следующим образом:
Например, если длина равна 266, то позже считывается еще один бит 1. Если первый бит равен 0, то длина равна 13, если первый бит равен 1, то длина равна 14 и так далее. За длиной следует расстояние, и расстояние будет обрабатываться аналогичным образом. Мы видим, что длина до 258, а расстояние до 32кб.
Особенностью gzip является то, что сжатие занимает много времени, но его легче распаковать. При сжатии необходимо подсчитывать символы и находить повторяющиеся строки, а при распаковке нужно только проверять таблицу кодирования переменной длины, а затем читать и сравнивать размер значения, чтобы перед выводом увидеть, является ли это парой содержимого или длины. Качество степени сжатия gzip зависит от повторения содержимого: чем выше частота повторения, тем выше степень сжатия.
Интерпретация HTTP в этой статье здесь, в основном о трех содержаниях: заголовок HTTP, кеш HTTP, сжатие Gzip. После прочтения этой статьи вы должны знать, как выглядят заголовок HTTP-запроса и заголовок ответа, как прописан UA Chrome, как организован и управляется браузер HTTP-кэша, как рассчитывается время кэширования и процесс сжатия Gzip. Как оно, почему эффект сжатия у Gzip вообще лучше и тд. Что касается другого интересного содержимого HTTP, мы разберем его в следующий раз.