Большинство используемых нами веб- и мобильных приложений постоянно взаимодействуют с глобальной сетью. Почти все подобные коммуникации совершаются с помощью запросов по протоколу HTTP. Рассказываем о них подробнее.
HTTP (HyperText Transfer Protocol, дословно — «протокол передачи гипертекста») представляет собой протокол прикладного уровня, используемый для доступа к ресурсам Всемирной Паутины. Под термином гипертекст следует понимать текст, в понятном для человека представлении, при этом содержащий ссылки на другие ресурсы.
Данный протокол описывается спецификацией RFC 2616. На сегодняшний день наиболее распространенной версией протокола является версия HTTP/2, однако нередко все еще можно встретить более раннюю версию HTTP/1.1.
В обмене информацией по HTTP-протоколу принимают участие клиент и сервер. Происходит это по следующей схеме:
- Клиент запрашивает у сервера некоторый ресурс.
- Сервер обрабатывает запрос и возвращает клиенту ресурс, который был запрошен.
По умолчанию для коммуникации по HTTP используется порт 80, хотя вместо него может быть выбран и любой другой порт. Многое зависит от конфигурации конкретного веб-сервера.
Подробнее о протоколе HTTP читайте по ссылке →
HTTP-сообщения: запросы и ответы
Данные между клиентом и сервером в рамках работы протокола передаются с помощью HTTP-сообщений. Они бывают двух видов:
- Запросы (HTTP Requests) — сообщения, которые отправляются клиентом на сервер, чтобы вызвать выполнение некоторых действий. Зачастую для получения доступа к определенному ресурсу. Основой запроса является HTTP-заголовок.
- Ответы (HTTP Responses) — сообщения, которые сервер отправляет в ответ на клиентский запрос.
Само по себе сообщение представляет собой информацию в текстовом виде, записанную в несколько строчек.
В целом, как запросы HTTP, так и ответы имеют следующую структуру:
- Стартовая строка (start line) — используется для описания версии используемого протокола и другой информации — вроде запрашиваемого ресурса или кода ответа. Как можно понять из названия, ее содержимое занимает ровно одну строчку.
- HTTP-заголовки (HTTP Headers) — несколько строчек текста в определенном формате, которые либо уточняют запрос, либо описывают содержимое тела сообщения.
- Пустая строка, которая сообщает, что все метаданные для конкретного запроса или ответа были отправлены.
- Опциональное тело сообщения, которое содержит данные, связанные с запросом, либо документ (например HTML-страницу), передаваемый в ответе.
Рассмотрим атрибуты HTTP-запроса подробнее.
Стартовая строка
Стартовая строка HTTP-запроса состоит из трех элементов:
- Метод HTTP-запроса (method, реже используется термин verb). Обычно это короткое слово на английском, которое указывает, что конкретно нужно сделать с запрашиваемым ресурсом. Например, метод GET сообщает серверу, что пользователь хочет получить некоторые данные, а POST — что некоторые данные должны быть помещены на сервер.
- Цель запроса. Представлена указателем ресурса URL, который состоит из протокола, доменного имени (или IP-адреса), пути к конкретному ресурсу на сервере. Дополнительно может содержать указание порта, несколько параметров HTTP-запроса и еще ряд опциональных элементов.
- Версия используемого протокола (либо HTTP/1.1, либо HTTP/2), которая определяет структуру следующих за стартовой строкой данных.
В примере ниже стартовая строка указывает, что в качестве метода используется GET, обращение будет произведено к ресурсу /index.html, по версии протокола HTTP/1.1:
Разберемся с каждым из названных элементов подробнее.
Методы
Методы позволяют указать конкретное действие, которое мы хотим, чтобы сервер выполнил, получив наш запрос. Так, некоторые методы позволяют браузеру (который в большинстве случаев является источником запросов от клиента) отправлять дополнительную информацию в теле запроса — например, заполненную форму или документ.
Ниже приведены наиболее используемые методы и их описание:
Разберемся с каждым из названных элементов подробнее.
Метод | Описание |
GET | Позволяет запросить некоторый конкретный ресурс. Дополнительные данные могут быть переданы через строку запроса (Query String) в составе URL (например ?param=value).О составляющих URL мы поговорим чуть позже. |
POST | Позволяет отправить данные на сервер. Поддерживает отправку различных типов файлов, среди которых текст, PDF-документы и другие типы данных в двоичном виде. Обычно метод POST используется при отправке информации (например, заполненной формы логина) и загрузке данных на веб-сайт, таких как изображения и документы. |
HEAD | Здесь придется забежать немного вперед и сказать, что обычно сервер в ответ на запрос возвращает заголовок и тело, в котором содержится запрашиваемый ресурс. Данный метод при использовании его в запросе позволит получить только заголовки, которые сервер бы вернул при получении GET-запроса к тому же ресурсу. Запрос с использованием данного метода обычно производится для того, чтобы узнать размер запрашиваемого ресурса перед его загрузкой. |
PUT | Используется для создания (размещения) новых ресурсов на сервере. Если на сервере данный метод разрешен без надлежащего контроля, то это может привести к серьезным проблемам безопасности. |
DELETE | Позволяет удалить существующие ресурсы на сервере. Если использование данного метода настроено некорректно, то это может привести к атаке типа «Отказ в обслуживании» (Denial of Service, DoS) из-за удаления критически важных файлов сервера. |
OPTIONS | Позволяет запросить информацию о сервере, в том числе информацию о допускаемых к использованию на сервере HTTP-методов. |
PATCH | Позволяет внести частичные изменения в указанный ресурс по указанному расположению. |
URL
Получение доступа к ресурсам по HTTP-протоколу осуществляется с помощью указателя URL (Uniform Resource Locator). URL представляет собой строку, которая позволяет указать запрашиваемый ресурс и еще ряд параметров.
Использование URL неразрывно связано с другими элементами протокола, поэтому далее мы рассмотрим его основные компоненты и строение:
Поле Scheme используется для указания используемого протокола, всегда сопровождается двоеточием и двумя косыми чертами (://).
Host указывает местоположение ресурса, в нем может быть как доменное имя, так и IP-адрес.
Port, как можно догадаться, позволяет указать номер порта, по которому следует обратиться к серверу. Оно начинается с двоеточия (:), за которым следует номер порта. При отсутствии данного элемента номер порта будет выбран по умолчанию в соответствии с указанным значением Scheme (например, для http:// это будет порт 80).
Далее следует поле Path. Оно указывает на ресурс, к которому производится обращение. Если данное поле не указано, то сервер в большинстве случаев вернет указатель по умолчанию (например index.html).
Поле Query String начинается со знака вопроса (?), за которым следует пара «параметр-значение», между которыми расположен символ равно (=). В поле Query String могут быть переданы несколько параметров с помощью символа амперсанд (&) в качестве разделителя.
Не все компоненты необходимы для доступа к ресурсу. Обязательно следует указать только поля Scheme и Host.
Версии HTTP
Раз уж мы упомянули версию протокола как элемента стартовой строки, то стоит сказать об основных отличиях версий HTTP/1.X от HTTP/2.X.
Последняя стабильная, наиболее стандартизированная версия протокола первого поколения (версия HTTP/1.1) вышла в далеком 1997 году. Годы шли, веб-страницы становились сложнее, некоторые из них даже стали приложениями в том виде, в котором мы понимаем их сейчас. Кроме того, объем медиафайлов и скриптов, которые добавляли интерактивность страницам, рос. Это, в свою очередь, создавало перегрузки в работе протокола версии HTTP/1.1.
Стало очевидно, что у HTTP/1.1 есть ряд значительных недостатков:
- Заголовки, в отличие от тела сообщения, передавались в несжатом виде.
- Часто большая часть заголовков в сообщениях совпадала, но они продолжали передаваться по сети.
- Отсутствовала возможность так называемого мультиплексирования — механизма, позволяющего объединить несколько соединений в один поток данных. Приходилось открывать несколько соединений на сервере для обработки входящих запросов.
С выходом HTTP/2 было предложено следующее решение: HTTP/1.X-сообщения разбивались на так называемые фреймы, которые встраивались в поток данных.
Фреймы данных (тела сообщения) отделялись от фреймов заголовка, что позволило применять сжатие. Вместе с появлением потоков появился и ранее описанный механизм мультиплексирования — теперь можно было обойтись одним соединением для нескольких потоков.
Единственное о чем стоит сказать в завершение темы: HTTP/2 перестал быть текстовым протоколом, а стал работать с «сырой» двоичной формой данных. Это ограничивает чтение и создание HTTP-сообщений «вручную». Однако такова цена за возможность реализации более совершенной оптимизации и повышения производительности.
Заголовки
HTTP-заголовок представляет собой строку формата «Имя-Заголовок:Значение», с двоеточием(:) в качестве разделителя. Название заголовка не учитывает регистр, то есть между Host и host, с точки зрения HTTP, нет никакой разницы. Однако в названиях заголовков принято начинать каждое новое слово с заглавной буквы. Структура значения зависит от конкретного заголовка. Несмотря на то, что заголовок вместе со значениями может быть достаточно длинным, занимает он всего одну строчку.
В запросах может передаваться большое число различных заголовков, но все их можно разделить на три категории:
- Общего назначения, которые применяются ко всему сообщению целиком.
- Заголовки запроса уточняют некоторую информацию о запросе, сообщая дополнительный контекст или ограничивая его некоторыми логическими условиями.
- Заголовки представления, которые описывают формат данных сообщения и используемую кодировку. Добавляются к запросу только в тех случаях, когда с ним передается некоторое тело.
Ниже можно видеть пример заголовков в запросе:
Самые частые заголовки запроса
Заголовок | Описание |
Host | Используется для указания того, с какого конкретно хоста запрашивается ресурс. В качестве возможных значений могут использоваться как доменные имена, так и IP-адреса. На одном HTTP-сервере может быть размещено несколько различных веб-сайтов. Для обращения к какому-то конкретному требуется данный заголовок. |
User-Agent | Заголовок используется для описания клиента, который запрашивает ресурс. Он содержит достаточно много информации о пользовательском окружении. Например, может указать, какой браузер используется в качестве клиента, его версию, а также операционную систему, на которой этот клиент работает. |
Refer | Используется для указания того, откуда поступил текущий запрос. Например, если вы решите перейти по какой-нибудь ссылке в этой статье, то вероятнее всего к запросу будет добавлен заголовок Refer: https://selectel.ru |
Accept | Позволяет указать, какой тип медиафайлов принимает клиент. В данном заголовке могут быть указаны несколько типов, перечисленные через запятую (‘ , ‘). А для указания того, что клиент принимает любые типы, используется следующая последовательность — */*. |
Cookie | Данный заголовок может содержать в себе одну или несколько пар «Куки-Значение» в формате cookie=value. Куки представляют собой небольшие фрагменты данных, которые хранятся как на стороне клиента, так и на сервере, и выступают в качестве идентификатора. Куки передаются вместе с запросом для поддержания доступа клиента к ресурсу. Помимо этого, куки могут использоваться и для других целей, таких как хранение пользовательских предпочтений на сайте и отслеживание клиентской сессии. Несколько кук в одном заголовке могут быть перечислены с помощью символа точка с запятой (‘ ; ‘), который используется как разделитель. |
Authorization | Используется в качестве еще одного метода идентификации клиента на сервере. После успешной идентификации сервер возвращает токен, уникальный для каждого конкретного клиента. В отличие от куки, данный токен хранится исключительно на стороне клиента и отправляется клиентом только по запросу сервера. Существует несколько типов аутентификации, конкретный метод определяется тем веб-сервером или веб-приложением, к которому клиент обращается за ресурсом. |
Тело запроса
Завершающая часть HTTP-запроса — это его тело. Не у каждого HTTP-метода предполагается наличие тела. Так, например, методам вроде GET, HEAD, DELETE, OPTIONS обычно не требуется тело. Некоторые виды запросов могут отправлять данные на сервер в теле запроса: самый распространенный из таких методов — POST.
Ответы HTTP
HTTP-ответ является сообщением, которое сервер отправляет клиенту в ответ на его запрос. Его структура равна структуре HTTP-запроса: стартовая строка, заголовки и тело.
Строка статуса (Status line)
Стартовая строка HTTP-ответа называется строкой статуса (status line). На ней располагаются следующие элементы:
- Уже известная нам по стартовой строке запроса версия протокола (HTTP/2 или HTTP/1.1).
- Код состояния, который указывает, насколько успешно завершилась обработка запроса.
- Пояснение — короткое текстовое описание к коду состояния. Используется исключительно для того, чтобы упростить понимание и восприятие человека при просмотре ответа.
Коды состояния и текст статуса
Коды состояния HTTP используются для того, чтобы сообщить клиенту статус их запроса. HTTP-сервер может вернуть код, принадлежащий одной из пяти категорий кодов состояния:
Категория | Описание |
1xx | Коды из данной категории носят исключительно информативный характер и никак не влияют на обработку запроса. |
2xx | Коды состояния из этой категории возвращаются в случае успешной обработки клиентского запроса. |
3xx | Эта категория содержит коды, которые возвращаются, если серверу нужно перенаправить клиента. |
4xx | Коды данной категории означают, что на стороне клиента был отправлен некорректный запрос. Например, клиент в запросе указал не поддерживаемый метод или обратился к ресурсу, к которому у него нет доступа. |
5xx | Ответ с кодами из этой категории приходит, если на стороне сервера возникла ошибка. |
Полный список кодов состояния доступен в спецификации к протоколу, ниже приведены только самые распространенные коды ответов:
Категория | Описание |
200 OK | Возвращается в случае успешной обработки запроса, при этом тело ответа обычно содержит запрошенный ресурс. |
302 Found | Перенаправляет клиента на другой URL. Например, данный код может прийти, если клиент успешно прошел процедуру аутентификации и теперь может перейти на страницу своей учетной записи. |
400 Bad Request | Данный код можно увидеть, если запрос был сформирован с ошибками. Например, в нем отсутствовали символы завершения строки. |
403 Forbidden | Означает, что клиент не обладает достаточными правами доступа к запрошенному ресурсу. Также данный код можно встретить, если сервер обнаружил вредоносные данные, отправленные клиентом в запросе. |
404 Not Found | Каждый из нас, так или иначе, сталкивался с этим кодом ошибки. Данный код можно увидеть, если запросить у сервера ресурс, которого не существует на сервере. |
500 Internal Error | Данный код возвращается сервером, когда он не может по определенным причинам обработать запрос. |
Помимо основных кодов состояния, описанных в стандарте, существуют и коды состояния, которые объявляются крупными сетевыми провайдерами и серверными платформами. Так, коды состояния, используемые Selectel, можно посмотреть здесь.
Заголовки ответа
Response Headers, или заголовки ответа, используются для того, чтобы уточнить ответ, и никак не влияют на содержимое тела. Они существуют в том же формате, что и остальные заголовки, а именно «Имя-Значение» с двоеточием (:) в качестве разделителя.
Ниже приведены наиболее часто встречаемые в ответах заголовки:
Категория | Пример | Описание |
Server | Server: ngnix | Содержит информацию о сервере, который обработал запрос. |
Set-Cookie | Set-Cookie:PHPSSID=bf42938f | Содержит куки, требуемые для идентификации клиента. Браузер парсит куки и сохраняет их в своем хранилище для дальнейших запросов. |
WWW-Authenticate | WWW-Authenticate: BASIC realm=»localhost» | Уведомляет клиента о типе аутентификации, который необходим для доступа к запрашиваемому ресурсу. |
Тело ответа
Последней частью ответа является его тело. Несмотря на то, что у большинства ответов тело присутствует, оно не является обязательным. Например, у кодов «201 Created» или «204 No Content» тело отсутствует, так как достаточную информацию для ответа на запрос они передают в заголовке.
Безопасность HTTP-запросов, или что такое HTTPs
HTTP является расширяемым протоколом, который предоставляет огромное количество возможностей, а также поддерживает передачу всевозможных типов файлов. Однако, вне зависимости от версии, у него есть один существенный недостаток, который можно заметить если перехватить отправленный HTTP-запрос:
Да, все верно: данные передаются в открытом виде. HTTP сам по себе не предоставляет никаких средств шифрования.
Но как же тогда работают различные банковские приложения, интернет-магазины, сервисы оплаты услуг и прочие приложения, в которых циркулирует чувствительная информация пользователей?
Время рассказать про HTTPs!
HTTPs (HyperText Transfer Protocol, secure) является расширением HTTP-протокола, который позволяет шифровать отправляемые данные, перед тем как они попадут на транспортный уровень. Данный протокол по умолчанию использует порт 443.
Теперь если мы перехватим не HTTP , а HTTPs-запрос, то не увидим здесь ничего интересного:
Данные передаются в едином зашифрованном потоке, что делает невозможным получение учетных данных пользователей и прочей критической информации средствами обычного перехвата.
Повысьте безопасность на сетевых портах с Selectel
Три межсетевых экрана для любых потребностей бизнеса.
Выбрать
Если хотите подробнее узнать о деталях работы протокола, читайте статью в нашем блоге →
Как отправить HTTP-запрос и прочитать его ответ
Теория это, конечно, отлично, но ничего так хорошо не закрепляет материал, как практика
Мы рассмотрим несколько способов, как написать HTTP-запрос в браузер, послать HTTP-запрос на сервер и получить ответ:
- Инструменты разработчика в браузере.
- Утилита cURL.
Инструменты разработчика
Основной программой на наших устройствах, которая работает с HTTP-протоколом, в большинстве случаев является браузер. Помимо обычных пользователей, с браузерами часто работают и разработчики веб-приложений. Именно их инструментами мы воспользуемся для работы с запросами и ответами.
По нажатию комбинации клавиш [Ctrl+Shift+I] или просто [F12] в подавляющем большинстве современных браузеров у нас откроется окно инструментов разработчика, которая представляет собой панель с несколькими вкладками. Нужная нам вкладка обычно называется Network. Перейдем в нее, а в адресной строке введем URL того сайта, на который хотим попасть. В качестве примера воспользуемся сайтом блога Selectel — https://selectel.ru/blog/.
После нажатия Enter сайт начнет загружаться, а открытая вкладка Network — заполняться различными элементами, начиная все больше напоминать приборную панель самолета.
Не спешите пугаться. Это всего лишь список ресурсов, которые нужны для правильного отображения и работы сайта.
Нажав на любой из них, мы можем увидеть детали обработки отправленного запроса:
В данном запросе, например:
- URL, к которому было совершено обращение — https://selectel.ru/blog,
- Метод, который был использован в запросе — GET,
- И в качестве кода возврата сервер вернул нам страницу с кодом статуса — 200 OK
Утилита cURL
Следующий инструмент, с помощью которого мы сможем послать запрос на тот или иной сервер, это утилита cURL.
cURL (client URL) является небольшой утилитой командной строки, которая позволяет работать с HTTP и рядом других протоколов.
Для отправки запроса и получения ответа мы можем воспользоваться флагом -v и указанием URL того ресурса, который мы хотим получить. «Схему» HTTP-запроса можно увидеть на скрине ниже:
После запуска утилита выполняет:
- подключение к серверу,
- самостоятельно разрешает все вопросы, необходимые для отправки запроса по HTTPs,
- отправляет запрос, содержимое которого мы можем видеть благодаря флагу -v,
- принимая ответ от сервера, отображает его в командной строке «как-есть».
Помимо этого, у данной утилиты есть огромное количество опций, которые предоставляют возможности по детальной настройке отправляемых запросов. Все эти возможности и делают ее такой популярной у веб-разработчиков и других специалистов, которым приходится работать с протоколом HTTP.
Заключение
HTTP представляет собой расширяемый протокол прикладного уровня, на котором работает весь веб-сегмент интернета. В данной статье мы рассмотрели принцип его работы, структуру, «компоненты» HTTP-запросов. Коснулись вопросов отличия версий протокола, HTTPs — его расширения, добавляющего шифрование. Разобрали устройство запроса, поняли, как можно отправить HTTP-запрос и получить ответ от сервера.
Являетесь вы программистом или нет, вы видели его повсюду в Интернете. На данный момент в адресной строке браузера отображается нечто, что начинается с «https: //». Даже ваш первый скрипт Hello World отправил HTTP-header без вашего понимания. В этой статье мы собираемся узнать об основах HTTP-заголовков и о том, как их можно использовать в наших веб-приложениях.
Что такое HTTP Headers?
HTTP значит «Hypertext Transfer Protocol» (Протокол передачи гипертекста). Всемирная паутина использует этот протокол. Он был создан в начале 1990-х годов. Почти всё, что вы видите в вашем браузере, передаётся на ваш компьютер через HTTP. Например, когда вы открыли страницу этой статьи, ваш браузер отправил более 40 HTTP-запросов и получил HTTP-ответы для каждого из них.
Заголовки HTTP являются основной частью этих HTTP-запросов и ответов, и они несут информацию о браузере клиента, запрошенной странице, сервере и многом другом.
Пример
Когда вы вводите URL-адрес в адресной строке, ваш браузер отправляет HTTP-запрос, и он может выглядеть так:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 Host: net.tutsplus.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120 Pragma: no-cache Cache-Control: no-cache
Первая строка — это «Request Line», которая содержит некоторую базовую информацию по запросу. Остальные — HTTP заголовки.
После этого запроса ваш браузер получает ответ HTTP, который может выглядеть так:
HTTP/1.x 200 OK Transfer-Encoding: chunked Date: Sat, 28 Nov 2009 04:36:25 GMT Server: LiteSpeed Connection: close X-Powered-By: W3 Total Cache/0.8 Pragma: public Expires: Sat, 28 Nov 2009 05:36:25 GMT Etag: "pub1259380237;gz" Cache-Control: max-age=3600, public Content-Type: text/html; charset=UTF-8 Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT X-Pingback: https://net.tutsplus.com/xmlrpc.php Content-Encoding: gzip Vary: Accept-Encoding, Cookie, User-Agent <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Top 20+ MySQL Best Practices - Nettuts+</title> <!-- ... rest of the html ... -->
Первая строка — это «Строка состояния», за которой следуют «HTTP-заголовки», до пустой строки. После этого начинается «содержимое» (в данном случае — HTML вывод).
Когда вы смотрите на исходный код веб-страницы в своём браузере, вы видите только часть HTML, а не заголовки HTTP, хотя они фактически были переданы вместе.
Эти HTTP-запросы также отправляются и принимаются для других вещей, таких как изображения, CSS-файлы, файлы JavaScript и т. д. Именно поэтому я сказал ранее, что ваш браузер отправил не менее 40 или более HTTP-запросов, поскольку вы загрузили только эту страницу статьи.
Теперь давайте рассмотрим структуру более подробно.
Как увидеть HTTP Headers
Для анализа HTTP-заголовков я использую следующие расширения Firefox:
- Firebug
- Live HTTP Headers
В PHP:
- getallheaders() получает запросы headers. Вы можете использовать массив $_SERVER.
- headers_list() получает отзывы headers.
Далее в этой статье мы увидим примеры кода в PHP.
Структура запроса HTTP
Первая строка HTTP-запроса называется линией запроса и состоит из трёх частей:
- «method» указывает, какой это запрос. Наиболее распространённые методы GET, POST и HEAD.
- «path» , как правило, является частью URL-адреса, который идёт после host (домена). Например, если запрос «https://net.tutsplus.com/tutorials/other/top-20-mysql-best-practices/» , часть path будет «/tutorials/other/top-20-mysql-best-practices/».
- Часть «protocol» содержит «HTTP» и версию, которая обычно 1.1 в современных браузерах.
Остальная часть запроса содержит HTTP headers как пары «Name: Value» в каждой строке. Они содержат различную информацию о HTTP-запросе и вашем браузере. Например, строка «User-Agent» предоставляет информацию о версии браузера и операционной системе, которую вы используете. «Accept-Encoding» сообщает серверу, может ли ваш браузер принимать сжатый output, например gzip.
Возможно, вы заметили, что данные cookie также передаются внутри HTTP-заголовка. И если бы ссылочный url, это было бы в header тоже.
Большинство этих заголовков являются необязательными. Этот HTTP-запрос мог быть таким же маленьким:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 Host: net.tutsplus.com
И вы всё равно получите правильный ответ от веб-сервера.
Методы запроса
Три наиболее часто используемых метода запроса: GET, POST и HEAD. Вы, вероятно, уже знакомы с первыми двумя, начиная с написания html-форм.
GET: получение документа
Это основной метод, используемый для извлечения html, изображений, JavaScript, CSS и т. д. С использованием этого метода запрошено большинство данных, загружаемых в ваш браузер.
Например, при загрузке статьи Nettuts +, самая первая строка HTTP-запроса выглядит так:
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 ...
Как только html загрузится, браузер начнет отправлять GET-запрос изображений, который может выглядеть так:
GET /wp-content/themes/tuts_theme/images/header_bg_tall.png HTTP/1.1 ...
Веб-формы можно настроить под метод GET. Вот пример.
<form method="GET" action="foo.php"> First Name: <input type="text" name="first_name" /> <br /> Last Name: <input type="text" name="last_name" /> <br /> <input type="submit" name="action" value="Submit" /> </form>
Когда эта форма отправлена, HTTP-запрос начинается так:
GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1 ...
Вы можете видеть, что каждый ввод формы был добавлен в строку запроса.
POST: отправка данных на сервер
Даже если вы можете отправлять данные на сервер с помощью GET и строки запроса, во многих случаях POST будет предпочтительнее. Отправка больших объёмов данных с помощью GET нецелесообразна и имеет ограничения.
Запросы POST чаще всего отправляются веб-формами. Давайте изменим предыдущий пример формы на метод POST.
<form method="POST" action="foo.php"> First Name: <input type="text" name="first_name" /> <br /> Last Name: <input type="text" name="last_name" /> <br /> <input type="submit" name="action" value="Submit" /> </form>
Отправка этой формы создает HTTP-запрос следующим образом:
POST /foo.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost/test.php Content-Type: application/x-www-form-urlencoded Content-Length: 43 first_name=John&last_name=Doe&action=Submit
Здесь нужно отметить три важных момента:
- Путь в первой строке просто /foo.php, и больше нет строки запроса.
- Добавлены заголовки Content-Type и Content-Length, которые предоставляют информацию об отправляемых данных.
- Все данные теперь отправляются после заголовков, в том же формате, что и строка запроса.
Запросы POST метода также могут быть сделаны через AJAX, приложения, cURL и т. д. И все формы загрузки файлов необходимы для использования метода POST.
HEAD: получение информации заголовка
HEAD идентичен GET, за исключением того, что сервер не возвращает содержимое HTTP-ответа. Когда вы отправляете запрос HEAD, это означает, что вас интересуют только код ответа и HTTP headers, а не сам документ.
«Когда вы отправляете запрос HEAD, это означает, что вас интересуют только код ответа и HTTP headers, а не сам документ».
С помощью этого метода браузер может проверить, был ли документ изменён для целей caching. Он также может проверить, существует ли документ вообще.
Например, если у вас много ссылок на веб-сайте, вы можете периодически отправлять HEAD-запросы каждой из них, чтобы проверить наличие неработающих ссылок. Это будет намного быстрее, чем при использовании GET.
Структура ответа HTTP
После того, как браузер отправляет HTTP-запрос, сервер отвечает HTTP-ответом. Исключая контент, он выглядит так:
Первой порцией данных является протокол. Обычно это снова HTTP/1.x или HTTP/1.1 на современных серверах.
Следующая часть — это код состояния, за которым следует короткое сообщение. Код 200 означает, что наш запрос GET был успешным и сервер вернёт содержимое запрошенного документа сразу после headers.
Мы все видели «404» pages. Это число фактически приходит из части кода состояния HTTP-ответа. Если запрос GET будет создан для path, который сервер не может найти, он ответил бы 404, а не 200.
Остальная часть ответа содержит headers так же, как HTTP-запрос. Эти значения могут содержать информацию о софте сервера при последнем изменении страницы/файла, типе mime и прочее…
Опять же, большинство этих headers на самом деле являются необязательными.
Коды статуса HTTP
- 200 используются для успешных запросов.
- 300 для перенаправления.
- 400 используются, если возникла проблема с запросом.
- 500 используются, если возникла проблема с сервером.
200 OK
Как упоминалось ранее, этот код состояния отправляется в ответ на успешный запрос.
206 Partial Content
Если приложение запрашивает только диапазон запрошенного файла, возвращается код 206.
Это часто используется с менеджерами закачек, которые могут остановить и возобновить загрузку или разделить загрузку на части.
404 Not Found
Когда запрашиваемая страница или файл не найдена, сервер отправляет код ответа 404.
401 Unauthorized
Защищённые паролем веб-страницы отправляют этот код. Если вы не ввели логин правильно, вы можете увидеть следующее в вашем браузере.
Обратите внимание, что это относится только к страницам, защищённым паролем HTTP, которые вызывают запросы для входа следующим образом:
403 Forbidden
Если вам не разрешен доступ к странице, этот код может быть отправлен в ваш браузер. Это часто происходит, когда вы пытаетесь открыть URL-адрес для папки, в которой нет индексной страницы. Если параметры сервера не позволяют отображать содержимое папки, вы получите ошибку 403.
Например, на моем локальном сервере я создал папку изображений. Внутри этой папки я помещаю файл .htaccess с этой строкой: «Options -Indexes». Теперь, когда я пытаюсь открыть http://localhost/images/ — я вижу это:
Существуют другие способы блокировки доступа и 403 могут быть отправлены. Например, вы можете блокировать по IP-адресу с помощью некоторых директив htaccess.
order allow,deny deny from 192.168.44.201 deny from 224.39.163.12 deny from 172.16.7.92 allow from all
302 (or 307) Moved Temporarily & 301 Moved Permanently
Эти два кода используются для перенаправления браузера. Например, когда вы используете службу сокращения URL, такую как bit.ly, именно так они перенаправляют людей, которые идут по ссылке.
302 и 301 обрабатываются браузером очень похоже, но они могут иметь различные значения для spiders поисковых систем. Например, если ваш сайт не готов для обслуживания, вы можете перенаправить его в другое место с помощью 302. Поисковая система продолжит проверку вашей страницы в будущем. Но если вы перенаправите с использованием 301, это сообщит spider, что ваш сайт переехал в это место навсегда. За более точной информацией: http://www.nettuts.com перейдите на https://net.tutsplus.com/ используя 301 код вместо 302.
500 Internal Server Error
Этот код обычно отображается при сбое веб-скрипта. Большинство скриптов CGI не выводят ошибки непосредственно в браузер, в отличие от PHP. Если есть фатальные ошибки, они просто отправят код статуса 500. И тогда программист должен искать в журналах ошибок сервера, чтобы найти сообщения об ошибках.
Complete List
Вы можете найти полный список кодов состояния HTTP с их пояснениями here.
Заголовки HTTP в запросах HTTP
Теперь мы рассмотрим некоторые из наиболее распространенных HTTP headers , найденных в HTTP requests.
Почти все эти заголовки можно найти в массиве $ _SERVER в PHP. Вы также можете использовать функцию getallheaders() для извлечения всех заголовков одновременно.
Host
HTTP-запрос отправляется на определенные IP-адреса. Но так как большинство серверов способны размещать несколько сайтов под одним IP, они должны знать, какое доменное имя ищет браузер.
Host: net.tutsplus.com
Это в основном имя host, включая домен и поддомен.
В PHP его можно найти, как $_SERVER[‘HTTP_HOST’] или $_SERVER[‘SERVER_NAME’].
User-Agent
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Этот заголовок может содержать несколько частей информации, таких как:
- Имя и версия браузера.
- Название и версия операционной системы.
- Язык по умолчанию.
Именно так веб-сайты могут собирать определённую общую информацию о своих системах surfers. Например, они могут определить, использует ли surfer мобильный браузер и перенаправляет их на мобильную версию своего веб-сайта, который лучше работает с низким разрешением.
В PHP может быть выражен так: $_SERVER[‘HTTP_USER_AGENT’].
if ( strstr($_SERVER['HTTP_USER_AGENT'],'MSIE 6') ) { echo "Please stop using IE6!"; }
Accept-Language
Accept-Language: en-us,en;q=0.5
Этот заголовок отображает настройки языка по умолчанию. Если сайт имеет разные языковые версии, он может перенаправить нового surfer на основе этих данных.
Он может содержать несколько языков, разделённых запятыми. Первый — это предпочтительный язык, и каждый из перечисленных языков может иметь значение «q», которое представляет собой оценку предпочтения пользователя для языка (min. 0 max. 1).
В PHP его можно найти так: $ _SERVER [«HTTP_ACCEPT_LANGUAGE»].
if (substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) == 'fr') { header('Location: http://french.mydomain.com'); }
Accept-Encoding
Accept-Encoding: gzip,deflate
Большинство современных браузеров поддерживают gzip и отправляют это в header. Затем веб-сервер может отправить выходной HTML-код в сжатом формате. Это позволяет уменьшить размер до 80% для экономии пропускной способности и времени.
В PHP его можно найти так: $ _SERVER [«HTTP_ACCEPT_ENCODING»]. Однако, когда вы используете функцию обратного вызова ob_gzhandler(), она будет проверять значение автоматически, поэтому вам это не нужно.
// enables output buffering // and all output is compressed if the browser supports it ob_start('ob_gzhandler');
If-Modified-Since
Если веб-документ уже сохранен в кеше в браузере и вы посещаете его снова, ваш браузер может проверить, был ли документ обновлён, отправив следующее:
If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
Если он не изменялся с этой даты, сервер отправляет код ответа «304 Not Modified», а содержимое — нет, и браузер загружает содержимое из cache.
В PHP его можно найти так: $ _SERVER [‘HTTP_IF_MODIFIED_SINCE’].
// assume $last_modify_time was the last the output was updated // did the browser send If-Modified-Since header? if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // if the browser cache matches the modify time if ($last_modify_time == strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // send a 304 header, and no content header("HTTP/1.1 304 Not Modified"); exit; } }
Существует также HTTP-заголовок Etag, который можно использовать для проверки текущего кэша. Мы поговорим об этом в ближайшее время.
Cookie
Как следует из названия, это отправляет файлы cookie, хранящиеся в вашем браузере для этого домена.
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120; foo=bar
Это пары name=value, разделённые точками с запятой. Cookies могут также содержать id сеанса.
В PHP отдельные cookie-файлы могут быть доступны с помощью массива $ _COOKIE. Вы можете напрямую обращаться к переменным сеанса, используя массив $ _SESSION, и если вам нужен id сеанса, вы можете использовать функцию session_id () вместо cookie.
echo $_COOKIE['foo']; // output: bar echo $_COOKIE['PHPSESSID']; // output: r2t5uvjq435r4q7ib3vtdjq120 session_start(); echo session_id(); // output: r2t5uvjq435r4q7ib3vtdjq120
Referer
Как следует из названия, этот HTTP header содержит ссылочный url.
Например, если я зашел на домашнюю страницу Nettuts + и нажал ссылку на статью, этот header будет отправлен в мой браузер:
Referer: https://net.tutsplus.com/
В PHP его можно найти как $ _SERVER [‘HTTP_REFERER’].
if (isset($_SERVER['HTTP_REFERER'])) { $url_info = parse_url($_SERVER['HTTP_REFERER']); // is the surfer coming from Google? if ($url_info['host'] == 'www.google.com') { parse_str($url_info['query'], $vars); echo "You searched on Google for this keyword: ". $vars['q']; } } // if the referring url was: // http://www.google.com/search?source=ig&hl=en&rlz=&=&q=http+headers&aq=f&oq=&aqi=g-p1g9 // the output will be: // You searched on Google for this keyword: http headers
Возможно, вы заметили, что слово «referrer» написано с ошибкой, как «referer». К сожалению, он превратился в официальную спецификацию HTTP подобным образом и застрял.
Authorization
Когда веб-страница запрашивает авторизацию, браузер открывает окно входа в систему. Когда вы вводите имя пользователя и пароль в этом окне, браузер отправляет другой HTTP-запрос, но на этот раз он содержит этот header
Authorization: Basic bXl1c2VyOm15cGFzcw==
Данные внутри header имеют кодировку base64. Например, base64_decode (‘bXl1c2VyOm15cGFzcw ==’) возвратит ‘myuser: mypass’
В PHP эти значения можно найти как $ _SERVER [‘PHP_AUTH_USER’] и $ _SERVER [‘PHP_AUTH_PW’].
Подробнее об этом будет, когда мы поговорим о заголовке WWW-Authenticate.
Заголовки HTTP в ответах HTTP
Теперь мы рассмотрим некоторые из наиболее распространенных HTTP headers, найденных в HTTP-ответах.
В PHP вы можете установить заголовки ответа, используя функцию header(). PHP уже отправляет определённые заголовки автоматически, для загрузки содержимого и настройки файлов cookie и прочее… Вы можете увидеть headers, которые отправляются или будут отправляться с помощью функции headers_list (). Вы можете проверить, были ли уже отправлены заголовки с помощью функции headers_sent().
Cache-Control
Определение из w3.org: «Поле заголовка Cache-Control используется для указания директив, которые ДОЛЖНЫ выполняться всеми механизмами кэширования по цепочке запросов/ответов». Эти «механизмы кэширования» включают шлюзы и прокси, которые может использовать ваш интернет-провайдер.
Пример:
Cache-Control: max-age=3600, public
«public» означает, что ответ может быть кэширован кем угодно. «max-age» указывает, сколько секунд действителен кеш. Разрешение кэширования вашего сайта может снизить нагрузку на сервер и пропускную способность, а также увеличить время загрузки в браузере.
Кэширование также может быть предотвращено с помощью директивы «no-cache».
Cache-Control: no-cache
Подробности смотрите в w3.org.
Content-Type
Этот header указывает «mime-type» документа. Затем браузер определяет, как интерпретировать содержимое на основании этого. Например, страница html (или PHP-скрипт с выходом html) может возвращать это:
Content-Type: text/html; charset=UTF-8
«text» — это тип, а «html» — подтип документа. Заголовок также может содержать больше информации, такой как charset.
Для gif-изображения это может быть отправлено.
Content-Type: image/gif
Браузер может использовать внешнее приложение или расширение браузера на основе mime-type. Например, это приведет к загрузке Adobe Reader:
Content-Type: application/pdf
При загрузке напрямую Apache обычно может обнаружить mime-тип документа и отправить соответствующий header. Кроме того, большинство браузеров имеют некоторую степень отказоустойчивости и автоопределение типов mime, если заголовки указаны неверно или отсутствуют.
Вы можете найти список общих типов mime here.
В PHP вы можете использовать функцию finfo_file() для определения mime-типа файла.
Content-Disposition
Этот header указывает браузеру открыть окно загрузки файла, вместо того, чтобы пытаться проанализировать содержимое. Пример:
Content-Disposition: attachment; filename="download.zip"
Это заставит браузер сделать это:
Обратите внимание, что соответствующий заголовок Content-Type также должен быть отправлен вместе с этим:
Content-Type: application/zip Content-Disposition: attachment; filename="download.zip"
Content-Length
Когда контент будет передаваться браузеру, сервер может указать его размер (в байтах), используя этот header.
Content-Length: 89123
Это особенно полезно при загрузке файлов. Именно так браузер может определить ход загрузки.
Например, вот сценарий-макет, который я написал, имитирует медленную загрузку.
// it's a zip file header('Content-Type: application/zip'); // 1 million bytes (about 1megabyte) header('Content-Length: 1000000'); // load a download dialogue, and save it as download.zip header('Content-Disposition: attachment; filename="download.zip"'); // 1000 times 1000 bytes of data for ($i = 0; $i < 1000; $i++) { echo str_repeat(".",1000); // sleep to slow down the download usleep(50000); }
Вот результат:
Теперь я собираюсь закомментировать заголовок Content-Length
// it's a zip file header('Content-Type: application/zip'); // the browser won't know the size // header('Content-Length: 1000000'); // load a download dialogue, and save it as download.zip header('Content-Disposition: attachment; filename="download.zip"'); // 1000 times 1000 bytes of data for ($i = 0; $i < 1000; $i++) { echo str_repeat(".",1000); // sleep to slow down the download usleep(50000); }
Теперь результат такой:
Браузер может только сказать, сколько байтов было загружено, но он не знает общую сумму. И индикатор выполнения не показывает прогресс.
Etag
Это еще один header, который используется для кеширования. Это выглядит так:
Etag: "pub1259380237;gz"
Веб-сервер может отправлять этот header с каждым документом, который он обслуживает. Значение может быть основано на последней изменённой дате, размере файла или даже контрольной сумме файла. Браузер затем сохраняет это значение, так как он кэширует документ. В следующий раз, когда браузер запрашивает тот же файл, он отправляет это в HTTP-запросе:
If-None-Match: "pub1259380237;gz"
Если значение Etag документа совпадает с этим, сервер будет отправлять код 304 вместо 200, и никакого содержимого. Браузер будет загружать содержимое из своего кеша.
Last-Modified
Как следует из названия, этот header указывает дату последнего изменения документа в формате GMT:
Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT
$modify_time = filemtime($file); header("Last-Modified: " . gmdate("D, d M Y H:i:s", $modify_time) . " GMT");
Это предлагает браузеру другой способ для cache документа. Браузер может отправить это в HTTP-запросе:
If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
Мы уже говорили об этом ранее в разделе «If-Modified-Since».
Location
Этот заголовок используется для перенаправления. Если код ответа 301 или 302, сервер также должен отправить этот header. Например, когда вы перейдете на страницу http://www.nettuts.com, ваш браузер получит следующее:
HTTP/1.x 301 Moved Permanently ... Location: https://net.tutsplus.com/ ...
В PHP вы можете перенаправить surfer так:
header('Location: https://net.tutsplus.com/');
По умолчанию, это отправит 302 код ответа. Если вы хотите вместо 301 отправить:
header('Location: https://net.tutsplus.com/', true, 301);
Set-Cookie
Когда веб-сайт хочет установить или обновить файл cookie в вашем браузере, он будет использовать этот header.
Set-Cookie: skin=noskin; path=/; domain=.amazon.com; expires=Sun, 29-Nov-2009 21:42:28 GMT Set-Cookie: session-id=120-7333518-8165026; path=/; domain=.amazon.com; expires=Sat Feb 27 08:00:00 2010 GMT
Каждый файл cookie отправляется как отдельный header. Обратите внимание, что файлы cookie, установленные с помощью JavaScript, не проходят через HTTP headers.
В PHP вы можете установить cookie-файлы, используя функцию setcookie(), а PHP отправляет соответствующие HTTP headers.
setcookie("TestCookie", "foobar");
Что приводит к отправке этого заголовка:
Set-Cookie: TestCookie=foobar
Если дата истечения срока действия не указана, cookie удаляется, когда окно браузера закрыто.
WWW-Authenticate
Сайт может отправить этот header для аутентификации пользователя через HTTP. Когда браузер увидит этот header, он откроет диалоговое окно входа в систему.
WWW-Authenticate: Basic realm="Restricted Area"
Что будет выглядеть так:
В руководстве PHP есть section, в котором приведены образцы кода, как это сделать в PHP.
if (!isset($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); echo 'Text to send if user hits Cancel button'; exit; } else { echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>"; echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>"; }
Content-Encoding
Этот header обычно устанавливается, когда возвращаемое содержимое сжимается.
Content-Encoding: gzip
В PHP, если вы используете функцию обратного вызова ob_gzhandler(), она будет автоматически установлена.
Заключение
Спасибо за прочтение. Надеюсь, эта статья послужит хорошей отправной точкой для изучения HTTP Headers. Пожалуйста, оставьте свои комментарии и вопросы ниже, и я постараюсь дать как можно больше ответов.
1. Протокол HTTP. Введение
Сразу хочу уточнить одну маленькую вещь. Страшное слово протокол есть не что иное, как соглашение множества людей, просто в один прекрасный момент люди решили: «Давайте будем делать так, и тогда все будет в порядке». Бояться нечего, все просто до безобразия и это безобразие мы сейчас будем вскрывать. Итак, что же это такое протокол HTTP и с чем его едят?
1.1 Клиент и сервер
Чудес в мире, а тем более в мире программизма и интернета не бывает! Усвойте это как незыблемую истину. И, если программа не работает или работает не так как хочется, то, значит, скорее всего, она либо написана не правильно, либо содержит ошибки. Итак, как же все-таки браузер просит сервер прислать ему хоть что-нибудь? Да очень просто! Надо только немного расслабиться и начать получать удовольствие от процесса 🙂
1.2. Пишем наш первый HTTP запрос
Если Вы думаете, что все слишком сложно, то Вы ошибаетесь. Человек так устроен, что просто не способен создавать что-то сложное, иначе он сам в этом запутается 🙂 Итак, есть браузер и есть Web-сервер. Инициатором обмена данными всегда выступает браузер. Web-сервер никому, никогда просто так ничего не пошлет, чтобы он что-нибудь отправил браузеру — надо, чтобы браузер об этом попросил. Простейший HTTP запрос моет выглядеть, например, так:
GET http://www.php.net/ HTTP/1.0rnrn
* GET (В переводе с английского означает «получить») — тип запроса, тип запроса может быть разным, например POST, HEAD, PUT, DELETE (часть из них мы рассмотрим ниже).
* http://www.php.net/ — URI (адрес) от которого мы хотим получить хоть какую-нибудь информацию (естественно мы надеемся подучить HTML страницу).
* HTTP/1.0 — тип и версия протокола, который мы будем использовать в процессе общения с сервером.
* rn — конец строки, который необходимо повторить два раза, зачем, станет понятно немного позднее.
Вы можете выполнить данный запрос очень просто. Запустите программу telnet.exe, введите в качестве хоста www.php.net, укажите порт 80, и просто наберите данный запрос, нажав два раза Enter в качестве rnrn. В ответ вы получите HTML код главной страницы сайта www.php.net.
1.3 Структура запроса
Рассмотрим, из чего состоит HTTP запрос. Все достаточно просто. Начнем с того, что HTTP запрос — это вполне осмысленный текст. Из чего же он состоит в общем случае? Будем рассматривать протокол HTTP 1.0. Итак:
Request-Line [ General-Header | Request-Header | Entity-Header ]rn[ Entity-Body ]
* Request-Line — строка запроса
*
Формат: «Method Request-URI HTTP-Versionrn»
* Method — метод, которым будет обрабатываться ресурс Request-URI, может быть GET, POST, PUT, DELETE или HEAD.
* Request-URI — относительная или абсолютная ссылка на страницу с набором параметров, например, /index.html или http://www.myhost.ru/index.html или /index.html?a=1&b=qq. В последнем случае серверу будет передан запрос с набором переменных a и b с соответствующими значениями, а знак «&» — амперсант служит разделителем между параметрами.
* HTTP-Version — версия HTTP протокола, в нашем случае «HTTP/1.0».
Нам крайне интересны методы обработки GET и POST. Методом GET можно просто передать параметры в скрипт, а методом POST можно эмулировать submit формы.
Для метода GET, Request-URI может выглядеть, например, так: «/index.html?param1=1¶m2=2».
* General-Header — главная часть заголовка.
Формат: [Date: valuen | Pragma: no-cachen]
Может иметь только два параметра: Date или Pragma. Date — дата по Гринвичу в формате «День недели, Число Месяц Год ЧЧ:ММ:СС GMT», например, «Tue, 15 Nov 1994 08:12:31 GMT» — дата создания запроса. Pragma может иметь одно значение no-cache, которое запрещает кэширование страницы.
* Request-Header — часть заголовка, описывающая запрос.
Request-Header может иметь следующие параметры: Allow, Authorization, From, If-Modified-Since, Referer, User-Agent.
В данной главе мы не будем рассматривать параметр Autorization, так как он используется для доступа к закрытым ресурсам, что требуется не так уж часто. Вы можете самостоятельно изучить формирование заголовка для авторизованного доступа на сайте www.w3c.org.
* Allow — задает допустимые методы обработки.
Формат: «Allow: GET | HEADn».
Параметр игнорируется при указании метода обработки POST в Request-Line. Задает допустимые методы обработки запроса. Прокси сервера не модифицируют параметр Allow и он в неизменном виде доходит до сервера.
* From — e-mail адрес, пославшего запрос.
Формат: «From: adderssrn».
Например, «From: myname@mailserver.rurn».
* If-Modified-Since — указывает, что запрос не модифицировался с такого-то времени.
Формат: «If-Modified-Since: datern»
Используется только для метода обработки GET. Дата указывается по Гринвичу в таком же формате, как и для параметра Date в General-Header.
* Referrer — абсолютная ссылка на страницу, с которой произошла инициация запроса, т. е. ссылка на страницу, с которой пользователь перешел на нашу.
Формат: «Referrer: urln».
Пример: «Referrer: www.host.ru/index.htmln».
* User-Agent — тип браузера.
Например: «User-Agent: Mozilla/4.0n»
* Entity-Header — часть заголовка, описывающая данные Entity-Body.
В данной части запроса задаются параметры, которые описывают тело страницы. Entity-Header может содержать следующие параметры: Allow, Content-Encoding, Content-Length, Content-Type, Expires, Last-Modified, extension-header.
* Allow — параметр аналогичный Allow из General-Header.
* Content-Encoding — тип кодирования данных Entity-Body.
Формат: «Сontent-Encoding: x-gzip | x-compress | другой типn».
Пример: «Сontent-Encoding: x-gzipn». Символ «|» означает слово «или», то есть то или то или то и.т.д.
Другой тип может указывать на способ кодирования данных, например, для метода POST: «Сontent-Encoding: application/x-www-form-urlencodedn».
* Content-Length — количество байт, пересылаемых в Entity-Body. Значение Content-Length имеет совсем другой смысл для данных, пересылаемых в формате MIME, где он выступает как параметр описания части данных — «external/entity-body». Допустимыми являются целые числа от нуля и больше.
Пример: «Content-Length: 26457n».
* Content-Type — тип передаваемых данных.
Например: «Content-Type: text/htmln».
* Expires — Время, когда страница должна быть удалена из кэша браузера.
Формат: «Expires: daten». Формат даты алогичен формату даты для параметра Date из General-Header.
* Last-Modified — время последнего изменения пересылаемых данных.
Формат: «Last-Modified: daten». Формат даты алогичен формату даты для параметра Date из General-Header.
* Extention-header — часть заголовка, которая может предназначаться, например, для обработки браузером, или другой программой, которая принимает документ. В данной части можно описывать свои параметры в формате «ParameterName: parametervaluen». Данные параметры будут игнорироваться, если программа-клиент не знает, как их обработать.
Например: «Cookie: r=1rn» — устанавливает всем известные печеньки для страницы.
А теперь после таких страшных слов давайте попробуем немного успокоиться и понять, что же нам надо? Понимать мы естественно будем на примерах.
Давайте представим, что нам надо получить страницу с сайта, передав Cookies (Печеньки), иначе нас просто пошлют как незванных гостей, и более того, известно, что на данную страницу пускают только после того, как Вы побывали на главной странице сайта.
2 Метод GET
Напишем наш запрос.
GET http://www.site.ru/news.html HTTP/1.0rn Host: www.site.rurn Referer: http://www.site.ru/index.htmlrn Cookie: income=1rn rn
Данный запрос говорит нам о том, что мы хотим получить содержимое страницы по адресу http://www.site.ru/news.html, использую метод GET. Поле Host говорит о том, что данная страница находится на сервере www.site.ru, поле Referer говорит о том, что за новостями мы пришли с главной страницы сайта, а поле Cookie говорит о том, что нам была присвоена такая-то кука. Почему так важны поля Host, Referer и Сookie? Потому что нормальные программисты при создании динамических сайтов проверяют данные поля, которые появляются в скриптах (РНР в том числе) в виде переменных. Для чего это надо? Для того, например, чтобы сайт не грабили, т.е. не натравливали на него программу для автоматического скачивания, или для того, чтобы зашедший на сайт человек всегда попадал бы на него только с главной страницы и.т.д.
Теперь давайте представим, что нам надо заполнить поля формы на странице и отправить запрос из формы, пусть в данной форме будет два поля: login и password (логин и пароль),- и, мы естественно знаем логин и пароль.
GET http://www.site.ru/news.html?login=Petya%20Vasechkin&password=qq HTTP/1.0rn Host: www.site.rurn Referer: http://www.site.ru/index.htmlrn Cookie: income=1rn rn
Логин у нас «Petya Vasechkin» Почему же мы должны писать Petya%20Vasechkin? Это из=за того, что специальные символы могут быть распознаны сервером, как признаки наличия нового параметра или конца запроса и.т.д. Поэтому существует алгоритм кодирования имен параметров и их значений, во избежание оштбочных ситуаций в запросе. Полное описание данного алгоритма можно найти здесь, а в PHP есть функции rawurlencode и rawurldecode для кодирования и декодирования соответственно. Хочу отметеить, что декодирование РНР делает сам, если в запросе были переданы закодированные параметры. На этом я закону первую главу знакомства c протоколом HTTP. В следуючей главе мы рассмотрим построение запросов типа POST (в переводе с английского — «отправить»), что будет гораздо интереснее, т.к. именно данный тип запросов используется при отправке данных из HTML форм.
3. Метод POST.
В случае HTTP запроса типа POST существует два варианта передачи полей из HTML форм, а именно, используя алгоритм application/x-www-form-urlencoded и multipart/form-data. Различия между данными алгоритмами весьма существенные. Дело в том, что алгоритм первого типа создавался давным-давно, когда в языке HTML еще не предусматривали возможность передачи файлов через HTML формы. Итак, давайте рассмотрим эти алгоритмы на примерах.
3.1 Content-Type: application/x-www-form-urlencoded.
Пишем запрос, аналогичный нашему запросу GET для передачи логина и пароля, который был рассмотрен в предыдущей главе:
POST http://www.site.ru/news.html HTTP/1.0rn Host: www.site.rurn Referer: http://www.site.ru/index.htmlrn Cookie: income=1rn Content-Type: application/x-www-form-urlencodedrn Content-Length: 35rn rn login=Petya%20Vasechkin&password=qq
Здесь мы видим пример использования Content-Type и Content-Length полей заголовка. Content-Length говорит, сколько байт будет занимать область данных, которая отделяется от заголовка еще одним переводом строки rn. А вот параметры, которые раньше для запроса GET помещались в Request-URI, теперь находятся в Entity-Body. Видно, что они формируются точно также, просто надо написать их после заголовка. Хочу отметить еще один важный момент, ничто не мешает, одновременно с набором параметров в Entity-Body, помещать параметры с другими именами в Request-URI, например:
POST http://www.site.ru/news.html?type=user HTTP/1.0rn ..... rn login=Petya%20Vasechkin&password=qq
3.2 Content-Type: multipart/form-data
Как только интернет мир понял, что неплохо бы было через формы отсылать еще и файлы, так W3C консорциум взялся за доработку формата POST запроса. К тому времени уже достаточно широко применялся формат MIME (Multipurpose Internet Mail Extensions — многоцелевые расширения протокола для формирования Mail сообщений), поэтому, чтобы не изобретать велосипед заново, решили использовать часть данного формата формирования сообщений для создания POST запросов в протоколе HTTP.
Каковы же основные отличия этого формата от типа application/x-www-form-urlencoded?
Главное отличие в том, что Entity-Body теперь можно поделить на разделы, которые разделяются границами (boundary). Что самое интересное — каждый раздел может иметь свой собственный заголовок для описания данных, которые в нем хранятся, т.е. в одном запросе можно передавать данные различных типов (как в Mail письме Вы одновременно с текстом можете передавать файлы).
Итак, приступим. Рассмотрим опять все тот же пример с передачей логина и пароля, но теперь в новом формате.
POST http://www.site.ru/news.html HTTP/1.0rn Host: www.site.rurn Referer: http://www.site.ru/index.htmlrn Cookie: income=1rn Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467Arn Content-Length: 209rn rn --1BEF0A57BE110FD467Arn Content-Disposition: form-data; name="login"rn rn Petya Vasechkinrn --1BEF0A57BE110FD467Arn Content-Disposition: form-data; name="password"rn rn qqrn --1BEF0A57BE110FD467A--rn
Теперь давайте разбираться в том что написано. 🙂 Я специально выделил некоторые символы rn жирным, чтобы они не сливались с данными. Присмотревшись внимательно можно заметить поле boundary после Content-Type. Это поле задает разделитель разделов — границу. В качестве границы может быть использована строка, состоящая из латинских букв и цифр, а так же из еще некоторых символов (к сожалению, не помню каких еще). В теле запроса в начало границы добавляется ‘—‘, а заканчивается запрос — границей, к которой символы ‘—‘ добавляются еще и в конец. В нашем запросе два раздела, первый описывает поле login, а второй поле password. Content-Disposition (тип данных в разделе) говорит, что это будут данные из формы, а в поле name задается имя поля. На этом заголовок раздела заканчивается и далее следует область данных раздела, в котором помещается значение поля (кодировать значение не требуется!).
Хочу обратить Ваше внимание та то, что в заголовках разделов не надо использовать Content-Length, а вот в заголовке запроса надо и его значение является размером всего Entity-Body, стоящего после второго rn, следующего за Content-Length: 209rn. Т.е. Entity-Body отделяется от заголовка дополнительным переводом строки (что можно заметить и в разделах).
А теперь давайте напишем запрос для передачи файла.
POST http://www.site.ru/postnews.html HTTP/1.0rn Host: www.site.rurn Referer: http://www.site.ru/news.htmlrn Cookie: income=1rn Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467Arn Content-Length: 491rn rn --1BEF0A57BE110FD467Arn Content-Disposition: form-data; name="news_header"rn rn Пример новостиrn --1BEF0A57BE110FD467Arn Content-Disposition: form-data; name="news_file"; filename="news.txt"rn Content-Type: application/octet-streamrn Content-Transfer-Encoding: binaryrn rn А вот такая новость, которая лежит в файле news.txtrn --1BEF0A57BE110FD467A--rn
В данном примере в первом разделе пересылается заголовок новости, а во втором разделе пересылается файл news.txt. Внимательный да увидит поля filename и Content-Type во втором разделе. Поле filename задает имя пересылаемого файла, а поле Content-Type — тип данного файла. Application/octet-stream говорит о том, что это стандартный поток данных, а Content-Transfer-Encoding: binary говорит на о том, что это бинарные данные, ничем не закодированные.
Очень важный момент. Большинство CGI скриптов написано умными людьми, поэтому они любят проверять тип пришедшего файла, который стоит в Content-Type. Зачем? Чаще всего закачка файлов на сайтах используется для получения картинок от посетителя. Так вот, браузер сам пытается определить что за файл посетитель хочет отправить и вставляет соответствующий Content-Type в запрос. Скрипт его проверяет при получении, и, например, если это не gif или не jpeg игнорирует данный файл. Поэтому при «ручном» формировании запроса позаботьтесь о значении Content-Type, чтобы оно было наиболее близким к формату передаваемого файла.
image/gif для gif
image/jpeg для jpeg
image/png для png
image/tiff для tiff (что используется крайне редко, уж больно емкий формат)
В нашем примере формируется запрос, в котором передается текстовый файл. Точно так же формируется запрос для передачи бинарного файла.
4. Постскриптум.
Думаю, что о передаче запросов на сервер не стоит рассказывать подробно. Это уже дело чисто РНР техники :-). Достаточно внимательно прочитать раздел о функциях работы с сокетами, или о функциях модуля CURL в официальной документации РНР.
Из выше сказанного, надеюсь теперь понятно, почему вопрос: «Как мне сформировать POST запрос, используя функцию header?» — бессмысленен. Функция header(string) добавляет запись только в заголовок запроса, но никак не в тело запроса.
Для начала нам стоит разобраться — что такое HTTP-запрос.
Краткое описание технологии
Если коротко, то HTTP-запрос, это сообщение отправленное между клиентом и сервером по протоколу HTTP.
Мы каждый день отправляем тысячи HTTP-запросов переходя по ссылкам в браузере, обновляя страницы сайтов и общаясь в мессенджерах. Основной этого является HTTP (HyperText Transfer Protocol, протокол передачи гипер-текста, подробнее в википедии), позволяющий нам передавать произвольные данные в виде текста по сети. На всякий случай отмечу, что для отправки сообщений и получения ответа нужно два приложения клиент и сервер.
Зафиксируем для себя:
Клиент HTTP — приложение отправляющее запрос.
Сервер HTTP— приложение принимающее запрос и возвращающее на него ответ.
Общая схема действий выглядит так:
1. Клиент отправляет запрос
2. Клиент переходит в режим ожидания ответа
3. Сервер получает запрос
4. Сервер обрабатывает запрос
5. Сервер отправляет ответ обратно
6. Клиент принимает ответ
7. Обмен завершен
К счастью, в большинстве случаев у нас нет нужды глубоко погружаться в детали работы сетевых протоколов так как в большинстве языков программирования уже есть встроенный HTTP-клиент, который можно просто использовать не в даваясь в суть вещей.
С помощью таких встроенных клиентов мы работаем с HTTP-протоколом не задумываясь о том, как это устроено.
Пример на псевдокоде:
АдресСервера = «academy.dev-ins.ru»
Ресурс = «integration/public_api/employees/»
Соединение = СоздатьСоединениеHTTP(АдресСервера)
Запрос = СоздатьHTTPЗапрос(Ресурс)
Ответ = Соединение.ПолучитьССервера(Запрос )
В этом примере мы видим что мы работаем с какими-то простыми сущностями и объектами языка для получения необходимого результата
Погружение на глубину
А если смотреть в глубь, то нас встретит описание формата общения по HTTP-протоколу.
Посмотрим как выглядит HTTP-запрос для получения главной страницы нашего сайта
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Cache-Control: no-cache
Host: academy.dev-ins.ru
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Именно такой текст отправляется в TCP-порт. Если не сильно вдаваться в суть, TCP — это нижележащая технология, протокол (Transmission Control Protocol, подробнее на википедии), обеспечивающий работу HTTP — протокола.
Как вы видите, наше сообщение это действительно текст, который распознается как HTTP — запрос, благодаря тому что оформлен в специальном виде.
Это сообщение можно разделить условно на две части:
Стартовая строка — GET / HTTP/1.1 — содержит метод HTTP / Протокол / Версию протокола
Заголовки запроса — все остальные строки из этого сообщения в формате Ключ: Значение
Заголовки бывают предопределенные, описанные в стандарте RFC, и произвольные, которые мы можем придумать и использовать самостоятельно.
В сообщении присутствует слово GET, это один из возможных методов (глаголов) HTTP. Указывает на то, что мы хотим получить какую-то информацию с сервера. Так же существуют и другие HTTP-методы (POST, DELETE, PATCH, PUT и т.д.).
После слова GET идет слеш (косая черта), оно указывает на конкретный ресурс к которому мы обращаемся. В данном случае косая черта означает что мы обращаемся к корневому ресурсу.
Ресурсы
Ресурсами называются любые файлы, страницы или обработчики запросов на сервере, к которым можно обратиться из вне. Это исходит из определения аббревиатуры URL (Uniform Resource Locator, Унифицированный указатель ресурса).
Зафиксируем, URL — это указатель на ресурс.
В URL может быть более одной косой черты, например адрес страницы с уроками https://academy.dev-ins.ru/lessons/, стартовая строка запроса к этому URL выглядел бы так
GET /lessons/ HTTP/1.1
как видите, добавилось название ресурса lessons, к которому мы обращаемся и еще одна косая черта.
Параметры URL
Все символы, которые находятся между двумя косыми чертами называются сегментами или параметрами URL, /параметр1/параметр2/…/параметрN
Параметры запроса
Также можно передавать и вспомогательные данные в URL. Для этого необходимо добавить символ вопроса (?) в конец URL и перечислить параметры и их значения разделяя пары ключ — значение символом амперсанд (&). Например так:
https://academy.dev-ins.ru/lessons?param1=1¶m2=2¶m3=test
Это очень удобно, когда нам нужно передавать какие-то конкретизированные, именованные значения в запросе.
Тело запроса
В URL запроса можно передать далеко не все данные необходимые для работы HTTP-запросов. Чаще всего длина URL имеет ограничения. И если для получения данные с помощью метода GET, этого достаточно, то для метода POST(создание данных на сервер) или PATCH/PUT(обновление данных на сервере) нам уже необходимо какое-то более емкое хранилище. Для этого используется тело запроса (body). Как мог бы выглядеть запрос на добавление
POST /integration/public_api/employees/ HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Cache-Control: no-cache
Host: academy.dev-ins.ru
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 140
{
«first_name»: «Иван»,
«last_name»: «Иванов»,
«middle_name»: «Иванович»,
«email»: «ivanov@mail.ru»
}
Как видим, мы отправили POST-запрос, на создание новой записи на сервере, и добавилось несколько важных заголовков:
Content-Type: applcation/json — определяющий формат данных содержащихся в теле
Content-Length: 140 — определяющий размер данных, передающихся в сообщении, в байтах
после заголовков добавляется пустая строка, и дальше идет тело запроса в текстовом виде.
И если Content-Type является по большей части вспомогательным заголовком, от которого зависит только сможет ли какой-то универсальный обработчик автоматически определить тип содержимого или нет, то Content-Length в некоторых случаях может быть критически важен, ведь клиент исервер должны как-то понять, что они загрузили сообщение полностью и когда количество полученных данных сопадает со значением указанным в заголовке, то передача прекращается.
Привет, читатель блога ZametkiNaPolyah.ru! Продолжим знакомиться с протоколом HTTP в рубрике серверы и протоколы и ее разделе HTTP протокол. В этой записи ты узнаешь всё что можно про запросы HTTP протокола. Для начала мы с тобой разберем структуру HTTP запроса, затем мы посмотрим, что собой представляет строка HTTP запроса, потом мы поговорим с тобой о методах HTTP запроса и ты узнаешь, собственно, что такое метод. Потом мы плавно перейдем к идентификаторам ресурса в HTTP запросе (Request-URI, если не совсем понятно), после чего мы с тобой разберем поля заголовков HTTP запроса и в конце этой записи мы с тобой разберем пару примеров HTTP запросов, которые, для закрепления прочитанного, ты можешь написать самостоятельно, как делает твой браузер, через который ты зашел на этот сайт.
HTTP запрос: заголовки HTTP запроса, методы HTTP запроса, строка HTTP запроса, ресурсы HTTP запроса, примеры запросов
Структура HTTP запроса
Если вы хотите узнать всё про протокол HTTP, обратитесь к навигации по рубрике HTTP протокол. HTTP запрос – это HTTP сообщение, которое клиент посылает HTTP серверу. Обычно HTTP запрос содержит:
- строку запроса, в которой указывается версия HTTP протокола и HTTP метод запроса;
- ноль или несколько заголовков, разделенных между собой символом конца строки, в которых передаются другие HTTP праметры для успешного HTTP соединения;
- пустую строку, чтобы отделить служебную информацию от тела сообщения;
- необязательное тело сообщения.
Вот так выглядит общий синтаксис (общая структура HTTP запроса):
Request = Request—Line ; *( general—header ; | request—header ; | entity—header ) ; CRLF [ message—body ] ; |
В первой строке HTTP сообщения обычно содержится HTTP метод, который нужно применить к ресурсу, который запрашивает клиент, идентификатор ресурса (читай URI в HTTP) и версию HTTP протокола. Далее мы рассмотрим каждую часть HTTP запроса в отдельности и приведем пример HTTP запроса.
Строка HTTP запроса
Строка HTTP запроса начинается с маркера/метки метода, после которой следует URI запрашиваемого ресурса (если не понятно, читай про параметры HTTP протокола), версия HTTP протокола и символ CRLF, который означает конец строки HTTP запроса. Синтаксис строки HTTP запроса:
Request—Line = Method SP Request—URI SP HTTP—Version CRLF |
Предлагаю рассмотреть в отдельности каждую часть строки HTTP запроса в отдельности.
HTTP метод запроса
Метод HTTP запроса указывает серверу, как нужно обращаться к запрашиваемому ресурсу, который указан в URI. Метод HTTP запроса чувствителен к регистру и его имя следует указывать только в верхнем регистре. Давайте перечислим все метода HTTP запроса, я сведу их в одну таблицу:
Номер | HTTP метод запроса и его описание |
1 | GET Метод HTTP запроса GET используется для получения информации с сервера по указанному URI. HTTP запросы, использующие метод GET должны получать только данные и не должны оказывать никакого влияния на эти данные. |
2 | HEAD Принцип работы метода HEAD в HTTP запросе аналогичен методу GET, но метод HEAD не передает тело сообщения (HTTP объект). |
3 | POST HTTP запрос POST используется для отправки данных на HTTP сервер, например, когда вы заполняете HTML форму на сайте. |
4 | PUT HTTP запросы с методом PUT сохраняются под запрашиваемым URI. То есть метод PUT используется для замены контента. |
5 | DELETE Метод DELETE при HTTP запросе позволяет запросить сервер удалить данные ресурса, указанного в URI. |
6 | CONNECT HTTP запрос с методом CONNECT позволяет установить туннель к серверу, который указан в URI. |
7 | OPTIONS HTTP запрос с методом OPTION позволяет получить параметры для связи с ресурсом. |
8 | TRACE При HTTP запросе с методом TRACE можно отследить то, что происходит с вашими запросами. |
Список методов, которые можно применить к ресурсу, может быть указан в поле заголовка Allow. HTTP сервер всегда должен возвращать код состояния ответа, чтобы сообщить клиенту: допустим метод или нет.
URI HTTP запроса (Request-URI). Запрашиваемый URI
URI HTTP запроса (Request-URI) или запрашиваемый URI для нас в большинстве случаев это обычный URL, который дает однозначное понимание HTTP серверу к какому ресурсу мы хотим обратиться:
Request—URI = «*» | absoluteURI | abs_path |
У URI, когда мы делаем HTTP запрос, есть три опции, которые зависят от характера запроса. Звездочка в предыдущем примере означает, что мы хотим обратиться не к какому-то ресурсу, а непосредственно к HTTP серверу. Такой способ допустим только в том случае, когда используемый метод HTTP запроса не обязательно обращается к ресурсу, например:
absoluteURI URI необходим при HTTP запросе в том случае, когда HTTP запрос производится при помощи прокси-сервера. Прокси-сервер может передать этот запрос HTTP серверу, а может дать ответ из своего кэша, пример absoluteURI в HTTP запросе:
GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1 |
Обращу ваше внимание на то, что в версии HTTP протокола 1.1 клиенты должны использовать absoluteURI только для обращений к прокси-серверам.
Рассмотрим третий вид URI в HTTP запросе, наиболее общую и часто встречаемую форму Request-URI, данную форму Request-URI используют для идентификации ресурса на конечном HTTP сервере, при этом абсолютный путь URI передается в HTTP запросе, как Request-URI, а вот сетевое расположение ресурса передается в поле Host HTTP заголовка. Пример:
GET /pub/WWW/TheProject.html HTTP/1.1 Host: www.w3.org |
Обратите внимание, что абсолютный путь не может быть пустым; если оригинальный URI пуст, то он должен запрашиваться как «/» (корневой каталог сервера). Первоначальный сервер должен декодировать Request-URI (кодирование в HTTP), чтобы правильно интерпретировать запрос. Серверам следует отвечать на недопустимые Request-URI соответствующим кодом состояния.
В запросах, которые передаются далее, прокси-сервера никогда не должны перезаписывать часть «abs_path» запрашиваемого URI (Request-URI), за исключением случая, отмеченного выше, когда пустой abs_path заменяется на «*», независимо от внутренней реализации прокси-сервера.
Первоначальные HTTP/1.1 сервера должны учитывать, что точный ресурс, идентифицированный интернет-запросом определяется как Request-URI, так и полем заголовка Host. Первоначальный сервер, который различает ресурсы, основанные на запрошенном хосте (иногда называемые виртуальными хостами или vanity hostnames) должен использовать следующие правила для определения запрошенного в HTTP/1.1 запросе ресурса:
- Если Request-URI — это absoluteURI, то хост — это часть Request-URI. Любое значение поля заголовка Host в запросе ДОЛЖНО игнорироваться (напомню про требования HTTP).
- Если Request-URI — не absoluteURI, а запрос содержит поле заголовка Host, то хост определяется значением поля заголовка Host.
- Если хоста, определенного правилами 1 или 2 не существует на сервере, код состояния ответа должен быть 400 (Испорченный Запрос, Bad Request).
Получатели HTTP/1.0 запроса, в котором недостает поля заголовка Host, могут пытаться использовать эвристику (например, исследовать путь в URI на предмет уникальности на каком-либо из хостов) чтобы определить какой точно ресурс запрашивается.
Поля заголовка HTTP запроса
Поля заголовка HTTP запроса дают возможность клиенту передавать дополнительную, уточняющую и служебную информацию о HTTP запросе и о самом себе любимом. Поля заголовка HTTP запроса это что-то вроде модификаторов HTTP запроса. Если вы изучали какой-нибудь язык программирования, то заголовки HTTP запроса можно сравнить с параметрами, которые мы передаем в функцию для ее вызова:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
request—header = Accept | Accept—Charset | Accept—Encoding | Accept—Language | Authorization | From | Host | If—Modified—Since | If—Match | If—None—Match | If—Range | If—Unmodified—Since | Max—Forwards | Proxy—Authorization | Range | Referer | User—Agent |
Никто не запрещает вам ввести свои собственные поля заголовков HTTP запроса, если вы решите написать свой собственный клиент или HTTP сервер.
Примеры HTTP запросов
Давайте теперь разберем несколько примеров HTTP запросов. Пример HTTP запроса для получения простой HTML страницы: представим, что на сайте example.org лежит HTML документ, который называется hello.htm, http запрос для него будет выглядеть примерно следующим образом:
GET /hello.htm HTTP/1.1 User—Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT) Host: www.example.com Accept—Language: ru—ru Accept—Encoding: gzip, deflate Connection: Keep—Alive |
Этим запросом мы не посылаем никаких данных на сервер, потому что мы хотим получить простую HTML страницу с сервера. Давайте теперь посмотрим пример HTTP запроса, который отправляет данные HTML формы на сервер с помощью тела HTTP сообщения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
POST /cgi—bin/process.cgi HTTP/1.1 User—Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT) Host: www.example.com Content—Type: application/x—www—form—urlencoded Content—Length: length Accept—Language: ru—ru Accept—Encoding: gzip, deflate Connection: Keep—Alive licenseID=string&content=string&/paramsXML=string |
В стартовой строке указан URL /cgi-bin/process.cgi – он будет использован для обработки данных, которые мы передадим серверу в запросе, на такой запрос мы даже получим от сервера ответ. Content-Type сообщает серверу о том, что данные, которые мы хотим передать серверу – это простая HTML форма.
В статье про устройство веба и как происходит серфинг я упомянул, что браузер отправляет запрос к веб-серверу. Но что представляет из себя запрос? Это куча машиночитаемых квантовых кодов и сингулярных шифров? Программистская магия? Вовсе нет.
Что такое HTTP
HTTP — это протокол передачи данных для клиент-серверных приложений.
Не нужно пугаться слова «протокол». Это значит лишь то, что разработчики встретились и договорились о возможных форматах запросов и ответов на них. Вокруг нас множество таких протоколов-договорённостей.
- При встрече на протянутую руку принято отвечать рукопожатием. Отсутствие рукопожатия — это тоже ответ, иногда даже более красноречивый, чем само рукопожатие.
- Девушкам же руку не протягивают — это тоже часть протокола. Можно и им руку протянуть, но в большинстве случаев не поймут, а в некоторых странах заставят жениться.
- Электрические розетки — хотя в разных странах они разные, внутри одной страны они одинаковы.
- Разъёмы для кабелей — USB type B, USB type C, mini USB, micro USB. Производители приняли внегласный протокол и производят кабели и устройства именно таких форматов, иначе при прочих равных пользователи их не поймут и не будут покупать их продукцию (исключение — Apple).
- Правила дорожного движения — знаки, разметка и светофоры помогают пешеходам дойти, а автомобилистам доехать до места назначения без происшествий.
- Формы налоговых деклараций и прочих бюрократических документов.
Любой из протоколов нас ни к чему не обязывает, это не ГОСТ, он лишь рекомендует поступать так или иначе, если мы хотим добиться желаемой цели — понимания от окружающих людей, одобрения от покупателей, сохранения продаж, избежания аварий и штрафов, получения веб-страницы от сервера.
HTTP-клиентами чаще всего являются браузеры — Google Chrome, Mozilla Firefox, Safari, Opera, Yandex Browser и другие. А серверами являются веб-сервера. Вот эта приставка «веб-» и указывает на то, что это не просто какой-то сервер, а сервер, который умеет принимать запросы и отвечать на них по протоколу HTTP. Как он будет устроен внутренне — не определено и не важно. Наиболее популярными в мире веб-серверами являются Nginx, Apache2, но вы можете написать и свой — в некоторых языках это делается крайне легко, см. пример на Golang.
Прикидываемся браузером, или делаем HTTP-запрос из терминала
Чтобы понять, как браузер общается с сервером, нужно думать как браузер, нужно стать браузером.
Попробуем обратиться к веб-странице http://http.maxkuznetsov.ru так, как это делают браузеры под капотом. Для этого отправим запрос из терминала/командной строки с помощью утилиты netcat. Чаще всего она установлена по умолчанию: в Mac OS X — это «nc», в других ОС может быть «ncat» или «netcat». (Или воспользуйтесь онлайн-сервисом https://reqbin.com/u4178vu3, в котором слева и справа выберите табы Raw для отображения «голых» запросов и ответов. Но из терминала получится нагляднее.)
nc http.maxkuznetsov.ru 80
Дальше ничего не произойдёт, терминал подвиснет — это нормально. Команда netcat подключилась к серверу по адресу http.maxkuznetsov.ru к 80-му порту, и сервер ждёт от нас текст запроса.
Порты — это как номера квартир в доме. Чтобы доставить письмо, почтальону нужно знать не только дом, но и номер квартиры. Причём в некоторых квартирах почтальону ответят, если он в них постучится, а другие — нет, потому что там никто не живёт. А кто-то ответит, что адресат уже давно здесь не живёт и дадут новый адрес почтальону (редирект запроса).
В компьютерных сетях всё точно также. На одном адресе (IP или доменном имени) могут висеть и ожидать запросов несколько портов одновременно. Чтобы избежать путаницы, сообщество разработчиков договорилось для наиболее популярных серверов выделять одни и те же порты: SSH — 22, FTP — 21, база данных MySQL — 3306, веб-сервера — 80. Это лишь соглашение и рекомендация, можно поднять какой угодно сервер на каком угодно порту, но для клиентов это скорее всего станет неожиданностью.
Когда в браузере мы вбиваем в адресной строке http.maxkuznetsov.ru, браузер подставляет порт 80 незаметно для нас. Вы можете убедиться в этом, вбив в адрес не http.maxkuznetsov.ru, а http.maxkuznetsov.ru:80 — результат не изменится.
Введём в терминале такие строки запроса.
$ nc http.maxkuznetsov.ru 80
GET / HTTP/1.1
Host: http.maxkuznetsov.ru
После Host нужно ввести две пустые строки: одна строка отступа, вторая содержит тело запроса, но в данном примере оно пустое. Такие правила протокола HTTP. Получив вторую пустую строку, веб-сервер поймёт, что запрос завершён, обработает его и пришлёт ответ, включающий интересующую нас веб-страницу с html.
$ nc http.maxkuznetsov.ru 80
GET / HTTP/1.1
Host: http.maxkuznetsov.ru
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 13 Apr 2020 20:10:04 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
<html>
<head>
<title>This is a test page for Max's blog article about http</title>
</head>
<body>
<h1>Hello HTTP world!</h1>
<form method="POST" action="/">
<input name="name" type="text" placeholder="Name" />
<button type="submit">Submit</button>
</form>
</body>
</html>
После этого браузер разбирает ответ, убирает техническую информацию и отображает html-страницу в кодировке UTF-8 — так ему сказал сервер в заголовке Content-Type. Если в HTML включены CSS, Javascript, картинки, то браузер запросит их отдельными запросами ровно таким же образом. Если он их уже запрашивал раньше, то возьмёт из локального кэша. Поэтому первый раз страницы грузятся визуально дольше.
Разберём структуру запроса и ответа более детально.
Структура HTTP-запроса
Каждый запрос имеет один и тот же формат:
метод /путь протокол
Host: <site.ru>
заголовок1: значение
заголовок2: значение
заголовокN: значение
<тело_запроса_в_одну_строку или пустая_строка_если_тело_пустое>
протокол
Указывает, какая конкретная версия протокола HTTP будет использоваться. Чаще всего 1.0 и 1.1, но могут встречаться устаревшая 0.9 или новая 2.0. Однако, веб-сервер может не поддерживать указанную версию и вернуть ошибку. На практике подавляющее количество веб-серверов поддерживают 1.0 и 1.1.
/путь
Относительный путь (без доменного имени) до документа. В нашем примере указан корень /, но путь может быть любым: /index.php, /catalog/food/milk. Под документом понимаются не только файлы с расширением .html, но и любые другие файлы, например картинки, .css, .js.
метод
Определяет, что веб-сервер должен сделать с документом, найденным по указанному «/пути».
- GET — вернуть документ — GET /messages/1.
- HEAD — вернуть только заголовки без самой страницы. Это подходит для случая, когда мы хотим проверить, что ссылка на документ валидная. Пример: HEAD /messages/1
- POST — отправить данные для создания документа — POST /messages (и детали нового сообщения).
- PUT — заменить документ
- PATCH — частично обновить документ
- DELETE — удалить документ
- TRACE, CONNECT — технические методы, которые можно пропустить.
- OPTIONS — просьба к веб-серверу вернуть разрешённые настройки для запросов к указанному документу. Ответ включает в себя в том числе список разрешённых методов.
На практике примерно 80% запросов приходится на GET, 15% — на POST и 5% — на все остальные методы.
Заголовки
Они опциональны (в нашем примере их не было вовсе) и подсказывают веб-серверу, как именно нужно обработать запрос. Например, что клиент отправляет запрос в виде текста с кодировкой utf-8, а ожидает получить json в кодировке cp1251.
Наиболее частые на практике заголовки:
- Accept — в каком формате ожидаем ответ: обычный текст, html, xml, json, что угодно ещё.
- Accept-Charset — кодировка тела запроса: utf8, cp1251, koi8.
- Authorization — данные для авторизации между запросами. Здесь чаще всего передаются токены API. Авторизация между запросами будет рассмотрена ниже.
- Accept-Language — список языков, которые нас бы устроили. Например: «Accept-Language: ru».
- Cache-Control — настройки кэширования страниц
- Cookie — известные браузеру куки. В них сохраняются идентификаторы сессий и пользовательские предпочтения.
- Referrer — с какой страницы был сделан текущий запрос. Полезно для аналитики сайта и для возвращения юзера на первоначальную страницу после регистрации, например.
- User-Agent — тип клиента (чаще всего тип вашего браузера). Пример: «Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36». Это поле часто используется на сервере, чтобы отслеживать количество запросов с одного устройства и блокировать их при превышении лимита. Однако это не панацея, ведь после блокировки злоумышленник может поменять User-Agent на любой другой.
Тело
Для GET-запросов тело не имеет смысла, так как всё, что нужно — это путь в стартовой строке и заголовки. Но что происходит, когда мы хотим отправить информацию на сервер? Логин-пароль, форма обратной связи, форма создания поста? Для этого используется POST-запрос.
Каждый элемент формы имеет аттрибут name. В нашем примере страница http://http.maxkuznetsov.ru содержит форму с единственным тегом input, который имеет name=«name». Именно это имя и введённое пользователем в инпут значение будут отправлены на сервер. В консоли такой браузерный запрос будет выглядеть так:
$ nc http.maxkuznetsov.ru 80
POST / HTTP/1.1
Host: http.maxkuznetsov.ru
Content-Type: application/x-www-form-urlencoded
Content-Length: 8
name=Max
Обратите внимание, что POST запрос очень похож на GET, мы даже обращаемся к тому же документу «/». Однако есть и отличия:
- вместо второй пустой строки в конце запроса содержатся данные: «name=Max»
- эти данные могут быть в разном формате, поэтому мы должны явно указать веб-серверу, что это данные из формы — application/x-www-form-urlencoded
- также мы сообщаем серверу, что в теле запроса содержится ровно 8 символов — «Content-Length: 8». Это техническое поле, которое браузер выполняет на лету, а нам приходится считать самим.
В ответ придёт знакомая страница, но с другим h1 заголовком — php-скрипт, обрабатывающий страницу, подставил вместо «http world» введённое нами имя.
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 13 Apr 2020 22:26:19 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Content-Encoding: gzip
<html>
<head>
<title>This is a test page for Max's blog article about http</title>
</head>
<body>
<h1>Hello Max</h1>
<form method="POST" action="/">
<input name="name" type="text" placeholder="Name" />
<button type="submit">Submit</button>
</form>
</body>
</html>
Структура HTTP-ответа на основе примера выше
Можно заметить, что структура ответа похожа на структуру запроса. Но есть несколько нюансов. Первая строка ответа выглядит иначе:
HTTP/1.1 200 OK
протокол статус пояснение
протокол
Значение поля то же самое, что и в запросе. Но может отличаться от версии, что запросил браузер, если веб-сервер её не понимает.
статус и пояснение
HTTP-статус из трёх цифр и короткая поясняющая фраза. Все фразы стандартизированы и чётко соответствуют статусу.
Статусов больше сотни, но не все их них используются браузерами. Некоторые предусмотрены на далёкое будущее, а некоторые слишком специфичны.
Первая цифра статуса указывает на класс:
- 1xx — информационные — технические статусы, вероятнее всего вы их не увидите в реальной жизни
- 2xx — успешно обработанные запросы с детализацией. Наиболее частые: 200 — всё ок, 201 — документ был создан, 204 — запрос завершился успешно, но ответ содержит заголовки и пустое тело. На практике реальные API не парятся и в 95% случаев возвращают код 200, а детали успешной операции отсылаются в теле.
- 3xx — перенаправление — запрос следует выполнить по другому адресу, который передаётся в заголовке Location. Частые: 301 — документ перенесён навсегда, 302 — документ перенесён временно. Они довольно критичны для поисковых ботов, которые индексируют ваш сайт. 301 говорит боту, чтобы он запомнил новый адрес страницы, а прежний забыл.
- 4xx — ошибка клиента — запрос содержит не все или некорректные данные (400), требуется аутентификация (401) или не хватает прав на выполнение операции (403), запрошенной страницы не существует (404) или http метод запроса для этой страницы запрещён (405).
- 5xx — ошибка на сервере — сервер не справился или произошла непредвиденная ошибка (500), ошибка обработки запроса вышестоящим сервером, например php-fpm не отвечает nginx’у (502), сервер временно не отвечает по техническим причинам (503), сервер не дождался ответа и запрос отвалился по таймауту (504). Например, стандартное ограничение на время выполнения php-скрипта — 30 секунд. Если скрипт делает запрос к стороннему ресурсу, который под нагрузкой, то nginx покажет 504ю ошибку.
При этом даже неуспешный статус не запрещает серверу вернуть веб-страницу, которую браузер отобразит как ни в чём не бывало. Попробуйте зайти на несуществующую страницу моего блога: https://maxkuznetsov.ru/non-existed-page. Сервер вернёт 404 вместо 200, но мы всё равно можем показать пользователю что-то полезное.
заголовки
Заголовки сервера выполняют ту же роль, что и заголовки запроса. Есть общие заголовки, как Cache-Control, но есть и свои уникальные.
- Allow — определяет список разрешённых http методов для запросов
- Location — адрес для перенаправления.
- WWW-Authenticate — информация про метод аутентификации, запрос должен послать соответствующую информацию в хедере Authorization.
тело
Тело ответа также отделяется от группы заголовков пустой строкой. При этом в теле может передаваться что угодно — текст, html, json, xml, картинки и прочие файлы. Все они отдаются браузеру в одинаковом формате, но с отличающемся заголовком Content-Type, который и поясняет браузеру, как отобразить контент пользователю: как html-страницу, как картинку, показать встроенный в браузер PDF-просмотрщик или начать скачивание файла.
Про аутентификацию и авторизацию
Если посмотреть на структуру HTTP запросов и ответов становится понятно, что каждый запрос для веб-сервера является изолированным и не сохраняет состояния. То есть если вы сделаете два одинаковых запроса с одного браузера, веб-сервер обработает их так, будто они были присланы разными пользователями.
В жизни это ограничение обходят двумя путями:
- хранят уникальный идентификатор сессии в куках (cookies), которые браузер по требованию сервера сохраняет локально и затем прикрепляет к каждому запросу в виде заголовка «Cookie». Cервер при каждом запросе разбирает заголовок с куками и по сохранённому там идентификатору «узнаёт» пользователя.
- через заголовок Authorization браузер посылает серверу в каждом запросе токен (форматы могут быть разными), по которому сервер определяет пользователя аналогично сессиям. Этот способ чаще всего используется в API.
Поскольку http протокол передаёт все данные в незащищённом виде, то ни один из этих способов не является безопасным, а идентификатор сессии или токен могут быть легко перехвачены злоумышленником. В качестве решения проблемы следует использовать более безопасного брата HTTP — HTTPS.
Что важно понимать про HTTP
- HTTP — это протокол общения клиент-серверных приложений в вебе. Набор правил, который помогает клиентам (прежде всего браузерам) и веб-серверам понимать друг друга.
- HTTP — это про формат общения, а не про управление сервером HTTP-командами. Клиент может отправить что угодно: удали страницу сайта, создай нового пользователя, выдай список всех пользователей — но сервер не обязан их выполнять, он лишь обязан ответить в формате HTTP, чтобы клиент его понял. То есть благодаря HTTP сервер поймёт, что клиент хочет, а потом уже решит, как это обработать и вернёт результат. Может быть удалит страницу, а может и нет.
- В HTTP общение всегда начинает клиент. А веб-сервер висит и ждёт. Сейчас есть способы инициировать запрос с сервера, но изначально протокол для этого не предназначен.
- HTTP-протокол не имеет шифрования, поэтому передавать персональные данные и прочие приватные данные через него не безопасно. В таком случае нужно использовать HTTPS.
- Простой способ изучить заголовки запроса и ответа — открыть консоль браузера на нужной странице и обновить её. В разделе Network/Сеть отобразятся все запросы с этой страницы, включая запросы на картинки и статические файлы.
Несколько слов о наступающем будущем — HTTP/2
Первая версия протокола HTTP была принята 20 лет назад. После этого 10 лет была тишина, пока Google не стал разрабатывать свой протокол SPDY поверх HTTP, что дало ускорение работе над HTTP/2. После серёзных подвижек Google отказался от разработки SPDY в пользу HTTP/2.
Вторая версия протокола отличается от первой чуть меньше, чем полностью.
- Протокол уже стал бинарен, а значит человеко нечитаемый.
- Несмотря на возможность работать без шифрования, все современные браузеры будут поддерживать именно вариант с шифрованием.
- В протоколе предусмотрена возможность push-сообщений, инициированных сервером.
- Протокол позволяет мультиплексирование — отправку нескольких запросов внутри одного соединения.
- Умеет сжимать данные заголовков.
Новый протокол безопаснее и в несколько раз быстрее. Самое приятное, что последние версии современных браузеров понимают http2, и половина наиболее крупных и популярных сайтов уже готовы к переходу на него. С приходом http2 веб станет ещё более интерактивным, а приложения приблизятся к десктопным.