Решение задач 1С:Предприятие 8 на примерах

HTTP-запросы из 1С к REST API: GET, POST и JSON

Интеграция и веб-сервисыОпубликовано

Интеграция 1С с внешними сервисами почти всегда сводится к HTTP-запросам: получить курс, отправить заказ, передать документ в маркетплейс. В статье — рабочий код на встроенном языке для GET и POST запросов по HTTPS, авторизация Bearer и Basic, передача JSON, работа через прокси, отправка файла и функция-обёртка, которую можно сразу взять в общий модуль.

Задача: обратиться из 1С к внешнему API

Почти любой современный сервис отдаёт данные через REST API: запрос по HTTPS, параметры в адресе или JSON в теле, ответ в JSON. Для 1С API интеграция строится на трёх объектах: HTTPСоединение описывает сервер, HTTPЗапрос — ресурс, заголовки и тело, HTTPОтвет — то, что вернул сервер. Ниже по шагам: простой GET, POST с JSON, авторизация, прокси, файлы и общая функция для всего этого.

GET запрос по HTTPS

Минимальный 1С get запрос: создаём защищённое соединение, отправляем запрос, проверяем код состояния и читаем тело как строку.

&НаСервереБезКонтекста
Функция ПолучитьКурсы()

	Сервер = "api.example.com";
	Ресурс = "/v1/rates?base=" + КодироватьСтроку("RUB", СпособКодированияСтроки.КодировкаURL);

	Соединение = Новый HTTPСоединение(Сервер, 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL);

	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Accept", "application/json");

	Запрос = Новый HTTPЗапрос(Ресурс, Заголовки);
	Ответ = Соединение.Получить(Запрос);

	Если Ответ.КодСостояния <> 200 Тогда
		ВызватьИсключение СтрШаблон("Сервис вернул код %1: %2",
			Ответ.КодСостояния, Ответ.ПолучитьТелоКакСтроку());
	КонецЕсли;

	Чтение = Новый ЧтениеJSON;
	Чтение.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
	Данные = ПрочитатьJSON(Чтение, Истина);
	Чтение.Закрыть();

	Возврат Данные;

КонецФункции

Обратите внимание: в конструктор HTTPСоединение передаётся только имя хоста, без https:// и без пути. Путь и параметры идут в первый аргумент HTTPЗапрос. Шестой параметр конструктора — таймаут в секундах; без него при зависании сервиса сеанс будет ждать очень долго. Седьмой — объект ЗащищенноеСоединениеOpenSSL, он включает HTTPS. Для обычного HTTP порт 80 и седьмой параметр не указывается.

POST запрос с JSON-телом

Для 1С post запрос отличается тем, что у него есть тело. Структуру сериализуем в строку через ЗаписьJSON и кладём в запрос методом УстановитьТелоИзСтроки. Важная деталь — отключить BOM: многие серверы не ожидают служебных байтов в начале JSON и отвечают ошибкой разбора.

