HTTP-запросы

Общая информация

HTTP — это протокол передачи данных прикладного уровня, описанный в стандарте RFC 2616. Основой HTTP является взаимодействие «клиент-сервер». В этом взаимодействии выделяются две стороны:
  • Клиент — некоторая система, которая инициирует соединение и посылает запрос.
  • Сервер — программное обеспечение, которое ожидает установку соединения от клиента, получает запрос, выполняет некоторые действия и возвращает ответ клиенту.

В основе HTTP-взаимодействия лежит понятие URI (или URL в более простом варианте) — универсального идентификатора ресурса, который описан к стандарте RFC 3986. Взаимодействие осуществляется с помощью запросов. Каждый запрос содержит несколько частей: стартовую строку (определяет тип сообщения), заголовки (определяют параметры запроса) и тело сообщения (дополнительные данные запроса). Тело сообщения может быть пустым.

Стартовая строка имеет следующий вид:
метод uri HTTP/версия

Здесь:

  • метод — действие, которое сервер должен выполнить с ресурсом, указанным с помощью URI. Состоит из любых символов US-ASCII (кроме управляющих символов и разделителей). Название метода может быть любым, но рекомендуется использовать методы, описанные в спецификации HTTP 1.1 (RFC 2616).
  • uri — путь к конкретному ресурсу на сервере. Запрос будет выполняться над данными, которые адресуются указанным URI. Если используемый в запросе метод не требует указания ресурса, вместо URI пишется символ «*».
  • версия — версия HTTP-протокола. В настоящий момент это версия 1.1.
В ответ на запрос клиента сервер возвращает ответ, который по структуре аналогичен запросу. Ответ сервера содержит стартовую строку ответа, заголовки и тело сообщения (которое не является обязательным). Стартовая строка ответа имеет следующий вид:
HTTP/версия код-состояния пояснение
Здесь:
  • версия — номер версии HTTP-протокола, аналогично стартовой строке запроса. В настоящий момент это версия 1.1.
  • код-состояния — описывает результат выполнения запроса.
  • пояснение — текстовое описание результата выполнения запроса. Данный текст не содержит символов перевода строки и возврата каретки.
Заголовки запроса — это набор именованных параметров запроса. Каждый запрос может содержать различный набор таких параметров. Заголовки делятся на четыре группы:
  • общие заголовки — используются как в запросах, так и в ответах;
  • заголовки запроса — используются только в запросах;
  • заголовки ответа — используются только в ответах;
  • заголовки сущностей — сопровождают каждую сущность сообщений, используются как в запросах, так и в ответах.
Имена заголовков не чувствительны к регистру символов. Запрос может содержать несколько заголовков с одинаковым именем. Такое допускается только в том случае, если значения заголовка разделяются запятыми и порядок следования значений в заголовке не изменяет его семантику.

Таким образом, HTTP-взаимодействие выполняется следующим образом:

  • Клиент устанавливает соединение с сервером.
  • Клиент создает запрос и отправляет его серверу.
  • Сервер принимает запрос и пытается его выполнить.
  • По результатам выполнения запроса сервер формирует ответ и отправляет его клиенту.
  • Клиент получает ответ и анализирует содержащийся в нем код состояния, после чего принимает решение о дальнейших действиях.

«1С:Исполнитель» может выступать только в качестве клиента для протокола HTTP.

Методы HTTP-запроса

В таблице ниже перечислен рекомендуемый набор методов HTTP-запроса, описанный в стандарте HTTP 1.1 (RFC 2616):
Метод Описание
CONNECT Устанавливает двустороннюю связь с запрошенным ресурсом. Может использоваться для открытия туннеля.
DELETE Удаляет указанный ресурс.
GET Позволяет запросить содержимое какого-либо ресурса или выполнить какое-либо действие.
HEAD Позволяет получить метаданные, проверить наличие какого-либо ресурса или узнать, что изменилось с момента последнего обращения. Ответ сервера не содержит тела.
OPTIONS Используется для определения возможностей веб-сервера или параметров соединения для конкретного ресурса.
PATCH Аналогичен запросу PUT, но применяется к фрагменту данных ресурса.
POST Отправляет данные на сервер для обработки.
PUT Создает новый ресурс или заменяет представление целевого ресурса данными, указанными в теле запроса.
TRACE Возвращает полученный запрос обратно клиенту. Позволяет клиенту узнать, что добавляют или изменяют в оригинальном запросе промежуточные узлы (через которые проходит запрос).

Коды состояния

Существует пять основных классов кодов состояния (RFC 2616):
Код Класс Описание
1хх Информационный Информирует о процессе передачи запроса.
2хх Успех Информирует об успешном выполнении запроса.
3хх Перенаправление Сообщает клиенту о том, что запрос необходимо выполнить с другими параметрами. Заголовки ответа содержат информацию о том, что надо изменить в запросе.
4хх Ошибка клиента Указывает, что запрос клиента содержит ошибку.
5хх Ошибка сервера Указывает, что во время выполнения запроса на сервере произошла ошибка.

Кодирование URL-адреса в «ЗапросHttp»

