Изменения API

Заголовок Sunset у API и когда его отправлять

5 мин чтения

Sunset — это один заголовок ответа, определённый в RFC 8594, который сообщает вызывающему, когда ресурс перестанет отвечать. Депрекация API разбирает полный график объявление-напоминание-brownout-удаление и уведомления, идущие вместе с ним; здесь — об одном машиночитаемом сигнале в этом графике, о том, что он на самом деле говорит, и о единственном случае, когда сам RFC советует его не отправлять.

Что говорит заголовок Sunset, а чего не говорит?

Он несёт одну HTTP-дату — момент, когда ресурс, как ожидается, перестанет отвечать:

Sunset: Sat, 31 Dec 2028 23:59:59 GMT

RFC называет это подсказкой, а не гарантией: он не обещает, что ресурс продолжит работать вплоть до этой отметки времени, и ничего не говорит о том, как будет выглядеть сбой после неё. Вызывающие могут получить 4xx, редирект или вообще ничего; заголовок это не различает. Дата, уже находящаяся в прошлом, означает «сейчас или в любой момент», а не ошибку в значении. Ничего из этого протокол не обеспечивает принудительно. Клиент, никогда не читающий заголовок, ведёт себя точно так же, как всегда, и узнаёт об исчезновении ресурса тем же способом, каким узнал бы в любом случае.

Когда его действительно стоит отправлять?

Только когда ресурс по-настоящему собирается перестать отвечать, а не когда он всего лишь перестал быть рекомендуемым выбором. RFC явно говорит, что депрекация проходит в две стадии, и поле заголовка Sunset относится только ко второй: во время первой стадии, объявления, что версия больше не предпочтительна, API остаётся полностью работоспособным, и заголовок там неприменим. Он применяется, когда версия действительно запланирована к остановке ответа.

Это напрямую ложится на график депрекации: заголовок Deprecation отправляется с первого дня, на шаге объявления; Sunset описывает дату, когда старое поведение реально остановится, и это та же дата, которую график из четырёх шагов называет удалением. Отправлять Sunset в первый день — не ошибка, раз дата уже зафиксирована к этому моменту, но отправлять его без предшествующего объявления депрекации, или указывать в нём версию, удаление которой вы на самом деле не подтвердили, сообщает вызывающим то, что вы сами ещё не решили.

Взаимодействует ли он с кэшированием?

Нет, и RFC говорит об этом прямо: Sunset и HTTP-кэширование решают не связанные друг с другом задачи, и их стоит читать как дополняющие, а не пересекающиеся. Заголовки кэширования говорят, когда безопасно переиспользовать закэшированную копию; Sunset ничего не говорит о текущем состоянии ресурса, только о том, что сам ресурс перестанет существовать. Ответ может быть полностью кэшируемым вплоть до момента своего sunset. Не используйте один заголовок как приближение другого и не считайте, что длинный max-age отменяет приближающуюся дату sunset, или наоборот.

Может ли один заголовок закрыть больше одного endpoint?

Заголовок применяется к ресурсу, который его вернул, но RFC позволяет сервису задокументировать более широкую область действия: дата Sunset на домашнем ресурсе API может быть определена как означающая, что уходит весь API, а не только этот один URL. Загвоздка в том, что это работает только для вызывающих, уже знающих ваше правило области действия. Вызывающий, читающий заголовок буквально, видит sunset только на том одном ресурсе, который запросил, и ничего больше, так что более широкую область действия нужно где-то записать так, чтобы вызывающий мог это найти, а не подразумевать.

Что должно идти вместе с заголовком?

Ссылка на то, где объяснено удаление. RFC 8594 регистрирует собственное отношение ссылки sunset именно для этого: указание на ресурс, описывающий политику удаления, предстоящую дату или способ миграции, отдельно от голой отметки времени в заголовке.

HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2028 23:59:59 GMT
Link: <https://example.com/docs/sunset-policy>; rel="sunset"

Направив эту ссылку на свои собственные примеры changelog или отдельную страницу миграции, вы превращаете заголовок, который почти ничей клиентский код не проверяет, в нечто, что человек, действительно решивший поискать, находит немедленно. Скомбинируйте его с отношением successor-version из заголовков депрекации, и вызывающий получает из одного только ответа и куда идти, и чем это заменяется.

Как это выглядит целиком?

Допустим, v1 уходит 1 марта 2027 года. Объявление о депрекации в первый день добавляет Deprecation и Link: rel="successor-version" к каждому ответу v1, согласно заголовкам депрекации, но откладывает Sunset до момента, когда дата удаления по-настоящему зафиксирована, а не является заглушкой. Как только это происходит, каждый ответ v1 несёт:

HTTP/1.1 200 OK
Deprecation: @1756425600
Sunset: Mon, 01 Mar 2027 00:00:00 GMT
Link: <https://api.example.com/v2/reports>; rel="successor-version"
Link: <https://example.com/docs/sunset-policy>; rel="sunset"

Шлюз или мониторинг вызывающего может независимо реагировать на любой из заголовков: Deprecation говорит, что существует более новая версия, Sunset — что у этой есть свои часы. Ни один из заголовков не обязан меняться до 1 марта; меняется сам ответ, в этот день и во время любых запланированных перед ним окон brownout.

Меняет ли brownout то, что говорит заголовок?

Само значение заголовка не обязано сдвигаться из-за запланированного brownout: дата sunset остаётся датой sunset независимо от того, отказывает ли ресурс с перебоями до неё. Меняется ответ, а не заголовок. Планирование коротких окон 410 Gone за недели до объявленной даты, как описывает Депрекация API, — это то, что превращает первый контакт вызывающего со сбоем в репетицию, а не в реальность в день, когда наступает дата из заголовка.

FAQ

Действительно ли какие-то реальные HTTP-клиенты или инструменты читают заголовок Sunset? На стороне клиента — редко. Его ценность в основном для того, кто управляет инфраструктурой между вами и вызывающим: API-шлюз или инструмент мониторинга, настроенный следить за этим заголовком, может оповестить вашу собственную команду или команду партнёра задолго до того, как код вызывающего вообще что-то заметит. Относитесь к нему как к сигналу, под который вы строите инструментарий, а не как к тому, что у другой стороны уже наверняка есть.

Это то же самое, что Cache-Control: max-age? Нет. max-age о том, как долго закэшированная копия остаётся валидной; Sunset о том, когда ресурс вообще перестаёт существовать. Ответ может нести короткий max-age и дату Sunset через годы, или наоборот, и ни один из заголовков не ограничивает другой.

Можно ли отправить Sunset для одного исчезающего поля, а не всего endpoint? Нет, заголовок привязан к ресурсу, то есть к URL, а не к полю внутри тела ответа. Для поля, параметра или значения enum, которое уходит, пока сам endpoint остаётся, используйте вместо этого заголовок Deprecation и запись в changelog; Депрекация API разбирает объявление именно такого рода изменений.

Что если дату sunset нужно сдвинуть? Обновите значение заголовка и скажите об этом в записи changelog, объявившей её изначально; тихая смена опубликованной даты — это то, как вызывающий решает, что ни одна из ваших дат не реальна. RFC описывает это значение как подсказку именно потому, что даты иногда действительно сдвигаются, но сдвинутая дата без объяснения будет стоить вам доверия и к следующей.


Технические утверждения в этой статье не проходили независимую проверку. Если здесь что-то не так, сообщите нам, и мы исправим.

По теме на changeloop: Документация для разработчиков, Примеры changelog

changeloop
Команда, которая делает changelog, замыкающий цикл. Пользователи о чём-то просят, ваша команда это делает, тот, кто просил, узнаёт об этом.