О чем это здесь рассказывают
На этот раз мы поговорим о механизме платформы 1С:Предприятие — HTTP-сервисы. Подробнее Вы можете прочитать на официальном сайте или посмотреть примеры на Infostart. Там и вывод графиков, и передача данных, RSS-лента. Кто-то даже реализовал мини-CMS на HTTP-сервисах! =) Мы же рассмотрим создание самого примитивного HTTP-сервиса с минимально полезной функцией.
Механизм HTTP-сервисов открыл довольно обширные возможности по интеграции, расширению функционала, оптимизации существующих приложений и т.д. Чем то это похоже на WebAPI в .NET, но, конечно же, имеет куда больше ограничений и "заточено" под более узкий спектр задач. Список всего того, что можно сделать с помощью HTTP-сервисов настолько большой, что в публикации не хватит места, чтобы сохранить его!
Поэтому в статье мы создадим небольшой сервис, который будет использоваться для вывода простейшей HTML-странички. На ней будут выполняться асинхронные запросы к методам этого сервиса для получения данных. Сразу покажу окончательный результат.
Небольшой, но результат!
Конфигуратор — наше все!
Откроем конфигуратор и добавим новый HTTP-сервис. В нашем случае у сервиса будут три метода:
- "MainPage" — метод типа GET, который возвращает HTML-страницу с минимальным внесением изменений в разметку (о об этом чуть позже). Страницу Вы уже видели выше.
- "Products" — метод типа POST, который принимает в теле запроса параметр "query" с текстом, по которому будет выполняться поиск товаров в базе по наименованию. В качестве ответа формируется список найденных товаров в формате JSON.
- "Info" — метод типа POST, который в теле запроса принимает параметр "GUID" значение уникального идентификатора товара, а в ответ формирует список значений реквизитов товара (артикул, полное наименование, код и описание).
В конфигураторе это выглядит следующим образом:
В описании корня HTTP-сервиса самой важной настройкой является свойство "Корневой URL", которое отвечает за формирование URL-адреса, по которому мы будем обращаться ко всем методам этого сервиса.
Далее идут свойства шаблонов URL ("GetProducts", "Info" и "MainPage"). Шаблоны также отвечают за формирование URL, по которому мы будем обращаться к методам, но уже для каждого отдельного HTTP-метода сервиса. Если мы посмотрим на скриншоты выше, то понять принцип формирования адреса для каждого из методов не составит особого труда:
Для каждого шаблона URL был добавлен метод. Для одного шаблона может быть несколько методов, но в нашем примере схема сервиса упрощенная. Для каждого шаблона добавлено по одному методу без описания каких-либо дополнительных параметров в шаблоне URL.
На скриншотах выше Вы могли видеть свойства методов "GetProducts", "GetInfo" и "get". Первые два имеют тип POST и просто так к ним обратиться по их URL в браузере нельзя. По адресам этих методов нужно отправить соответствующие параметры методом POST, об этих параметрах мы говорили в самом начале. Метод "get" шаблона "MainPage" имеет тип "GET" и при обращении возвращает сформированную HTML-страницу. К этому методу мы можем обратиться непосредственно в адресной строке браузера.
Шаблон HTML-страницы хранится в общем макете с типом HTML-документ. При обращении к методу мы программно получаем текст страницы и возвращаем его в качестве ответа:
Функция MainPageget(Запрос)
МакетСтраницыПоиска = ПолучитьОбщийМакет("ГлавнаяСтраница");
Ответ = Новый HTTPСервисОтвет(200);
// Для корректного отображения веб-страницы установим тип содержимого как HTML
Ответ.Заголовки.Вставить("Content-Type","text/html; charset=utf-8");
// Получаем исходный код страницы и делаем подмену имени сервера
// в ссылках на методы HTTP-сервиса, чтобы AJAX-запросы отработали
// корректно
ТекстСтраницы = МакетСтраницыПоиска.ПолучитьТекст();
ТекстСтраницы = СтрЗаменить(ТекстСтраницы, "{ServerName}", Константы.ИмяСервера.Получить());
ТекстСтраницы = СтрЗаменить(ТекстСтраницы, "{DatabaseName}", Константы.ИмяБазы.Получить());
Ответ.УстановитьТелоИзСтроки(ТекстСтраницы);
Возврат Ответ;
КонецФункции
В ответе обязательно нужно указать тип возвращаемого содержимого, иначе браузер отобразит HTML-разметку страницы. Обработчики для методов "GetProducts" и "GetInfo" показаны на следующем листинге:
Функция ProductsGetProducts(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
// Обрабатываем присланный текст запроса для поиска номенклатуры
ТекстПоискаНоменклатуры = "";
Попытка
ТелоЗапроса = Запрос.ПолучитьТелоКакСтроку("UTF-8");
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоЗапроса);
ИмяСвойства = Неопределено;
ЗначениеСвойства = Неопределено;
Пока ЧтениеJSON.Прочитать()
И (ИмяСвойства = Неопределено ИЛИ ЗначениеСвойства = Неопределено) Цикл
Если ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.НачалоОбъекта Тогда
// Начинаем обрабатывать объект со строкой запроса
ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.ИмяСвойства Тогда
ИмяСвойства = ЧтениеJSON.ТекущееЗначение;
ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.Строка Тогда
ЗначениеСвойства = ЧтениеJSON.ТекущееЗначение;
КонецЕсли;
КонецЦикла;
Исключение
// Если при обработке возникает ошибка, то считем, что отбор не был установлен
КонецПопытки;
Если ИмяСвойства = "query"
И ЗначениеЗаполнено(ЗначениеСвойства) Тогда
ТекстПоискаНоменклатуры = Строка(ЗначениеСвойства);
КонецЕсли;
// Получаем список номенклатуры для отправки на страницу в формате JSON
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 10
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.ПолноеНаименование КАК ПолноеНаименование,
| Номенклатура.Описание КАК Описание
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование ПОДОБНО &Наименование
|
|УПОРЯДОЧИТЬ ПО
| Наименование";
Запрос.УстановитьПараметр("Наименование", "%"+ТекстПоискаНоменклатуры+"%");
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Попытка
ВремФайл = ПолучитьИмяВременногоФайла("json");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ВремФайл, "UTF-8");
ЗаписьJSON.ЗаписатьНачалоМассива();
Пока Выборка.Следующий() Цикл
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("GUID");
ЗаписьJSON.ЗаписатьЗначение(Строка(Выборка.Ссылка.УникальныйИдентификатор()));
ЗаписьJSON.ЗаписатьИмяСвойства("Name");
ЗаписьJSON.ЗаписатьЗначение(СокрЛП(Выборка.Наименование));
ЗаписьJSON.ЗаписатьКонецОбъекта();
КонецЦикла;
ЗаписьJSON.ЗаписатьКонецМассива();
ЗаписьJSON.УстановитьСтроку();
ЗаписьJSON.Закрыть();
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ВремФайл, "UTF-8");
СтрокаJSON = Текст.ПолучитьТекст();
Исключение
СтрокаJSON = "Ошибка: " + ОписаниеОшибки();
КонецПопытки;
Ответ.УстановитьТелоИзДвоичныхДанных(Новый ДвоичныеДанные(ВремФайл));
Попытка
УдалитьФайлы(ВремФайл);
Исключение
КонецПопытки;
Возврат Ответ;
КонецФункции
Функция Infoget(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
// Обрабатываем присланный в теле запроса GUID товара
ТекстGUID = "";
Попытка
ТелоЗапроса = Запрос.ПолучитьТелоКакСтроку("UTF-8");
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоЗапроса);
ИмяСвойства = Неопределено;
ЗначениеСвойства = Неопределено;
Пока ЧтениеJSON.Прочитать()
И (ИмяСвойства = Неопределено ИЛИ ЗначениеСвойства = Неопределено) Цикл
Если ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.НачалоОбъекта Тогда
// Начинаем обрабатывать объект со строкой запроса
ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.ИмяСвойства Тогда
ИмяСвойства = ЧтениеJSON.ТекущееЗначение;
ИначеЕсли ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.Строка Тогда
ЗначениеСвойства = ЧтениеJSON.ТекущееЗначение;
КонецЕсли;
КонецЦикла;
Исключение
// Если при обработке возникает ошибка, то считем, что отбор не был установлен
КонецПопытки;
Если ИмяСвойства = "GUID"
И ЗначениеЗаполнено(ЗначениеСвойства) Тогда
ТекстGUID = Строка(ЗначениеСвойства);
КонецЕсли;
СтрокаJSON = "{ }";
НоменклатураGUID = Неопределено;
Попытка
НоменклатураGUID = Новый УникальныйИдентификатор(ТекстGUID);
Исключение
КонецПопытки;
// Если GUID корректный, то формируем ответ в формате JSON со значениями
// реквизитов номенклатуры
Если ЗначениеЗаполнено(НоменклатураGUID) Тогда
Номенклатура = Справочники.Номенклатура.ПолучитьСсылку(НоменклатураGUID);
Попытка
ВремФайл = ПолучитьИмяВременногоФайла("json");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ВремФайл, "UTF-8");
ЗаписьJSON.ЗаписатьНачалоМассива();
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("Art");
ЗаписьJSON.ЗаписатьЗначение(СокрЛП(Номенклатура.Артикул));
ЗаписьJSON.ЗаписатьИмяСвойства("FullName");
ЗаписьJSON.ЗаписатьЗначение(СокрЛП(Номенклатура.ПолноеНаименование));
ЗаписьJSON.ЗаписатьИмяСвойства("Code");
ЗаписьJSON.ЗаписатьЗначение(СокрЛП(Номенклатура.Код));
ЗаписьJSON.ЗаписатьИмяСвойства("Descr");
ЗаписьJSON.ЗаписатьЗначение(СокрЛП(Номенклатура.Описание));
ЗаписьJSON.ЗаписатьКонецОбъекта();
ЗаписьJSON.ЗаписатьКонецМассива();
ЗаписьJSON.УстановитьСтроку();
ЗаписьJSON.Закрыть();
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ВремФайл, "UTF-8");
СтрокаJSON = Текст.ПолучитьТекст();
Ответ.УстановитьТелоИзДвоичныхДанных(Новый ДвоичныеДанные(ВремФайл));
Исключение
Ответ.УстановитьТелоИзСтроки(СтрокаJSON);
КонецПопытки;
Иначе
Ответ.УстановитьТелоИзСтроки(СтрокаJSON);
КонецЕсли;
Возврат Ответ;
КонецФункции
Код далеко не идеален и такой же примитивный, как и весь сервис 🙂 1C:Совместимо с таким подходом мы вряд ли получим.
Расписывать выполняемые обработчиками действия особого смысла нет, т.к. по оставленным комментариям в коде логика работы должна быть ясна.
Это и есть вся реализация HTTP-сервиса. Давайте посмотрим какой функционал скрывается на HTML-странице и как он реализован.
DEFAULT.HTML
Главная страница нашего HTTP-сервиса содержим элемент SELECT для выбора товара и поиска по вводу. О том как удалось элемент SELECT сделать редактируемым рассказывать здесь не буду, об этом Вы можете прочитать в статье "Редактируемый SELECT" в соседнем блоге. Здесь же предлагаю рассмотреть выполнение асинхронных вызовов методов HTTP-сервиса со страницы с помощью AJAX. Если Вам интересен полный текст разметки страницы и используемый JavaScript-код, то в верху страницы есть ссылка на файл тестовой конфигурации с описываемым примером.
И так, первый асинхронный вызов обращается к методу "GetProducts" для получения списка товаров по введенной строке запроса. Запрос выполняется каждый раз при изменении текста в поле ввода. С использованием JQuery асинхронный запрос выполняется проще простого:
$.ajax({
type: "POST",
contentType: "application/json;charset=utf-8",
url: "http://{ServerName}/{DatabaseName}/hs/PrimitiveService/products",
data: "{ "query": "" + query + "" }",
dataType: "json",
success: function (queryResult) {
try {
for (var i = 0; i < queryResult.length; i++) {
var item = queryResult[i];
originalSelect.editableSelect('add', function () {
$(this).attr('data-value', item.GUID);
$(this).text(item.Name);
})
}
} catch (e) {
alert('Wow! Error!!!');
}
}
});
В качестве ответа мы ожидаем текст в формате JSON, поэтому параметр "dataType" установлен в "json". В параметре "data" описываем произвольный объект со свойством "query" и текстовым значением, введенным для поиска на странице. В параметре "url" указан адрес метода HTTP-сервиса. Если запрос выполнен успешно, то вызывается событие "success", а в вызываемой функции первым параметром передается объект, полученный от сервера. Далее в функции выполняется обработка полученных данных и заполнение списка выбора.
Второй асинхронный запрос используется при изменении товара. Запрос обращается по адресу метода "GetInfo" и при успешном выполнении помещает полученные значения на страницу. Листинг кода запроса следующий:
$.ajax({
type: "POST",
contentType: "application/json;charset=utf-8",
url: "http://{ServerName}/{DatabaseName}/hs/PrimitiveService/info",
data: "{ "GUID": "" + GUID + "" }",
dataType: "json",
success: function (queryResult) {
try {
var Code = queryResult[0].Code;
var FullName = queryResult[0].FullName;
var Art = queryResult[0].Art;
var Descr = queryResult[0].Descr;
$('#ArtValue').text(Art);
$('#CodeValue').text(Code);
$('#DescrValue').text(Descr);
$('#CodeFullNameValue').text(FullName);
} catch (e) {
// Обработка ошибки
}
}
});
При необходимости Вы можете подробнее изучить тему работы с JavaScript, JQuery и AJAX на сайте metanit.com, рекомендую.
Вместо заключения
Мы реализовали простой HTTP-сервис и продемонстрировали работы с ним сторонними средствами. Всю мощь нового механизма сложно раскрыть в одной статье, но я надеюсь, что благодаря этому материалу у Вас появится интерес для изучения этой темы. Интеграция, интерфейсы и оптимизация — это лишь малая часть тех задач, которые можно решить с их помощью.
Исходный код примера Вы можете найти в репозитории на GitHub.
В качестве более интересного примера, приближенного к реальной разработке, можно рассмотреть создание асинхронных виджетов для 1С:Предприятия с использованием HTTP-сервисов. Но это уже совсем другая история.
Спасибо за внимание и до скорых встреч!
Другие ссылки
Отдельно выделю серию статей от Дмитрия Сидоренко:
Это отличный путь в мир HTTP-сервисов!
Юрий, спасибо.
HTTP Сервисы: Путь к своему сервису. Часть 4 упоминал тоже ;))
Я тебя кстати в статье
Вот эту статью твою приводил в пример
Передача больших пакетов через веб-сервисы
(1) спасибо огромное 🙂
А я про 4 часть забыл совсем. Надо бы ее тоже добавить в список ссылок.
А как у вас выполняется авторизация в 1С?
Да это, фактически, микро Яндекс! 🙂
(3) по-разному.
Можно и обычную BASIC-аутентификации использовать дальше.
(5) А разве при BASIC-аутентификации проблемы с CORS запросами не будут возникать?
Вобщем меня интересует, как решить проблему CORS без использования проксирующего сервера. Вроде как можно настроить apache но у меня не получилось.
Может есть работающая инструкция или может это тема будущей статьи будет?
А зачем генерировать полный URL в запросе?
Относительный путь отлично работает, проверено
От жуквери народ отучать надо, на чистом js примеры давай 🙂
Извините за критику, но после такой публикации хочется просить администрацию инфостарта о модерации кода,
1. Зачем временные файлы?
Так же проще:
2. Получение значений реквизитов через точку по ссылке, мне казалось так уже никто не делает
3. Попытки при любом сомнении в результате?
Уберите все попытки, они у вас или не имеют смысла или можно написать код без использования попытки.
4. С JSON же работать очень просто:
Зачем горы кода. Надо «ЗаписатьНачалоМассива», создайте Новый Массив и запихните туда структуру. Функция ЗаписатьJSON все отлично преобразует сама. Я по умолчанию пропускаю ее через процедуру преобразующую таблицу значений в массив структур. Тогда очень удобно отправлять результат запроса сразу в JSON.
Попытки, временные файлы, множество неявных запросов и вы превратили элементарный молниеносный http-сервис в медленного монстра. Грустно, особенно когда понимаешь, что цель публикации научить чему-то программистов. Еще раз извините, не сдержался.
(9) примитивному сервису — примитивный код 🙂
P.S. сервис на коленке, которому уже почти 5 лет. Думаю даже и обсуждение можно оставить, сейчас бы я все сделал по другому, особенно если для прода 🙂 Собеседование я у Вас не прохожу)))
(8) так и до Asm’а можно спуститься)))
(10) Ну это ж не повод выкладывать устаревшее наколеночное как пример к подражанию. А его так и воспримут, судя по раскрутке статьи. Соглашусь с критикой. Тем более что и статей на эту тему полно, не особо ясно, зачем была ещё одна.
(12) каюсь 🙁
(13)»Обидеть художника может каждый…»! 🙂
Злобный народ пошёл какой-то!
Человек показывает интересное решение, нет,
надо задавить своей учёностью…
Критикуешь, как говорится, предлагай, выкатывайте
свои статьи!
Спасибо большое.
(8) На правах рекламы, в моейпубликации во внешней обработке есть пример асинхронных xhr запросов на чистом js.
(14) уже давно на ИС аналог гита (или сам гит), для открытых разработок, чтобы «злобные комменты» о том что кто-то не умеет кодить решались коммитами, а не двумя разворотами экрана =)
(0) реально ли организовать интернет-продажи через заказы в розничном магазине? то есть в магазине есть продажи в розницу, магазин имеет интернет-сайт, запилили в 1С обмен заказами с сайтом. Уперлись в онлайн-оплату и определение актуальных остатков.
В момент оформления заказа на сайте и онлайн-оплате на кассе может стоять человек с тем же товаром.Поэтому после оплаты заказа на сайте, может выясниться,что остаток в магазине отличается…
Получение остатков на сайте, думаю, делать через http-сервис.
Но организационно вижу пробел.
Есть мнение?
(17)Это кирпич в чей огород, Юрия? 🙂
(19) стоило показать код пятилетней (точно ее помню) давности, теперь все считают что я так и пишу 😀
Пусть он такой тут и останется. Мои ошибки — мое богатство:)
(19)Это идея для Инфостарта. Было приколько иметь на сайте аналог pasteBin (в меньшей степени) или CodePen, что бы можно было размещать не стены кода, но и «играться» с ним. Зачем мне кидаться камнями тут, удобнее это на стаковерфлоу =)
(21)
Играться надо дома! 🙂
А вот иметь на ИС аналог гита для разработчиков 1С на серверах внутри России, это правильная идея,
только ведь у нас, как правило, бесплатного ничего нет…
Неубедил
(0) я лишь хотел учочнить своим предыдущим постом, был ли у вас опыт развертывания http-сервиса для обмена с 1с заказами с сайта или получения остатков товаров для интернет-сайтов?
(24) да, такой опыт есть. Вариант рабочий, но есть различные тех. и организационные ограничения.
Я знаю коробочное решение, которое так и работает 🙂
Автору огромное спасибо!
Побольше бы таких статей )