О чем это здесь рассказывают
На этот раз мы поговорим о механизме платформы 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-сервисов!