Заголовки HTTP важны для контроля, как кэш и браузеры обрабатывают ваш контент. Но многие из них используются неправильно или бессмысленно, затрачивая лишние ресурсы в критический момент загрузки страницы, и они могут работать не так, как вы думаете. В серии статей о лучших практиках сначала рассмотрим ненужные заголовки.
Большинство разработчиков знают о важных и нужных HTTP-заголовках. Самые известные — Content-Type
и Content-Length
, это почти универсальные хедеры. Но в последнее время для повышения безопасности начали использоваться заголовки вроде Content-Security-Policy
и Strict-Transport-Security
, а для повышения производительности — Link rel=preload
. Несмотря на широкую поддержку в браузерах, лишь немногие их используют.
В то же время есть много чрезвычайно популярных заголовков, которые вообще не новые и не очень полезные. Мы можем это доказать с помощью HTTP Archive, проекта под управлением Google и спонсируемого Fastly, который каждый месяц при помощи WebPageTest скачивает 500 000 сайтов и выкладывает результаты в BigQuery.
Вот 30 самых популярных заголовков ответов по данным HTTP Archive (на основе количества доменов в архиве, которые выдают каждый заголовок), и примерная оценка полезности каждого из них:
Заголовок | Запросов | Доменов | Статус |
---|---|---|---|
date | 48779277 | 535621 | Требуется по протоколу |
content-type | 47185627 | 533636 | Обычно требуется браузером |
server | 43057807 | 519663 | Необязателен |
content-length | 42388435 | 519118 | Полезен |
last-modified | 34424562 | 480294 | Полезен |
cache-control | 36490878 | 412943 | Полезен |
etag | 23620444 | 412370 | Полезен |
content-encoding | 16194121 | 409159 | Требуется для сжатого контента |
expires | 29869228 | 360311 | Необязателен |
x-powered-by | 4883204 | 211409 | Необязателен |
pragma | 7641647 | 188784 | Необязателен |
x-frame-options | 3670032 | 105846 | Необязателен |
access-control-allow-origin | 11335681 | 103596 | Полезен |
x-content-type-options | 11071560 | 94590 | Полезен |
link | 1212329 | 87475 | Полезен |
age | 7401415 | 59242 | Полезен |
x-cache | 5275343 | 56889 | Необязателен |
x-xss-protection | 9773906 | 51810 | Полезен |
strict-transport-security | 4259121 | 51283 | Полезен |
via | 4020117 | 47102 | Необязателен |
p3p | 8282840 | 44308 | Необязателен |
expect-ct | 2685280 | 40465 | Полезен |
content-language | 334081 | 37927 | Спорно |
x-aspnet-version | 676128 | 33473 | Необязателен |
access-control-allow-credentials | 2804382 | 30346 | Полезен |
x-robots-tag | 179177 | 24911 | Не имеет значения для браузеров |
x-ua-compatible | 489056 | 24811 | Необязателен |
access-control-allow-methods | 1626129 | 20791 | Полезен |
access-control-allow-headers | 1205735 | 19120 | Полезен |
Давайте посмотрим на необязательные заголовки, почему они нам не нужны и что с этим делать.
Тщеславие (server, x-powered-by, via)
Вы можете очень гордиться своим выбором серверного ПО, но большинству людей на это наплевать. В худшем случае эти заголовки могут разглашать конфиденциальные данные, что упрощает атаку на ваш сайт.
Server: apache
X-Powered-By: PHP/5.1.1
Via: 1.1 varnish, 1.1 squid
RFC7231 позволяет включать в ответ сервера заголовок Server
, указывая конкретный софт на сервере, который используется для выдачи контента. Чаще всего это строка вроде "apache" или "nginx". Хотя заголовок разрешён, он не обязательный и не особо ценный для разработчиков или конечных пользователей. Тем не менее, сегодня это третий по популярности серверный HTTP-заголовок в интернете.
X-Powered-By
— самый популярный заголовок из тех, что не определены никаким стандартом, и у него похожая цель: обычно он указывает платформу приложений, на которой работает сервер. Самые популярные ответы включают в себя "ASP.net", "PHP" и "Express". Опять же, это не несёт никакой ощутимой пользы и просто занимает место.
Возможно, более спорным можно считать Via
. Его обязан (по RFC7230) добавлять в запрос каждый прокси, через который проходит запрос — для идентификации прокси. Это может быть имя хоста прокси, но чаще общий идентификатор вроде "vegur", "varnish" или "squid". Удаление (или не добавление) такого заголовка может привести к циклу переадресации прокси. Но интересно, что он также добавляется в ответ на обратном пути к браузеру — и здесь выполняет просто информационную функцию: никакие браузеры ничего не с ним делают, поэтому достаточно безопасно избавиться от него, если хотите.
Устаревшие стандарты (P3P, Expires, X-Frame-Options, X-UA-Compatible)
Другая категория заголовков — это те, которые действительно вызывают эффект в браузере, но (уже) представляют собой не лучший способ достижения данного эффекта.
P3P: cp="this is not a p3p policy"
Expires: Thu, 01 Dec 1994 16:00:00 GMT
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=edge
P3P
— забавная штучка. Я понятия не имел, что это такое. Ещё забавнее, что одно из самых распространённых содержаний заголовка P3P — «Это не правило P3P». Ну так это оно или нет?
Тут история восходит к попытке стандартизировать машиночитаемые правила приватности. Были разногласия по поводу того, как отображать данные в браузерах, и только один браузер реализовал поддержку этого заголовка — Internet Explorer. Но даже в нём P3P не имел никакого визуального эффекта для пользователя; он просто должен был присутствовать, чтобы разрешить доступ к сторонним кукам во фреймах. Некоторые сайты даже установили правила несоблюдения P3P, как в примере выше, хотя делать так весьма сомнительно.
Нечего и говорить, что чтение сторонних куков вообще обычно плохая идея, так что если вы этого не делаете, то вам и не нужно устанавливать заголовок P3P
!
Expires
просто невероятно популярен с учётом того, что Cache-Control
имеет преимущество перед Expires
уже в течение 20 лет. Если заголовок Cache-Control
содержит директиву max-age
, то любой заголовок Expires
в том же ответе игнорируется. Но огромное количество сайтов устанавливает оба этих заголовка, а Expires
чаще всего ставят на дату Thu, 01 Dec 1994 16: 00: 00 GMT
, чтобы контент не кэшировался. Конечно, ведь проще всего копипастнуть дату из спецификаций.
Но просто незачем это делать. Если у вас заголовок Expires
с датой из прошлого, просто замените его на:
Cache-Control: no-store, private
(no-store
— слишком строгая директива не записывать контент в постоянное хранилище, так что вы можете предпочесть no-cache
ради лучшей производительности, например, для навигации назад/вперёд или возобновления «спящих» вкладок в браузере)
Некоторые инструменты проверки сайтов посоветуют добавить заголовок X-Frame-Options
со значением 'SAMEORIGIN'. Он говорит браузерам, что вы отказываетесь отдавать контент во фрейм на другом сайте: как правило, это хорошая защита от кликджекинга. Но того же эффекта можно достигнуть другим заголовком с более последовательной поддержкой и более надёжным поведением:
Content-Security-Policy: frame-ancestors 'self'
Здесь есть дополнительная выгода, потому что заголовок CSP вы всё равно должны отдавать по иным причинам (о них позже). Вероятно, в наше время можно обойтись без X-Frame-Options
.
Наконец, ещё в IE9 компания Microsoft представила «режим совместимости», который отображал страницу с помощью движка IE8 или IE7. Даже в нормальном режиме браузер думал, что для правильного рендеринга может понадобиться более ранняя версия движка. Эти эвристики не всегда работали корректно, и разработчики могли переопределить их, используя заголовок или метатег X-UA-Compatible
. Фактически, это стало стандартной частью многих фреймворков вроде Bootstrap. Сейчас этот заголовок практически бесполезен: очень мала доля браузеров, которые понимают его. И если вы активно поддерживаете сайт, то очень маловероятно, что на нём используются технологии, которые запустят режим совместимости.
Данные для отладки (X-ASPNet-Version, X-Cache)
В каком-то роде удивительно, что некоторые из самых популярных заголовков вообще не упоминаются ни в каком стандарте. По сути это значит, что тысячи веб-сайтов каким-то образом внезапно договорились использовать определённый заголовок определённым образом.
X-Cache: HIT
X-Request-ID: 45a336c7-1bd5-4a06-9647-c5aab6d5facf
X-ASPNet-Version: 3.2.32
X-AMZN-RequestID: 0d6e39e2-4ecb-11e8-9c2d-fa7ae01bbebc
На самом деле, эти «неизвестные» заголовки не выдумали разработчики. Обычно это артефакты использования определённых серверных фреймворков, софта или сервисов конкретных поставщиков (например, последний заголовок типичен для AWS).
Заголовок X-Cache
добавляет Fastly (и другие CDN), вместе с другими специфичными хедерами, такими как X-Cache-Hits
и X-Served-By
. Когда отладка включена, то добавляется ещё больше, например, Fastly-Debug-Path
и Fastly-Debug-TTL
.
Эти заголовки не распознаются ни одним браузером, а их удаление совершенно не отразится на отображении страниц. Но поскольку они могут предоставить вам, разработчику, полезную информацию, то можете их сохранить.
Недоразумения (Pragma)
Не ожидал, что в 2018 году придётся упоминать заголовок Pragma
, но согласно HTTP Archive он по-прежнему в топе (11-е место). Его не только объявили устаревшим ещё в 1997 году, но он вообще никогда не задумывался как заголовок ответа, а только как часть запроса.
Pragma: no-cache
Тем не менее его настолько широко используют в качестве заголовка ответа, что некоторые браузеры даже распознают его и в этом контексте. Но сегодня практически нулевая вероятность, что кто-то понимает Pragma
в контексте ответа, но не понимает Cache-Control
. Если хотите запретить кэширование, всё что вам нужно — это Cache-Control: no-store, private
.
Не-браузеры (X-Robots-Tag)
Один заголовок в нашем топ-30 не является заголовком для браузера. X-Robots-Tag
предназначен для краулеров, таких как боты Google или Bing. Поскольку для браузера он бесполезен, то можете установить такой ответ только на запросы краулеров. Или вы решите, что это затрудняет тестирование или нарушает условия использования поисковой системы.
Баги
Наконец, стоит закончить почётном упоминанием простых ошибок. Заголовок Host
имеет смысл в запросе, но если он встречается в ответе, то вероятно ваш сервер неправильно настроен (и я хотел бы знать, как именно). Тем не менее 68 доменов в HTTP Archive возвращают заголовок Host
в своих ответах.
Удаление заголовков на edge-сервере CDN
К счастью, если ваш сайт работает у нас на Fastly, то удалить заголовки довольно просто с помощью VCL. Если хотите сохранить команде разработчиков действительно полезные данные для отладки, но скрыть их от общей публики, то это легко делается по куки или входящему заголовку HTTP:
unset resp.http.Server;
unset resp.http.X-Powered-By;
unset resp.http.X-Generator;
if (!req.http.Cookie:debug && !req.http.Debug) {
unset resp.http.X-Amzn-RequestID;
unset resp.http.X-Cache;
}
В следующей статье я расскажу о лучших практиках для действительно нужных заголовков и как активировать их на edge-сервере CDN.
Автор: m1rko