Является ли это ошибкой, чтобы опустить заголовок Accept */* в HTTP/1.0 Request для REST API

Я пытаюсь определить, является ли это ошибкой, что функция Python urllib.urlopen() пропускает заголовок HTTP Accept при выполнении простых запросов API REST.

API-интерфейс Facebook, кажется, замечает, присутствует ли заголовок или нет:

GET /zuck HTTP/1.0
Host: graph.facebook.com
Accept: */*

Без заголовка accept возвращаемый тип содержимого application/json; charset=UTF-8 становится text/javascript; charset=UTF-8. Это может быть ошибкой в ​​API REST Facebook или может быть законным ответом на отсутствующий заголовок принятия.

Я заметил, что инструменты командной строки, такие как curl, используют Accept: */* по умолчанию:

$ curl -v https://graph.facebook.com/zuck
> GET /zuck HTTP/1.1
> User-Agent: curl/7.30.0
> Host: graph.facebook.com
> Accept: */*

Аналогично, пакет Python запросов также использует Accept: */* по умолчанию:

def default_headers():
    return CaseInsensitiveDict({
        'User-Agent': default_user_agent(),
        'Accept-Encoding': ', '.join(('gzip', 'deflate')),
        'Accept': '*/*',
        'Connection': 'keep-alive',
    })

Я предполагаю, что завиток и запросы добавляют по умолчанию по какой-то причине, но я не уверен, что это за причина.

RFC 2616 для HTTP/1.1 говорит, что */* indicates all media types и if no Accept header field is present, then it is assumed that the client accepts all media types. Это, по-видимому, указывает на то, что Accept: */* является необязательным, и его упущение не будет иметь никакого эффекта. Тем не менее, Python использует HTTP/1.0, и RFC молчат о влиянии опущения заголовка.

Я хотел бы определить, следует ли использовать Accept: */* как curl, а запросы делать или это нормально опустить, как это делает Python urllib.urlopen().

Вопрос важен, потому что я могу исправить urllib.urlopen(), если он определен как ошибочный или если он проблематичен для использования с API-интерфейсы REST, обычно используемые с использованием HTTP/1.0:

>>> import httplib
>>> httplib.HTTPConnection.debuglevel = 1
>>> import urllib
>>> u = urllib.urlopen('https://graph.facebook.com/zuck')
send: 'GET /zuck HTTP/1.0\r\nHost: graph.facebook.com\r\nUser-Agent: Python-urllib/1.17\r\n\r\n'

Связанные с этим вопросы по StackOverflow не подходят для этого вопроса. Что означает "Принять: */*" в разделе "Клиент" заголовков запроса? спрашивает, что означает */* (мы уже знаем, что это означает все типы носителей) и Отправить запрос на завивание без заголовка Accept? спрашивает, как опустить заголовок accept в запросе curl. Мой вопрос фокусируется на том, следует ли включать */* и является ли это ошибкой, чтобы опустить его.

Ответ 1

Чтение прокси-серверов (таких как NGinx и Varnish) помогло мне выяснить, что происходит.

В то время как наличие заголовка Accept: */* не должно влиять на сервер, оно может и, вероятно, изменит прокси-сервер, когда ответ включает Vary: Accept. В частности, прокси-серверу разрешено кэшировать разные результаты для разных или опущенных заголовков Accept.

Facebook обновил (и закрыл) свой API, так как этот вопрос был задан, но в то время вот сценарий, вызвавший наблюдаемые эффекты. В целях обратной совместимости Facebook использовал контентное согласование и ответил text/javascript; charset=UTF-8 при получении запроса, который либо пропустил заголовок Accept, либо имел браузерный Accept: text/html;text/*;*/*. Однако, когда он получил Accept: */*, он вернул более современный application/json; charset=UTF-8. Когда прокси-сервер получает запрос без заголовка accept, он может давать либо один из кэшированных ответов; однако, когда он получает Accept: */*, он всегда дает последний ответ.

Итак, вот , почему вы должны включать заголовок Accept: */*: если вы это сделаете, то прокси-сервер кэширования всегда будет возвращать тот же тип контента. Если опустить заголовок, ответ может варьироваться в зависимости от результатов последнего согласования содержимого пользователя. Клиенты REST API склонны полагаться всегда на то, чтобы каждый раз получать одинаковый тип содержимого.

Ответ 2

состояния RFC

Поле заголовка запроса принятия можно использовать для указания определенных типов носителей, приемлемых для ответа.

Это означает, что заголовок является необязательным, потому что он говорит can be used.

как вы указали, RFC также говорит:

Если поле заголовка Accept отсутствует, предполагается, что клиент принимает все типы медиа.

Это означает, что исключение заголовка ДОЛЖНО быть равнозначно интерпретировано сервером как отправка Accept: */* в том смысле, что клиент acceptes all media types в обоих случаях.