&НаСервереБезКонтекста
Функция СоздатьЗаказ(Токен)

	Заказ = Новый Структура;
	Заказ.Вставить("number", "A-1024");
	Заказ.Вставить("amount", 1500.50);
	Заказ.Вставить("items", Новый Массив);
	Заказ.items.Добавить(Новый Структура("sku, qty", "SKU-1", 2));

	Запись = Новый ЗаписьJSON;
	Запись.УстановитьСтроку();
	ЗаписатьJSON(Запись, Заказ);
	ТекстТела = Запись.Закрыть();

	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
	Заголовки.Вставить("Accept", "application/json");
	Заголовки.Вставить("Authorization", "Bearer " + Токен);

	Запрос = Новый HTTPЗапрос("/v1/orders", Заголовки);
	Запрос.УстановитьТелоИзСтроки(ТекстТела, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

	Соединение = Новый HTTPСоединение("api.example.com", 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
	Ответ = Соединение.ОтправитьДляОбработки(Запрос);

	Возврат Ответ;

КонецФункции

Для методов PUT, PATCH и DELETE используйте универсальный Соединение.ВызватьHTTPМетод("PATCH", Запрос): первым параметром передаётся имя метода строкой.

Авторизация: Bearer и Basic

С токеном всё просто — заголовок Authorization: Bearer <токен>, как в примере выше. Для Basic-авторизации есть два способа. Первый — передать логин и пароль третьим и четвёртым параметрами конструктора HTTPСоединение. Второй — сформировать заголовок вручную; это надёжнее, если сервер не присылает запрос авторизации и ждёт заголовок сразу:

Функция ЗаголовокBasic(Логин, Пароль)
	ДД = ПолучитьДвоичныеДанныеИзСтроки(Логин + ":" + Пароль, КодировкаТекста.UTF8, Ложь);
	Строка64 = Base64Строка(ДД);
	// Base64Строка разбивает длинный результат на строки — убираем переносы
	Строка64 = СтрЗаменить(Строка64, Символы.ВК, "");
	Строка64 = СтрЗаменить(Строка64, Символы.ПС, "");
	Возврат "Basic " + Строка64;
КонецФункции

Токены и пароли не храните в коде модуля: используйте константы с ограниченным доступом или безопасное хранилище БСП, если оно есть в конфигурации.

Работа через прокси

Если сервер 1С выходит в интернет через прокси, передайте объект ИнтернетПрокси пятым параметром соединения. Конструктор с параметром Истина берёт системные настройки, с Ложь — создаёт пустой объект, который заполняется вручную.

Прокси = Новый ИнтернетПрокси(Ложь);
Прокси.Установить("https", "proxy.local", 3128, "proxyuser", "proxypass", Ложь);
Прокси.НеИспользоватьПроксиДляЛокальныхАдресов = Истина;

Соединение = Новый HTTPСоединение("api.example.com", 443, , , Прокси, 30, Новый ЗащищенноеСоединениеOpenSSL);

Отправка файла

Если API принимает файл «как есть» в теле запроса, достаточно метода УстановитьИмяФайлаТела или УстановитьТелоИзДвоичныхДанных. Для формы multipart/form-data тело собирается вручную из частей:

Функция ОтправитьФайл(Соединение, ПутьКФайлу, ИмяФайла)

	Граница = СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
	ПС = Символы.ВК + Символы.ПС;

	Начало = "--" + Граница + ПС
		+ "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """" + ПС
		+ "Content-Type: application/octet-stream" + ПС + ПС;
	Конец = ПС + "--" + Граница + "--" + ПС;

	Части = Новый Массив;
	Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Начало, КодировкаТекста.UTF8, Ложь));
	Части.Добавить(Новый ДвоичныеДанные(ПутьКФайлу));
	Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Конец, КодировкаТекста.UTF8, Ложь));

	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);

	Запрос = Новый HTTPЗапрос("/v1/upload", Заголовки);
	Запрос.УстановитьТелоИзДвоичныхДанных(СоединитьДвоичныеДанные(Части));

	Возврат Соединение.ОтправитьДляОбработки(Запрос);

КонецФункции

Универсальная функция-обёртка с логированием

В реальной интеграции удобно иметь одну функцию в серверном общем модуле, которая выполняет запрос, ловит сетевые исключения, разбирает JSON и пишет ошибки в журнал регистрации. Тогда весь код 1С подключение по API сводится к одной строке вызова.

// Возвращает структуру: Успех, КодСостояния, Данные, ТекстОшибки
Функция ВыполнитьЗапросAPI(Метод, Ресурс, Тело = Неопределено, Токен = "") Экспорт

	Результат = Новый Структура("Успех, КодСостояния, Данные, ТекстОшибки", Ложь, 0, Неопределено, "");

	Заголовки = Новый Соответствие;
	Заголовки.Вставить("Accept", "application/json");
	Если ЗначениеЗаполнено(Токен) Тогда
		Заголовки.Вставить("Authorization", "Bearer " + Токен);
	КонецЕсли;

	Запрос = Новый HTTPЗапрос(Ресурс, Заголовки);

	Если Тело <> Неопределено Тогда
		Запись = Новый ЗаписьJSON;
		Запись.УстановитьСтроку();
		ЗаписатьJSON(Запись, Тело);
		Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
		Запрос.УстановитьТелоИзСтроки(Запись.Закрыть(), КодировкаТекста.UTF8,
			ИспользованиеByteOrderMark.НеИспользовать);
	КонецЕсли;

	Попытка
		Соединение = Новый HTTPСоединение("api.example.com", 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL);
		Ответ = Соединение.ВызватьHTTPМетод(ВРег(Метод), Запрос);
	Исключение
		Результат.ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
		ЗаписьЖурналаРегистрации("Интеграция.API", УровеньЖурналаРегистрации.Ошибка, , ,
			Метод + " " + Ресурс + Символы.ПС + Результат.ТекстОшибки);
		Возврат Результат;
	КонецПопытки;

	Результат.КодСостояния = Ответ.КодСостояния;
	ТекстОтвета = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
	Результат.Успех = (Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300);

	Если Результат.Успех И ЗначениеЗаполнено(ТекстОтвета) Тогда
		Попытка
			Чтение = Новый ЧтениеJSON;
			Чтение.УстановитьСтроку(ТекстОтвета);
			Результат.Данные = ПрочитатьJSON(Чтение, Истина);
			Чтение.Закрыть();
		Исключение
			Результат.Данные = ТекстОтвета;
		КонецПопытки;
	ИначеЕсли Не Результат.Успех Тогда
		Результат.ТекстОшибки = ТекстОтвета;
		ЗаписьЖурналаРегистрации("Интеграция.API", УровеньЖурналаРегистрации.Предупреждение, , ,
			СтрШаблон("%1 %2: код %3%4%5", Метод, Ресурс, Ответ.КодСостояния, Символы.ПС, Лев(ТекстОтвета, 2000)));
	КонецЕсли;

	Возврат Результат;

КонецФункции

Вызов выглядит так: Рез = ИнтеграцияAPI.ВыполнитьЗапросAPI("POST", "/v1/orders", Заказ, Токен). Адрес сервера в реальном модуле лучше вынести в параметр или настройку. Тело ответа обрезается до 2000 символов, чтобы журнал регистрации не разрастался.

Как разбирать коды ответа

КодЧто означаетЧто делать
200, 201, 204УспехЧитать тело (у 204 его нет)
400Неверный запросПроверить JSON и обязательные поля, смотреть текст ответа
401, 403Нет авторизации или правПроверить токен, срок его действия, заголовок Authorization
404Ресурс не найденПроверить путь и версию API
415Неподдерживаемый типУказать Content-Type: application/json
429Слишком много запросовСделать паузу и повторить, соблюдать лимиты сервиса
500–504Ошибка сервераПовторить позже, залогировать запрос

Повторные попытки и лимиты сервиса

Внешний сервис может временно не отвечать или ограничивать частоту обращений. Если интеграция работает в регламентном задании, разумно повторять запрос при сетевой ошибке и кодах 429, 502, 503, 504, но не бесконечно, а два-три раза с паузой. Запросы с кодами 400, 401 и 404 повторять бессмысленно — ошибка не исчезнет сама. Многие сервисы в ответе 429 присылают заголовок Retry-After с числом секунд, которое нужно подождать; его можно прочитать из Ответ.Заголовки. Обратите внимание на регистр имён: сервер может прислать заголовок как Retry-After или retry-after, поэтому при поиске в соответствии лучше перебрать ключи и сравнить их через НРег.

// Решает, имеет ли смысл повторить запрос позже
Функция НужноПовторить(Рез) Экспорт
	Если Рез.Успех Тогда
		Возврат Ложь;
	КонецЕсли;
	// 0 — ответ не получен (сетевая ошибка или таймаут)
	Возврат Рез.КодСостояния = 0
		Или Рез.КодСостояния = 429
		Или Рез.КодСостояния = 502
		Или Рез.КодСостояния = 503
		Или Рез.КодСостояния = 504;
КонецФункции

// В регламентном задании
Рез = ИнтеграцияAPI.ВыполнитьЗапросAPI("POST", "/v1/orders", Заказ, Токен);
Если ИнтеграцияAPI.НужноПовторить(Рез) Тогда
	// оставить заказ в очереди отправки и увеличить счётчик попыток;
	// следующий запуск регламентного задания отправит его снова
ИначеЕсли Не Рез.Успех Тогда
	// ошибка данных: пометить заказ как ошибочный и показать текст пользователю
КонецЕсли;

Во встроенном языке нет оператора «заснуть», поэтому паузу между повторами проще всего получить за счёт расписания: неотправленные объекты остаются в очереди (например, в регистре сведений со счётчиком попыток), а регламентное задание раз в несколько минут обрабатывает очередь заново. После трёх-пяти неудачных попыток объект стоит пометить как проблемный, чтобы он не отправлялся бесконечно.

Где хранить настройки и как отлаживать

Адрес сервера, порт, токен и таймаут лучше хранить не в коде, а в константах или отдельном справочнике настроек интеграции. Тогда тестовый и рабочий контуры переключаются без изменения конфигурации, а токен можно обновить, не привлекая программиста. Для отладки удобно сначала повторить запрос в Postman или через curl: если там всё работает, а из 1С нет, сравните заголовки и тело байт в байт. Частые расхождения — лишний BOM, другой Content-Type, неэкранированные символы в параметрах. На время отладки можно сохранять тело запроса и ответа в файл или в журнал регистрации, но в рабочей базе не пишите в журнал токены и персональные данные.

Если сервис работает только по TLS 1.2 и выше, а соединение не устанавливается, проверьте версию платформы и операционной системы на сервере 1С: в старых релизах и старых ОС поддержка современных протоколов может отсутствовать. Ошибки проверки сертификата решаются передачей в ЗащищенноеСоединениеOpenSSL хранилища корневых сертификатов операционной системы или файла с нужным сертификатом.

Типичные ошибки

  • Передача в HTTPСоединение полного URL с https:// и путём — соединение не установится. Нужен только хост.
  • Отсутствие ЗащищенноеСоединениеOpenSSL при обращении на порт 443: сервер ответит ошибкой или закроет соединение.
  • BOM в теле запроса из-за УстановитьТелоИзСтроки без третьего параметра.
  • Нет таймаута — при недоступности сервиса фоновое задание висит часами.
  • Кириллица и пробелы в параметрах GET без КодироватьСтроку.
  • Ожидание, что исключение возникнет при коде 4xx или 5xx. Платформа исключение не вызывает: код нужно проверять самостоятельно, исключение будет только при сетевой ошибке.
  • Если сервер отвечает 301 или 302, посмотрите заголовок Location в Ответ.Заголовки — обычно это переход с HTTP на HTTPS или на другой адрес, и запрос нужно отправить по новому адресу.

REST в 1С в обратную сторону

Всё сказанное касается вызова чужих API из 1С. Если нужно, чтобы внешние системы обращались к самой базе, rest в 1С реализуется через HTTP-сервисы в конфигурации (свои URL-шаблоны и обработчики методов) либо через стандартный интерфейс OData, который отдаёт объекты конфигурации без программирования. В обоих случаях базу публикуют на веб-сервере Apache или IIS, а клиенты работают с ней как с обычным 1С REST API.

Актуальность для новых версий

Код рассчитан на платформу 8.3 актуальных релизов (8.3.1x–8.3.2x). Метод ВызватьHTTPМетод и функция СоединитьДвоичныеДанные доступны в 8.3 начиная с промежуточных релизов, в старых версиях вместо первого используйте Получить или ОтправитьДляОбработки. Для проверки сертификатов сервера по хранилищу Windows можно передать в ЗащищенноеСоединениеOpenSSL объект СертификатыУдостоверяющихЦентровОС. HTTP-запросы выполняйте на сервере: в веб-клиенте объект HTTPСоединение недоступен.

Частые вопросы

Где выполнять HTTP-запрос — на клиенте или на сервере?

Лучше на сервере: там доступен HTTPСоединение во всех вариантах клиента, а ключи и токены не попадают на компьютер пользователя. С клиента вызывайте серверную процедуру общего модуля.

Почему POST запрос из 1С возвращает 415 или 400?

Чаще всего не указан заголовок Content-Type: application/json или в тело попал BOM. Устанавливайте тело методом УстановитьТелоИзСтроки с параметром ИспользованиеByteOrderMark.НеИспользовать.

Как передать параметры в GET запросе?

Добавьте их в строку ресурса после знака вопроса, а значения закодируйте функцией КодироватьСтроку с СпособКодированияСтроки.КодировкаURL, иначе пробелы и кириллица сломают адрес.

Может ли 1С сама предоставлять REST API?

Да. Для этого в конфигурации создаются HTTP-сервисы или включается стандартный интерфейс OData, после чего база публикуется на веб-сервере.

XML в 1С программно: запись, чтение, DOM и выгрузка данных

Интеграция и веб-сервисы

Готовые примеры работы с XML на встроенном языке 1С: запись и чтение, DOM с XPath, преобразование XML в структуру, выгрузка справочника и разбор ошибок парсинга.

Работа с JSON в 1С: чтение, запись, даты и типичные ошибки

Интеграция и веб-сервисы

Практическое руководство по JSON в 1С: чтение строки и файла, Структура против Соответствия, запись массивов, даты, сериализация через XDTO и разбор частых ошибок.

Как убрать окно авторизации при обращении к веб-сервису опубликованной базы 1С

Интеграция и веб-сервисы

При обращении к веб-сервису опубликованной базы 1С браузер каждый раз спрашивает логин и пароль. Показываем, как прописать пользователя в файле default.vrd и убрать окно авторизации.

Файл базы данных поврежден в 1С 8.3: как исправить ошибку СУБД

Базы данных и СУБД

Пошаговое восстановление файловой базы 1С при ошибке «Файл базы данных поврежден»: резервная копия, chdbfl.exe, тестирование и исправление и запасные варианты.