HTTP-сервис 1С — объект конфигурации, который принимает запросы по адресу вида https://сервер/база/hs/api/... и возвращает ответ, собранный кодом его модуля: JSON, текст или файл. Через него сайт забирает остатки, CRM передаёт заказы, мобильное приложение читает справочники — без COM-соединения и файловых выгрузок. Ниже пример HTTP-сервиса 1С 8.3 от объекта метаданных до проверки из другой базы: методы GET и POST, разбор JSON, коды ответа, авторизация.
Листинги складываются в один модуль. Справочник Номенклатура и его реквизиты названы как в УТ 11, БП 3.0 и ERP — в своей конфигурации подставьте свои имена.
У сервиса есть корневой URL и шаблоны URL с параметрами в фигурных скобках. У каждого шаблона — методы: HTTP-глагол и имя функции-обработчика. Адрес вызова: http://сервер/публикация/hs/корневой-URL/шаблон. Префикс hs постоянный, он отличает HTTP-сервисы от веб-сервисов (ws).
| Шаблон URL | Шаблон | Метод | Обработчик | Назначение |
|---|---|---|---|---|
| Пинг | /v1/ping | GET | ПингGET | проверка публикации |
| Товары | /v1/products | GET | ТоварыGET | список по страницам |
| Товар | /v1/products/{code} | GET | ТоварGET | товар по коду |
| Заказы | /v1/orders | POST | ЗаказыPOST | приём заказа |
API, корневой URL api.Товар, шаблон /v1/products/{code}. Остальные — по таблице.GET, HTTP-метод GET. В свойстве «Обработчик» нажмите кнопку с лупой — в модуле сервиса появится функция-заготовка.КлючAPIСайта (Строка, 64) и непериодический регистр сведений ЗаказыССайта: измерение ВнешнийИД (Строка, 50), ресурс Обработан (Булево), реквизиты ДатаПолучения (Дата и время) и ТелоЗаказа (Строка неограниченной длины).ИспользованиеAPI: чтение справочника и константы, чтение и изменение регистра.api_site с этой ролью, флагом «Аутентификация 1С:Предприятия» и длинным случайным паролем.Первый обработчик — диагностический: по нему проверяют публикацию до того, как появится логика.
Функция ПингGET(Запрос)
Строки = Новый Массив;
Строки.Добавить("Сервис работает");
Строки.Добавить("Метод: " + Запрос.HTTPМетод);
Строки.Добавить("Базовый URL: " + Запрос.БазовыйURL);
Строки.Добавить("Относительный URL: " + Запрос.ОтносительныйURL);
Строки.Добавить("Пользователь: " + ИмяПользователя());
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "text/plain; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(СтрСоединить(Строки, Символы.ПС), КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
HTTPСервисЗапрос и обязан вернуть HTTPСервисОтвет. Модуль сервиса выполняется только на сервере, директивы компиляции в нём не пишут.БазовыйURL и ОтносительныйURL показывают, какую часть адреса платформа сопоставляет с шаблонами.УстановитьТелоИзСтроки отключает BOM — служебные байты в начале тела, на которых спотыкаются JSON-парсеры сайтов.trade (станет частью адреса), веб-сервер — Apache 2.4, каталог — например, C:\www\trade.API.В каталоге публикации появится default.vrd. Фрагмент для серверной базы:
<?xml version="1.0" encoding="UTF-8"?>
<point xmlns="http://v8.1c.ru/8.2/virtual-resource-system"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
base="/trade"
ib="Srvr="srv1c";Ref="trade";">
<httpServices publishByDefault="false">
<service name="API"
rootUrl="api"
enable="true"
reuseSessions="dontuse"
sessionMaxAge="20"
poolSize="10"
poolTimeout="5"/>
</httpServices>
</point>
base — имя публикации в адресе, ib — строка соединения; для файловой базы в ней File="D:\Bases\Trade";. При publishByDefault="false" доступны только сервисы из элементов service с enable="true". После ручной правки файла перезапустите веб-сервер.
Откройте в браузере http://localhost/trade/hs/api/v1/ping. Браузер спросит логин и пароль — это Basic-аутентификация, введите данные api_site. Пришёл текст «Сервис работает» — публикация в порядке.
Чтобы обработчики не собирали ответ заново, сериализацию выносят в две функции модуля. Подробнее о записи и чтении JSON — в статье о JSON в 1С.
// Ответ с телом JSON и заданным кодом состояния
Функция ОтветJSON(КодСостояния, Данные)
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(Запись, Данные);
ТекстJSON = Запись.Закрыть();
Ответ = Новый HTTPСервисОтвет(КодСостояния);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(ТекстJSON, КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
КонецФункции
// Ошибка в едином формате: {"error": "текст"}
Функция ОтветОшибка(КодСостояния, ТекстОшибки)
Возврат ОтветJSON(КодСостояния, Новый Структура("error", ТекстОшибки));
КонецФункции
ЗаписатьJSON принимает структуры, соответствия со строковыми ключами, массивы, строки, числа, даты и булево; Неопределено становится null. Ссылку на объект он не примет — передавайте код или УникальныйИдентификатор() строкой.ПереносСтрокJSON.Нет убирает переносы и отступы — ответ компактнее.{"error": "текст"} — сайту хватает одной ветки разбора.Платформа пускает в обработчик только пользователя информационной базы: логин и пароль приходят в заголовке Authorization: Basic. Поверх этого код проверяет роль и ключ интеграции. Утёкший пароль без ключа бесполезен, а сотрудник, который вызовет API со своим логином, получит 403.
// Значение заголовка без учёта регистра имени; пустая строка, если заголовка нет
Функция ЗначениеЗаголовка(Запрос, ИмяЗаголовка)
Для Каждого Заголовок Из Запрос.Заголовки Цикл
Если НРег(Заголовок.Ключ) = НРег(ИмяЗаголовка) Тогда
Возврат Заголовок.Значение;
КонецЕсли;
КонецЦикла;
Возврат "";
КонецФункции
// Неопределено - доступ разрешён, иначе готовый ответ с ошибкой
Функция ПроверитьДоступ(Запрос)
Если Не РольДоступна("ИспользованиеAPI") Тогда
Возврат ОтветОшибка(403, "Нет прав на использование API");
КонецЕсли;
КлючИзЗапроса = ЗначениеЗаголовка(Запрос, "X-Api-Key");
КлючВБазе = Константы.КлючAPIСайта.Получить();
Если ПустаяСтрока(КлючВБазе) Или КлючИзЗапроса <> КлючВБазе Тогда
ЗаписьЖурналаРегистрации("API.Доступ", УровеньЖурналаРегистрации.Предупреждение, , ,
"Неверный X-Api-Key, пользователь " + ИмяПользователя());
Возврат ОтветОшибка(401, "Не передан или неверен заголовок X-Api-Key");
КонецЕсли;
Возврат Неопределено;
КонецФункции
ЗначениеЗаголовка не зависит от регистра: по HTTP/2 и через часть прокси имена заголовков приходят строчными буквами.API.Доступ.Неопределено. Первые четыре строки каждого рабочего обработчика ниже проверяют именно это.Если внешняя система не умеет Basic, логин и пароль технического пользователя прописывают в строку ib файла default.vrd параметрами Usr и Pwd. Платформа входит под этим пользователем без заголовка Authorization, а доступ держится на ключе; каталог публикации тогда закройте от посторонних.
Значение из {code} лежит в Запрос.ПараметрыURL, параметры после знака вопроса — в Запрос.ПараметрыЗапроса. Оба свойства — фиксированные соответствия со строковыми значениями. Товар по коду:
Функция ТоварGET(Запрос)
ОтказВДоступе = ПроверитьДоступ(Запрос);
Если ОтказВДоступе <> Неопределено Тогда
Возврат ОтказВДоступе;
КонецЕсли;
КодТовара = Запрос.ПараметрыURL.Получить("code");
Если Не ЗначениеЗаполнено(КодТовара) Тогда
Возврат ОтветОшибка(400, "Не указан код товара");
КонецЕсли;
ЗапросКБазе = Новый Запрос;
ЗапросКБазе.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Код = &Код
| И НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления";
ЗапросКБазе.УстановитьПараметр("Код", КодТовара);
Выборка = ЗапросКБазе.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат ОтветОшибка(404, "Товар с кодом " + КодТовара + " не найден");
КонецЕсли;
Товар = Новый Структура;
Товар.Вставить("id", Строка(Выборка.Ссылка.УникальныйИдентификатор()));
Товар.Вставить("code", СокрЛП(Выборка.Код));
Товар.Вставить("name", Выборка.Наименование);
Товар.Вставить("sku", Выборка.Артикул);
Возврат ОтветJSON(200, Товар);
КонецФункции
ПараметрыURL совпадает с именем в фигурных скобках шаблона: {code} → "code".УникальныйИдентификатор(): он не меняется при перенумерации кодов.Список товаров по страницам, адрес /v1/products?limit=100&after=00-00000123:
Функция ТоварыGET(Запрос)
ОтказВДоступе = ПроверитьДоступ(Запрос);
Если ОтказВДоступе <> Неопределено Тогда
Возврат ОтказВДоступе;
КонецЕсли;
Лимит = 50;
ЛимитСтрокой = Запрос.ПараметрыЗапроса.Получить("limit");
Если ЛимитСтрокой <> Неопределено Тогда
Попытка
Лимит = Число(ЛимитСтрокой);
Исключение
Возврат ОтветОшибка(400, "Параметр limit должен быть числом");
КонецПопытки;
КонецЕсли;
Если Лимит < 1 Или Лимит > 500 Или Лимит <> Цел(Лимит) Тогда
Возврат ОтветОшибка(400, "Параметр limit: целое число от 1 до 500");
КонецЕсли;
ПослеКода = Запрос.ПараметрыЗапроса.Получить("after");
Если ПослеКода = Неопределено Тогда
ПослеКода = "";
КонецЕсли;
ЗапросКБазе = Новый Запрос;
ЗапросКБазе.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 50
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа
| И НЕ Номенклатура.ПометкаУдаления
| И Номенклатура.Код > &ПослеКода
|
|УПОРЯДОЧИТЬ ПО
| Код";
ЗапросКБазе.Текст = СтрЗаменить(ЗапросКБазе.Текст, "ПЕРВЫЕ 50", "ПЕРВЫЕ " + Формат(Лимит, "ЧГ=0"));
ЗапросКБазе.УстановитьПараметр("ПослеКода", ПослеКода);
Выборка = ЗапросКБазе.Выполнить().Выбрать();
Товары = Новый Массив;
ПоследнийКод = Неопределено;
Пока Выборка.Следующий() Цикл
ПоследнийКод = СокрЛП(Выборка.Код);
Товары.Добавить(Новый Структура("code, name", ПоследнийКод, Выборка.Наименование));
КонецЦикла;
Результат = Новый Структура;
Результат.Вставить("items", Товары);
Результат.Вставить("next", ?(Товары.Количество() = Лимит, ПоследнийКод, Неопределено));
Возврат ОтветJSON(200, Результат);
КонецФункции
Попытка: Число("abc") вызывает исключение, а клиент должен получить 400, а не 500.ПЕРВЫЕ нельзя передать параметром, поэтому проверенный лимит подставляется в текст через СтрЗаменить.next содержит последний код страницы, следующий запрос передаёт его в after. Дальние страницы не замедляются.ЭтоГруппа в обоих запросах.Заказ с сайта не проводят прямо в обработчике: тело сохраняют в регистр и сразу отвечают, а документ создаёт регламентное задание. Сайт не ждёт проведения, ошибка в документе не теряет заказ.
Функция ЗаказыPOST(Запрос)
ОтказВДоступе = ПроверитьДоступ(Запрос);
Если ОтказВДоступе <> Неопределено Тогда
Возврат ОтказВДоступе;
КонецЕсли;
ТекстТела = Запрос.ПолучитьТелоКакСтроку("UTF-8");
Если ПустаяСтрока(ТекстТела) Тогда
Возврат ОтветОшибка(400, "Пустое тело запроса");
КонецЕсли;
Попытка
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТекстТела);
Заказ = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
Исключение
Возврат ОтветОшибка(400, "Тело запроса не является корректным JSON");
КонецПопытки;
Если ТипЗнч(Заказ) <> Тип("Соответствие") Тогда
Возврат ОтветОшибка(422, "Ожидается JSON-объект заказа");
КонецЕсли;
ВнешнийИД = Заказ.Получить("id");
Если ТипЗнч(ВнешнийИД) <> Тип("Строка") Или ПустаяСтрока(ВнешнийИД) Или СтрДлина(ВнешнийИД) > 50 Тогда
Возврат ОтветОшибка(422, "Поле id: строка от 1 до 50 символов");
КонецЕсли;
Позиции = Заказ.Получить("items");
Если ТипЗнч(Позиции) <> Тип("Массив") Или Позиции.Количество() = 0 Тогда
Возврат ОтветОшибка(422, "Поле items: непустой массив позиций");
КонецЕсли;
Для Каждого Позиция Из Позиции Цикл
Если ТипЗнч(Позиция) <> Тип("Соответствие")
Или ТипЗнч(Позиция.Получить("code")) <> Тип("Строка")
Или ТипЗнч(Позиция.Получить("qty")) <> Тип("Число")
Или Позиция.Получить("qty") <= 0 Тогда
Возврат ОтветОшибка(422, "Позиция заказа: нужны code (строка) и qty (число больше нуля)");
КонецЕсли;
КонецЦикла;
МенеджерЗаписи = РегистрыСведений.ЗаказыССайта.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ВнешнийИД = ВнешнийИД;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
Возврат ОтветJSON(200, Новый Структура("id, status", ВнешнийИД, "duplicate"));
КонецЕсли;
МенеджерЗаписи.ВнешнийИД = ВнешнийИД;
МенеджерЗаписи.ДатаПолучения = ТекущаяДатаСеанса();
МенеджерЗаписи.ТелоЗаказа = ТекстТела;
МенеджерЗаписи.Обработан = Ложь;
Попытка
МенеджерЗаписи.Записать();
Исключение
ЗаписьЖурналаРегистрации("API.Заказы", УровеньЖурналаРегистрации.Ошибка, , ,
"Заказ " + ВнешнийИД + ": " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат ОтветОшибка(500, "Заказ не сохранён, повторите запрос позже");
КонецПопытки;
Возврат ОтветJSON(201, Новый Структура("id, status", ВнешнийИД, "accepted"));
КонецФункции
ПрочитатьJSON(Чтение, Истина) читает объекты в Соответствие. В структуру ключи вроде order-id не прочитаются — будет исключение.id заказа из внешней системы — измерение регистра: повтор после тайм-аута получит 200 и duplicate, второй заказ не появится.GET-методы проверяют в браузере. Для POST удобен Postman: метод POST, адрес http://localhost/trade/hs/api/v1/orders, на закладке Authorization — Basic Auth с логином api_site, в Headers — X-Api-Key, в Body → raw → JSON — тело заказа:
{
"id": "W-1001",
"items": [
{"code": "00-00000123", "qty": 2},
{"code": "00-00000456", "qty": 1}
]
}
Первый вызов вернёт 201 и accepted, повторный — 200 и duplicate. Из другой базы 1С тот же запрос отправляет форма внешней обработки с командой ОтправитьТестовыйЗаказ:
&НаКлиенте
Процедура ОтправитьТестовыйЗаказ(Команда)
ПоказатьПредупреждение(, ОтправитьЗаказНаСервере());
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОтправитьЗаказНаСервере()
// Параметры теста: подставьте свои
Сервер = "localhost";
Порт = 80;
Логин = "api_site";
Пароль = "пароль пользователя api_site";
КлючAPI = "значение константы КлючAPIСайта";
Позиции = Новый Массив;
Позиции.Добавить(Новый Структура("code, qty", "00-00000123", 2));
Заказ = Новый Структура("id, items", "TEST-" + Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMddHHmmss"), Позиции);
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Заказ);
ТекстЗаказа = Запись.Закрыть();
// Для HTTPS: Порт = 443 и Новый ЗащищенноеСоединениеOpenSSL вместо Неопределено
Соединение = Новый HTTPСоединение(Сервер, Порт, Логин, Пароль, , 30, Неопределено);
ЗапросHTTP = Новый HTTPЗапрос("/trade/hs/api/v1/orders");
ЗапросHTTP.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
ЗапросHTTP.Заголовки.Вставить("X-Api-Key", КлючAPI);
ЗапросHTTP.УстановитьТелоИзСтроки(ТекстЗаказа, КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
Попытка
Ответ = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
Исключение
Возврат "Сервис недоступен: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Возврат СтрШаблон("Код %1: %2", Ответ.КодСостояния, Ответ.ПолучитьТелоКакСтроку("UTF-8"));
КонецФункции
&НаСервереБезКонтекста: запрос выполняется на сервере без передачи данных формы, клиентская процедура только показывает результат.HTTPСоединение уходят как Basic-аутентификация, заголовок Authorization собирать не нужно.Новый ЗащищенноеСоединениеOpenSSL. Таймауты, прокси и разбор ответов — в статье об HTTP-запросах из 1С.| Код или текст | Причина | Что сделать |
|---|---|---|
| 404 Not Found | В адресе нет /hs/, опечатка в корневом URL или шаблоне, сервис не отмечен при публикации, Apache не перезапущен | Вызвать /v1/ping, сверить адрес с БазовыйURL, открыть default.vrd |
| 405 Method Not Allowed | Шаблон найден, но метода для этого глагола нет: POST на шаблон только с GET | Добавить метод в шаблон |
| 401 Unauthorized | Нет заголовка Authorization, неверный пароль, у пользователя выключена аутентификация 1С:Предприятия | Проверить пользователя и пароль |
| 500 и «Нарушение прав доступа!» | Роли не хватает права на объект, который читает или пишет обработчик | Выдать права «Чтение» и «Изменение» на нужные объекты |
500 и «Передано значение недопустимого типа» из ЗаписатьJSON | В структуру ответа попала ссылка на справочник или документ | Передавать код или Строка(Ссылка.УникальныйИдентификатор()) |
| 500 у файловой базы на Apache | У учётной записи службы Apache нет прав на каталог базы | Дать ей полный доступ к каталогу |
| Apache не стартует: «Cannot load …wsap24.dll into server» | Разрядность Apache и модуля расширения 1С не совпадает | Поставить модули расширения той же разрядности |
| «Unexpected token» в JavaScript, «Syntax error» в PHP при разборе ответа | В начале тела BOM | Передавать ИспользованиеByteOrderMark.НеИспользовать в УстановитьТелоИзСтроки |
| Кириллица в теле заказа нечитаема | Тело прочитано не в той кодировке | Указывать "UTF-8" в ПолучитьТелоКакСтроку, клиенту — charset=utf-8 |
Веб-сервис работает по SOAP: XML, WSDL-описание, типы через XDTO. HTTP-сервис — обычный REST: адреса, методы и формат ответа определяет разработчик. Для сайтов, мобильных приложений и JavaScript проще HTTP-сервис; веб-сервис выбирают, когда другая сторона требует SOAP.
Да, HTTP-сервис создаётся в расширении конфигурации, код модуля не меняется. При публикации включите публикацию HTTP-сервисов расширений, иначе адрес вернёт 404.
Вместо УстановитьТелоИзСтроки вызовите Ответ.УстановитьТелоИзДвоичныхДанных(Данные) и добавьте заголовки Content-Type: application/pdf и Content-Disposition: attachment; filename="invoice.pdf".
Если сервис нужен под конкретный сайт, CRM или маркетплейс — с согласованным контрактом, тестовым контуром и сопровождением, — быстрее поручить его исполнителю: услуги по доработке и интеграции 1С. Опишите конфигурацию, внешнюю систему и нужные методы в форме задания, оценка бесплатная.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.