Интересно, что ответ в facebook отличается в обоих случаях, но я думаю, что это их неспособность правильно интерпретировать протокол. Хотя с другой стороны оба ответа, очевидно, являются правильными ответами на запрос (который я нахожу забавным поводом).

У меня есть общие мысли по этой проблеме (которые также могут внести вклад в обсуждение исправления):

  • Следуя Закона о Postel Be conservative in what you do, be liberal in what you accept from others (often reworded as "Be conservative in what you send, be liberal in what you accept")., вы можете решить более точно и явно добавить Accept: */*. Вы бы более точно помогли серверу в том, что он, возможно, неправильно истолковал протокол (например, facebook), что отсутствующий заголовок будет эквивалентен Accept: */*
  • Просто добавление полей заголовков, таких как Accept: */*, которые можно опустить, увеличивает сетевой трафик на 11 байт для каждого отдельного запроса, что может привести к проблемам с производительностью. Если значение Accept: */* по умолчанию в запросе может затруднить разработчикам вывести его из заголовка, чтобы сохранить до 11 байт.
  • Существует разница между спецификацией (или стандартной) и фактическим стандартом. Очевидно, что исключение поля заголовка идеально соответствует спецификации, с другой стороны, многие библиотеки, похоже, включают это, и такие сервисы, как API facebook, ведут себя по-другому, это можно увидеть как фактический стандарт, который создается и вы могли бы перейти в цикл и стать частью его создания.

Говоря HTTP/1.1: хотя (1) и (3) говорят за исправление urllib, я бы, вероятно, следуйте спецификации и аргументу производительности (2) и опустите заголовок. Как указано выше, ответ в facebook в обоих случаях правилен, поскольку им разрешено устанавливать тип носителя в том, что им нравится. (хотя это поведение кажется непреднамеренным, странным и по ошибке)

Говоря HTTP/1.0: я бы послал заголовок accept, так как вы сказали, что он не указан в HTTP/1.0 RFC, и тогда я думаю, что закон Postel становится более важным. С другой стороны заголовок Accept просто необязателен в http 1.0. The Accept request-header field can be used to indicate a list of media ranges which are acceptable as a response to the request Почему вы устанавливаете необязательный заголовок по умолчанию?

Ответ 3

  • Если служба реагирует по-разному на Accept: */* и отсутствует Accept, она не работает (и вы должны отправить отчет об ошибке).

  • Кроме того, наличие параметра charset на application/json также является ошибкой; это тип носителя, который не имеет параметра charset.

Ответ 4

RFC 7231 obsoletes RFC 2616.

Символ "*" звездочки используется для группировки типов носителей в диапазонах, при этом "*/*" указывает типы носителей all и "type/*", указывающие все подтипы этого типа.
...

Запрос без поля заголовка Accept подразумевает, что пользовательский агент примет ответ в виде любого.

Источник: http://tools.ietf.org/html/rfc7231#section-5.3.2

Из RFC 7231 любой из них может быть истолкован как самый устаревший или наиболее совместимый, и все они могут быть интерпретированы как самый последний тип медиа, если мы рассмотрим graph.facebook.com и список типов медиафайлов javascript/json MIME; text/javascript, text/ecmascript, application/javascript, application/ecmascript и application/json.

Похоже, что сервер считает, что пользовательские агенты опускают заголовок HTTP Accept менее способным и устаревшим, а также с эпохи pre-json. Это может быть причиной того, что он отправляет устаревший тип носителя MIME text/javascript, который устарел application/javascript.

Тем не менее, Python использует HTTP/1.0, и RFC молчат о влиянии опущения заголовка.
@raymond-hettinger

Не имеет значения, является ли запрос HTTP/1.0 или HTTP/1.1, современные серверы всегда отвечают HTTP/1.1. Поэтому, чтобы считаться обновленными серверами, пользовательские агенты должны включать заголовок Accept в запросах. А также принимающие заголовки участвуют в обсуждении контента.

Относительно charset в application/json; charset=UTF-8,

Текст JSON ДОЛЖЕН быть закодирован в UTF-8, UTF-16 или UTF-32. Кодировка по умолчанию - UTF-8.

https://tools.ietf.org/html/rfc7159#section-8.1

Таким образом, это не похоже на ошибку.

Ответ 5

Как вы указали, RFC 2616 уже заявляет, каково ожидаемое поведение службы в отсутствие заголовка Accept (что эквивалентно отправке Accept: */*). Из спецификации мы можем заключить

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

Таким образом, в вашем примере ни ваш PHP-клиент, ни вызываемый вами сервис не ошибаются. Я думаю, там нечего исправить.