Согласование содержания определяется как:

Процесс выбора наилучшего представления для данного ресурса при наличии нескольких представлений доступных.

В этой статье рассматривается, почему это полезно и как это можно сделать.

Ресурсы и представления

Начнем с некоторых определений.

Допустим, у меня на столе в роскошном манчестерском офисе Депоп стоит принтер ». Сам принтер - это ресурс, который мы хотим сделать доступным в сети.

На самом деле мы, конечно, не отправляем принтер по сети! Вместо этого мы отправляем представление - снимок состояния принтера.

Это может выглядеть примерно так:

{
  "data": {
    "status": "online", 
    "description": "The printer on Jon's desk",
    "ink": 56,
    "paper": true,
    "url": "https://depop.com/printers/man/4"
  }
}

Это JSON-представление некоторых данных о принтере.

Альтернативное представление могло бы выглядеть так:

data:
  status: online
  description: The printer on Jon's desk
  ink: 56
  paper: true
  url: https://depop.com/printers/man/4

Это YAML-представление данных. Это разновидность content-type. Вот еще YAML:

data:
  status: онлайн
  description: Принтер на столі Джона
  ink: 56
  paper: true
  url: https://depop.com/printers/man/4

На этот раз были переведены строковые значения документа - это украинская версия. Изображение изменилось на его (человеческом) языке.

Акт запроса и предоставления альтернативных представлений - вот в чем суть переговоров по контенту.

Что может отличаться?

HTTP имеет встроенную поддержку для указания предпочтений с использованием этих заголовков:

Accept - формат представления - например, XML, JSON и т. Д.

Accept-Language - язык представления - английский, испанский, русский и др.

Accept-Encoding - кодировка представления - gzip, br и т. Д.

Раньше также был заголовок Accept-Charset, но теперь он устарел².

Как проходят переговоры?

Спецификацию согласования можно найти в Разделе 3.4 RFC 7231 - есть два основных варианта, управляемые сервером или агентом (клиентом).

В случае согласования, управляемого сервером, клиент указывает свои требования в заголовках, а сервер запускает алгоритм для выбора наилучшего представления.

И наоборот, при согласовании, управляемом клиентом, именно клиент запускает алгоритм, чтобы решить, какое представление использовать из выбора, предоставленного сервером.

Давайте подробнее рассмотрим оба ³.

Серверное согласование

Определение требований

Вот несколько примеров заголовков, которые может указать клиент:

Accept: application/json
Accept-Language: es-mx
Accept-Encoding: gzip

Этот клиент запрашивает ответ JSON на мексиканском испанском языке, сжатый с использованием алгоритма GZIP.

Lo siento, no hablamos español

Увы, принтер на моем столе не говорит по-испански, так что же сервер?

  • Возврат 406 Not Acceptable
  • Вернуть документ по умолчанию, например разархивированный, на английском языке и т. д.

Первое, возможно, правильнее, но второе более вероятно. Ни то, ни другое не желательно, так что же мог сделать клиент?

Предпочтения, а не требования

Вместо жестких и быстрых требований клиент может отдать предпочтения, например:

Accept: application/json, application/xml;q=0.9
Accept-Language: es-mx, es;q=0.9; en;q=0.8, *;q=0.7
Accept-Encoding: gzip, identity;q=0.9 

Значения, разделенные запятыми, для каждого заголовка определяют предпочтения, а значения q представляют собой весовые коэффициенты (отсутствие весов означает, что q = 1.0, наиболее желательный вариант).

Взяв в качестве примера Accept-Languageheader выше, мы можем прочитать его так:

Я предпочитаю мексиканский испанский, но подойдет любая форма испанского; если у вас нет испанского, то английский вполне приемлем, а если и у вас его нет, просто пришлите мне, что у вас есть.

Это гораздо более гибкий подход, и моя конечная точка принтера может удовлетворить все эти требования, отправив JSON на английском языке со сжатием GZIP.

Ограничения

Клиент может указать только три стандартных заголовка, перечисленных выше, чтобы получить желаемое представление. Клиент и сервер могут договориться о том, что заголовок X-Funky-Header имеет некоторую семантику, помогающую выбрать представление, но это будет внеполосным ⁵ соглашением, поэтому это не рекомендуется.

Клиентские переговоры

Если сервер решает, какое представление использовать, является слишком ограничивающим для конкретного варианта использования, альтернативой является изменение ролей и предоставление клиенту решения на основе информации с сервера.

В этом случае, когда клиент запрашивает ресурс с несколькими представлениями, сервер отвечает:

300 Multiple Options

и полезная нагрузка, описывающая опции с уникальными ссылками на каждую из них.

Затем клиент просто выбирает тот, который ему нужен, и переходит по ссылке, чтобы получить его.

Не все так просто

Увы, это не так просто. Не существует согласованного формата для ответа полезной нагрузки и, следовательно, нет очевидного алгоритма, который клиент мог бы использовать для выбора желаемого представления.

Кэш в руке

Вдобавок некоторые из вас, вероятно, думают: «Это означает, что нам нужно сделать два запроса, чтобы получить этот ресурс».

Не беспокоиться! Оба запроса можно и нужно кэшировать⁶, поэтому стоимость минимальна, и если вам нужна такая гибкость, она того стоит.

Как это сделать

Предполагая, что клиент и сервер используют для связи тип содержимого с поддержкой гипертекста, имеет смысл использовать это для описания параметров для клиента. Чтобы гарантировать это, клиент может добавить к этому концу заголовок, например:

Accept: application/vnd.api+json

Теперь нам нужен документ в этом формате, который описывает способы изменения представления со ссылками на каждый вариант:

{
  "links": {
    "option 1": {
      "meta": {
        "supports": "Draft-07",
        "lang": "en-gb"
      },
      "href": "/api/address/schemata/address/1",
      "type": "application/schema+json"
    }
    "option 2": {
      "meta": {
        "supports": "2020-12",
        "lang": "en-gb"
      },
      "href": "/api/address/schemata/address/2",
      "type": "application/schema+json"
    }
  }
}

Хорошая особенность этого метода заключается в том, что у нас больше свободы в отношении аспектов, в которых может варьироваться представление. В этом случае мы используем знания о поддержке различных версий схемы JSON⁸.

¹ Полное раскрытие - я ни разу не был в офисе. Это может быть, а может и не быть роскошным. Или навес для велосипедов.

² Широкое распространение UTF-8 означает, что в наши дни нам редко нужны такие функции. См. Эту статью на MDN.

«Здесь также есть несколько хороших кавергей на MDN.

RFC 8942 высказывает идею о подсказках клиентов, которые могли бы расширить это.

⁵ Хорошее описание OOB можно найти в этой ветке переполнения стека.

⁶ Подробнее о кешировании читайте в другой статье этого блога.

⁷ Кем ты действительно должен быть! Подробнее о гипермедиа я расскажу в этом блоге.

⁸ Хотя это иллюстративный пример, профили часто являются лучшим способом сделать это, если рассматриваемый тип MIME их поддерживает.