В адресе запроса могут содержаться символы, использование которых недопустимо в URI. Для успешного выполнения такого запроса недопустимые символы должны быть закодированы с помощью символа «%». Однако разработчик не всегда может гарантировать кодирование всех недопустимых символов. В результате в адресе запроса могут встречаться как допустимые символы, так и закодированные и незакодированные недопустимые символы.

Чтобы обеспечить успешное выполнение запроса, ЗапросHttp сохраняет все существующие закодированные представления символов во входящей строке и кодирует недопустимые символы. Входящая строка адреса обрабатывается следующим образом: если за символом процента (%) следует значение, допустимое для кода символа (два шестнадцатеричных значения), то считается, что это код символа. Иначе считается, что это знак процента, который будет закодирован как %25.

Входящий URL-адрес разделяется на группы, каждая из которых кодируется отдельно. Группы адреса соответствуют описанию стандарта RFC3986: схема (scheme), основание (authority), путь (path), запрос (query), фрагмент (fragment):

Допустимыми являются все разделители внутри сегментов, которые указаны в стандарте RFC 3986: «!», «&», «'», «+», «*», «$», «(», «)», «,», «;», «=». Также для сегментов «запрос» и «фрагмент» допустимы символы «/» и «?».

Символы, которые являются разделителями сегментов, не меняются: «:», «/», «?», «#», «[», «]», «@». Не меняются и разрешенные к использованию во всех частях URL-адреса символы «-», «.», «_», «~».

Если во входящей строке представлены символы национальных алфавитов (то есть не латиница, в частности русские буквы), то они также кодируются. Если такие символы встречаются в доменной части строки, то выполняется IDN-кодирование. Если они присутствуют в других частях URL, то выполняется процентное кодирование.

Примеры процентного кодирования:
  1. https://yandex.ru/maps%2F%3Ftest1%2Ftest 2%5Ctest3\test4 — исходный адрес запроса.

    https://yandex.ru/maps%2F%3Ftest1%2Ftest%202%5Ctest3%5Ctest4 — адрес запроса после преобразования: пробел и обратная косая черта закодированы.

  2. http://test.ru/search=aa%2Faa — исходный адрес запроса.

    http://test.ru/search=aa%2Faa — адрес запроса после преобразования: текст не изменился, так как после процента идет допустимый код символа.

  3. http://test.ru/search=100% — исходный адрес запроса.

    http://test.ru/search=100%25 — адрес запроса после преобразования: знак процента закодирован.

Пример IDN-кодирования:

http://яндекс.рф — исходный адрес запроса.

http://xn--d1acpjx3f.xn--p1ai — адрес запроса после преобразования.

Объектная модель

Рассмотрим объектную модель, предоставляемую «1С:Исполнителем» для поддержки клиент-серверного взаимодействия с использованием HTTP-запросов.

В основе модели выступает тип КлиентHttp, содержащий базовые параметры HTTP-взаимодействия. Экземпляр этого типа можно получить из свойства глобального контекста КлиентHttp.

Экземпляр КлиентHttp может создавать новые экземпляры этого типа, если вам требуется работать с несколькими серверами. Данные экземпляры получают все параметры из базового экземпляра, кроме тех параметров, которые можно изменить при создании копии. Другими словами, создается измененная копия родительского экземпляра. Про такую возможность создания нового экземпляра на базе существующего говорят, что существующий экземпляр выступает в роли фабрики. Экземпляр типа КлиентHttp, предоставляемый «1С:Исполнителем», называется базовым экземпляром.

Для указания адреса сервера, к которому должны выполняться запросы клиентского приложения, используется свойство КлиентHttp.БазовыйUrl. В базовом экземпляре данное свойство пустое, поэтому для создания клиента, который сможет обратиться к какому-либо серверу, следует вызвать метод СБазовымUrl() и передать ему адрес сервера в качестве параметра. Таким образом, создание экземпляра для работы с определенным сервером выглядит следующим образом:
пер МойКлиент = КлиентHttp.СБазовымUrl("http://example.com")

«1С:Исполнитель» поддерживает односторонний (с аутентификацией сервера) и двусторонний (с аутентификацией сервера и клиента) TLS.

Пример использования одностороннего TLS
знч СертификатСервиса = новый ЦифровойСертификат(
    новый Файл("service.cer").ОткрытьПотокЧтения().ПрочитатьКакБайты())
знч СертификатыСервера = новый ХранилищеPkcs12()
СертификатыСервера.ДобавитьСертификат("some.api.ru", СертификатСервиса)

знч ПараметрыЗащиты = новый ПараметрыЗащищенногоСоединения(СертификатыСервера)

знч Клиент = КлиентHttp.СПараметрамиЗащиты(ПараметрыЗащиты)
знч Запрос = Клиент.ЗапросGet("https://some.api.ru/action")

исп Результат = Запрос.Выполнить()
Пример использования двустороннего TLS
знч СертификатСервиса = новый ЦифровойСертификат(
    новый Файл("service.cer").ОткрытьПотокЧтения().ПрочитатьКакБайты())
знч СертификатыСервера = новый ХранилищеPkcs12()
СертификатыСервера.ДобавитьСертификат("some.api.ru", СертификатСервиса)

