Часто доводится слышать, будто протокол HTTP прост. Естественно, чаще всего — от тех, кто не слишком поднаторел в обращении с ним и слабо представляет, как именно этот протокол реализован. Думаю, сам я тоже мыслил в таком ключе, когда ещё только начинал работать с HTTP.
К настоящему времени я уже без малого три десятка лет усердно пишу клиентский код, взаимодействующий с HTTP. Я участвую в работе IETF и приложил руку ко всем спецификациям HTTP, составленным примерно с 2008 года. Поэтому полагаю, что вполне имею право развёрнуто высказаться об этом протоколе. Протокол HTTP не прост. Далеко не прост. Даже если предположить, что те, кто отмечает его простоту, имеют в виду лишь HTTP/1.
Идея и концепция HTTP, пожалуй, до сих пор могут считаться простыми, а в чём-то и гениально простыми, но о внутреннем устройстве такого не скажешь.
Правда, действительно можно достучаться до сервера HTTP/1 по telnet, вручную ввести команду GET / и посмотреть ответ. Но я не думаю, что одного этого достаточно, чтобы считать весь инструмент простым.
Не думаю, что кто-то пытался настаивать на простоте HTTP/2 или HTTP/3. Чтобы как следует реализовать вторую или третью версию, для начала необходимо реализовать версию 1, и в данном случае сложность накапливается, не говоря уж о том, что в спецификациях новых версий HTTP как таковых немало своих нетривиальных аспектов.
Давайте подробно разберём некоторые детали протокола HTTP/1, с учётом которых я берусь утверждать, что он не прост.
Переходы на новую строку
HTTP имеет не просто текстовую, но, более того, построчную основу; это касается его заголовочных частей. Строка может быть сколь угодно длинной, так как в спецификациях она не ограничивается — но в конкретных реализациях такое ограничение предусматривать нужно, чтобы защититься от DoS-атак и т.д. Какой длины может достичь строка, прежде, чем сервер её отклонит? Каждая строка заканчивается возвратом каретки и переводом строки. Но бывает и так, что достаточно лишь перевода строки.
Кроме того, заголовки не закодированы в UTF-8, они являются октетами, поэтому нельзя рассчитывать, что через них вы сможете произвольно передавать всё, что вам заблагорассудится.
Пробелы
Такая проблема легко возникает с протоколами, основанными на тексте. Между полями могут оказаться один или несколько пробелов. Среди них есть как обязательные, так и опциональные. Зачастую HTTP также использует токены, и токен может представлять собой как последовательность символов без каких-либо пробелов, так и текст, заключённый в двойные кавычки (“). В некоторых вариантах текст обязательно заключается в кавычки.
Конец тела документа
Существует не один способ, позволяющий убедиться, что загрузка документа по HTTP/1 завершилась – то есть, что достигнут конец «тела» документа. Таких способов даже не два, а как минимум три (проверка длины содержимого, кодировка частями и проверка закрытия соединения). В двух из этих вариантов требуется, чтобы HTTP-клиент разобрал контент, предоставленный в текстовом формате, и определил его размер. Из-за того, что есть множество вариантов окончания тела документа, с годами возникли многочисленные проблемы с безопасностью, касающиеся протокола HTTP/1.
Синтаксический разбор чисел
Если числа представлены как текст, то синтаксический разбор такой информации идёт медленно, а иногда чреват ошибками. Следует особенно внимательно избегать целочисленных переполнений, правильно обрабатывать пробелы, префиксы +/-, ведущие нули и пр. Текст, который легко читать человеку, совсем не так удобен для машин.
Завёрстывание заголовков
Мало нам проблем с заголовками произвольной длины и неочевидными окончаниями строк — так оказывается, что заголовки ещё могут «завёрстываться», причём, двумя способами. Во-первых, прокси-сервер может объединить несколько заголовков в один, просто расставив между ними разделительные запятые. Это касается почти всех заголовков (кроме, например, куки). Во-вторых, сервер может отправить очередной заголовок как продолжение предыдущего, добавив ведущий пробел. Сейчас так делают редко (а в новейших версиях спецификаций это прямо не рекомендуется), но при реализации протокола нужно уделять внимание этой детали, поскольку такая практика до сих пор существует.
Никогда не реализовывалось
В HTTP/1.1 амбициозно добавляли такие фичи, которые на тот момент не использовались или не получили широкого распространения в общедоступном интернете. Например, в спецификации описано, как работает конвейеризация HTTP, но, если вы попробуете задействовать эту фичу «на местности», то сами напрашиваетесь на проблемы и… всё окончится грустно.
В более новых версиях HTTP добавились фичи, которые в большей степени отвечают тем критериям, которые не удовлетворяются средствами такой конвейеризации; в основном подобные задачи решаются через мультиплексирование.
Код состояния 100 примерно такого же рода: он описан в спецификации, но на практике используется редко. В новых реализациях от него одни проблемы. Совсем недавно возникла дискуссия о различных деталях обработки состояния при отклике 100, притом, что первый вариант спецификации, в котором он учитывается, опубликован 28 лет назад. Думаю, это говорит о многом.
Множество заголовков
В спецификации HTTP/1 рассмотрено множество заголовков и рассказано, как они работают, но этого не хватает, чтобы поддерживать нормальную современную реализацию HTTP. Дело в том, что такие вещи как куки-файлы, аутентификация, новые коды отклика и различные другие вещи, которые могут потребоваться в реализации, не охвачены основной спецификацией и описаны в отдельных документах. Некоторые детали, например, NTLM, не найти даже в документах RFC.
Следовательно, современному клиенту для HTTP/1 приходится реализовывать и поддерживать целый спектр дополнительных вещей и заголовков, чтобы нормально работать в широком Интернете. «HTTP/1.1» упоминается как минимум в 40 отдельных RFC-документах. Некоторые из них весьма сложны сами по себе.
Метод методу рознь
Притом, что в идеале должно быть можно использовать совершенно одинаковый синтаксис независимо от того, с каким именно методом вы работаете (иногда методы называются verb), в реальности всё совсем не так.
Например, при работе с методом GET действительно можно послать тело сообщения прямо в запросе примерно так, как это делается с POST и PUT. Но, поскольку ранее это ни разу как следует не проговаривалось, в настоящий момент с интероперабельностью этой функции накопились большие проблемы. Этот метод — верный путь к провалу, и примеров его неудачной реализации в Вебе более чем достаточно.
В частности, именно поэтому сейчас разрабатывают новый HTTP-метод QUERY, который должен работать как GET + тело запроса. Но протокол от этого не упрощается.
Заголовок заголовку рознь
Поскольку некоторые заголовки создавались, развёртывались и развивались сами по себе, прокси-сервер не может просто вслепую объединить два заголовка в один, хотя, в шаблонных правилах это разрешено. Но есть заголовки, идущие вразрез с этими правилами, поэтому и обращаться с ними нужно иначе. Типичный пример — куки.
Бесхребетные браузеры
Помните, как в браузерных реализациях протоколов всегда предпочитается что-то показать пользователю, а затем угадать, чего он хотел — вместо того, чтобы вывести ошибку? Дело в том, что при излишней строгости в этом вопросе есть риск, что пользователь откажется от вашего браузера в пользу другого, более снисходительного.
Это повлияло на практику обращения с HTTP во всех остальных сферах, поскольку пользователь рассчитывает: то, что работало в браузере, определённо должно работать и вне браузеров, а также в соответствующих реализациях HTTP.
В результате становится не столь важно уметь интерпретировать и понимать спецификацию — достаточно лишь придерживаться той линии, что избрана основными браузерами в каждом конкретном случае. Со временем браузеры даже могут пересматривать те или иные позиции, и в некоторых случаях они могут прямо противоречить тому, что сказано в спецификации.
Размеры спецификаций
В первом документе по HTTP/1.1 RFC 2068 от января 1997 года насчитывалось 52165 слов обычного текста. Это почти втрое больше, чем объём документа RFC 1945 по версии HTTP/1.0, где было всего 18615 слов. Красноречиво свидетельствует, насколько усложнился в версии 1.1 протокол, который, пожалуй, в версии HTTP 1.0 ещё был прост.
В июне 1999 года обновлённая версия RFC 2616 увеличилась ещё на несколько сотен строк и завесила на 57 897 слов. То есть, почти на 6000 слов больше.
Затем в рамках IETF была проделана огромная работа, и за последующие 15 лет спецификация HTTP/1.1, которая когда-то являлась единым документом, оказалась разделена на шесть отдельных документов.
В июне 2014 года были опубликованы запросы на спецификацию от RFC 7230 до RFC 7235, в них в совокупности насчитывается 90 358 слов. Материал прирос ещё на 56%. Теперь он сравним по размеру со средним романом.
Впоследствии вся спецификация переупорядочивалась и реорганизовывалась, чтобы лучше соответствовать новым версиям HTTP. Последнее обновление было опубликовано в июне 2022 года. После этого элементы HTTP/1.1 были умещены в три документа от RFC 9110 до RFC 9112 общим объёмом в 95 740 слов.
Навскидку, допустим, кто-то в состоянии штудировать эти документы со скоростью 200 слов в минуту. Пожалуй, это чуть ниже средней скорости чтения, но посудите сами – спецификацию-стандарт всё-таки читаешь несколько медленнее, чем беллетристику. При этом допустим, что 10% слов — это мусор, который можно пропускать.
Таким образом, если без остановки прочитать всего три самых свежих RFC, касающихся HTTP/1.1, на это уйдёт более семи часов.
В топку?
В одном недавнем докладе с достаточно кликбейтным названием автор предположил, что, если HTTP/1 в самом деле так сложно реализовать правильно, от него вообще следует отказаться.
А иначе никак?
Всё это верно, но, тем не менее, в Интернете совсем мало протоколов, которые могут потягаться с HTTP/1 по широте использования, усвоенности и популярности. HTTP — это флагман Интернета. Может быть, такой уровень сложности — необходимая составляющая такого успеха?
Сравнивая его с другими популярными протоколами, которые до сих пор в ходу, например, с DNS или SMTP, думаю, что им присущи схожие черты: все они зародились довольно давно как весьма простые технологии. Прошли десятилетия, и простыми их уже не назовёшь.
Возможно, просто такова жизнь?
Заключение
HTTP — не простой протокол.
Вероятно, в будущем всё ещё более усложнится по мере того, как HTTP будет от версии к версии обрастать новыми фичами.
Комментарии (2)
CaptainFlint
09.08.2025 23:29А если взять спецификацию языка C++, то там 870 тысяч слов. И как вообще на нём писать умудряются…
MonkAlex
Я ждал чего-то интересного. Жаль, что мои ожидания - мои проблемы.