знч КлючиКлиента = новый ХранилищеPkcs12(новый Файл("client.pfx").ОткрытьПотокЧтения().ПрочитатьКакБайты())

знч ПараметрыЗащиты = новый ПараметрыЗащищенногоСоединения(СертификатыСервера, КлючиКлиента, "key-password")

знч Клиент = КлиентHttp.СПараметрамиЗащиты(ПараметрыЗащиты)
знч Запрос = Клиент.ЗапросGet("https://some.api.ru/action")

исп Результат = Запрос.Выполнить()

Если вы хотите установить защищенное HTTPS-соединение для отправки запросов к серверу, использующему протокол TLS с шифрованием по ГОСТ, используйте метод СКриптоПроTls(). Для работы этого метода убедитесь, что у вас установлены модули КриптоПро.

Пример использования двустороннего TLS ГОСТ
знч ДоверенныеСертификаты = новый ХранилищеJks()
ДоверенныеСертификаты.ДобавитьСертификат(
    "demo",
    новый ЦифровойСертификат(Ресурс{demo.cer}.ОткрытьПотокЧтения().ПрочитатьКакБайты())
)
ДоверенныеСертификаты.ДобавитьСертификат(
    "demo.CA",
    новый ЦифровойСертификат(Ресурс{demo.CA.cer}.ОткрытьПотокЧтения().ПрочитатьКакБайты())
)

знч Параметры = новый ПараметрыЗащищенногоСоединения(
    ДоверенныеСертификаты,
    новый ХранилищеКриптоПро(),
    "key-password"
)

знч Клиент = КлиентHttp.СКриптоПроTls()
                       .СПараметрамиЗащиты(Параметры)

знч Запрос = Клиент.ЗапросGet("https://website-address.ru")
                   .ДобавитьЗаголовок("Заголовок", "Значение")

исп Ответ = Запрос.Выполнить()

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

Если вы хотите выполнить запрос, описанный в стандарте HTTP 1.1, то можете использовать метод КлиентHttp.ЗапросGet() или аналогичные ему (все поддерживаемые имена HTTP-методов перечислены в таблице выше). В этом случае в метод необходимо передать только URI на сервере, к которому будет применяться запрос.

Если вы хотите указать нужный HTTP-метод в качестве параметра или использовать нестандартный метод, используйте метод КлиентHttp.СоздатьЗапрос(). В этом случае необходимо передать и имя метода, и URI. Например, для создания GET-запроса к серверу можно использовать одну из следующих записей:
пер Запрос = МойКлиент.ЗапросGet("/path/resource?param1=value1&param2=value")
пер Запрос = МойКлиент.СоздатьЗапрос("GET", "/path/resource?param1=value1&param2=value")

После получения экземпляра типа ЗапросHttp вы можете указать ему нужные заголовки, задав их явно или переопределив уже существующие. Для этого используйте свойство ЗапросHttp.Заголовки, которое предоставляет доступ ко всей коллекции заголовков запроса. Свойство имеет тип ЗаголовкиHttp.

Тип ЗапросHttp позволяет работать как с одним заголовком (методы ДобавитьЗаголовок(), УстановитьЗаголовок(), УдалитьЗаголовок()), так и с несколькими заголовками сразу (методы ДобавитьЗаголовки(), УстановитьЗаголовки(), ОчиститьЗаголовки()). С помощью перечисленных методов нельзя изменять заголовки Content-Type и Transfer-Encoding. Для изменения заголовка Content-Type предназначен метод УстановитьТипСодержимого(). Для указания информации о клиентском приложении (заголовок User-Agent) предназначен метод УстановитьUserAgent().

Вы можете установить тело запроса из строки, файла или потока. Это может быть полезно, если тело запроса содержит много информации, вам требуется отправить большой файл или тело запроса формируется предварительно с помощью отдельного алгоритма. Для этого используются перегрузки метода УстановитьТело():
  • УстановитьТело(Тело: Строка): ЗапросHttp — устанавливает тело запроса из строки.
  • УстановитьТело(Поток: ПотокЧтения): ЗапросHttp — устанавливает тело запроса из потока. Запрос выполняется с использованием механизма Chunked transfer encoding. Чтение переданного потока осуществляется только в момент вызова метода ЗапросHttp.Выполнить(). После выполнения запроса поток закрывается.
  • УстановитьТело(Поток: ПотокЧтения, Размер: РазмерБайтов|Число): ЗапросHttp — устанавливает тело запроса из потока. Чтение переданного потока осуществляется только в момент вызова метода ЗапросHttp.Выполнить(). После выполнения запроса поток закрывается.
  • УстановитьТело(Файл: Файл): ЗапросHttp — устанавливает тело запроса из файла. Чтение переданного файла осуществляется только в момент вызова метода ЗапросHttp.Выполнить().

После того как наш запрос готов, его необходимо выполнить. Для этого служит метод ЗапросHttp.Выполнить(). Внутри вызова этого метода выполняются все действия HTTP-взаимодействия. Результат вызова помещается в экземпляр типа ОтветHttp, который содержит полученные от сервера данные.