Самый примитивный HTTP-сервис в мире





Пошаговый пример создания простейшего HTTP-сервиса, который генерирует HTML-страницу для поиска товара, а также реализует асинхронное получение данных из базы.

О чем это здесь рассказывают

На этот раз мы поговорим о механизме платформы 1С:Предприятие — HTTP-сервисы. Подробнее Вы можете прочитать на официальном сайте или посмотреть примеры на Infostart. Там и вывод графиков, и передача данных, RSS-лента. Кто-то даже реализовал мини-CMS на HTTP-сервисах! =) Мы же рассмотрим создание самого примитивного HTTP-сервиса с минимально полезной функцией.

Механизм HTTP-сервисов открыл довольно обширные возможности по интеграции, расширению функционала, оптимизации существующих приложений и т.д. Чем то это похоже на WebAPI в .NET, но, конечно же, имеет куда больше ограничений и "заточено" под более узкий спектр задач. Список всего того, что можно сделать с помощью HTTP-сервисов настолько большой, что в публикации не хватит места, чтобы сохранить его!

Поэтому в статье мы создадим небольшой сервис, который будет использоваться для вывода простейшей HTML-странички. На ней будут выполняться асинхронные запросы к методам этого сервиса для получения данных. Сразу покажу окончательный результат.

Небольшой, но результат!

Конфигуратор — наше все!

Откроем конфигуратор и добавим новый HTTP-сервис. В нашем случае у сервиса будут три метода:

  1. "MainPage" — метод типа GET, который возвращает HTML-страницу с минимальным внесением изменений в разметку (о об этом чуть позже). Страницу Вы уже видели выше.
  2. "Products" — метод типа POST, который принимает в теле запроса параметр "query" с текстом, по которому будет выполняться поиск товаров в базе по наименованию. В качестве ответа формируется список найденных товаров в формате JSON.
  3. "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-сервисов!

26 Comments

  1. dsdred

    Юрий, спасибо.

    Я тебя кстати в статье HTTP Сервисы: Путь к своему сервису. Часть 4 упоминал тоже ;))

    Вот эту статью твою приводил в пример

    Передача больших пакетов через веб-сервисы

    Reply
  2. YPermitin

    (1) спасибо огромное 🙂

    А я про 4 часть забыл совсем. Надо бы ее тоже добавить в список ссылок.

    Reply
  3. FreeArcher

    А как у вас выполняется авторизация в 1С?

    Reply
  4. 3vs

    Да это, фактически, микро Яндекс! 🙂

    Reply
  5. YPermitin

    (3) по-разному.

    Можно и обычную BASIC-аутентификации использовать дальше.

    Reply
  6. FreeArcher

    (5) А разве при BASIC-аутентификации проблемы с CORS запросами не будут возникать?

    Вобщем меня интересует, как решить проблему CORS без использования проксирующего сервера. Вроде как можно настроить apache но у меня не получилось.

    Может есть работающая инструкция или может это тема будущей статьи будет?

    Reply
  7. Sedaiko

    А зачем генерировать полный URL в запросе?

    url: «http://{ServerName}/{DatabaseName}/hs/PrimitiveService/products»,

    Относительный путь отлично работает, проверено

    Reply
  8. Steelvan

    От жуквери народ отучать надо, на чистом js примеры давай 🙂

    Reply
  9. trntv

    Извините за критику, но после такой публикации хочется просить администрацию инфостарта о модерации кода,

    1. Зачем временные файлы?

    ЗаписьJSON.Закрыть();
    Текст = Новый ТекстовыйДокумент;
    Текст.Прочитать(ВремФайл, «UTF-8»);
    СтрокаJSON = Текст.ПолучитьТекст();

    Так же проще:

    СтрокаJSON = ЗаписьJSON.Закрыть();

    2. Получение значений реквизитов через точку по ссылке, мне казалось так уже никто не делает

    3. Попытки при любом сомнении в результате?

    Попытка
    НоменклатураGUID = Новый УникальныйИдентификатор(ТекстGUID);
    Исключение
    КонецПопытки;

    Уберите все попытки, они у вас или не имеют смысла или можно написать код без использования попытки.

    4. С JSON же работать очень просто:

    ЗаписьJSON= Новый ЗаписьJSON;
    ЗначенияРеквизитовЭлемента = Новый Структура(«Art, FullName, Code, Descr»);
    ЗаполнитьЗначенияСвойств(ЗначенияРеквизитовЭлемента, Выборка);
    ЗаписатьJSON(ЗаписьJSON, ЗначенияРеквизитовЭлемента);
    СтрокаJSON = ЗаписьJSON.Закрыть();

    Зачем горы кода. Надо «ЗаписатьНачалоМассива», создайте Новый Массив и запихните туда структуру. Функция ЗаписатьJSON все отлично преобразует сама. Я по умолчанию пропускаю ее через процедуру преобразующую таблицу значений в массив структур. Тогда очень удобно отправлять результат запроса сразу в JSON.

    Попытки, временные файлы, множество неявных запросов и вы превратили элементарный молниеносный http-сервис в медленного монстра. Грустно, особенно когда понимаешь, что цель публикации научить чему-то программистов. Еще раз извините, не сдержался.

    Reply
  10. YPermitin

    (9) примитивному сервису — примитивный код 🙂

    P.S. сервис на коленке, которому уже почти 5 лет. Думаю даже и обсуждение можно оставить, сейчас бы я все сделал по другому, особенно если для прода 🙂 Собеседование я у Вас не прохожу)))

    Reply
  11. YPermitin

    (8) так и до Asm’а можно спуститься)))

    Reply
  12. Yashazz

    (10) Ну это ж не повод выкладывать устаревшее наколеночное как пример к подражанию. А его так и воспримут, судя по раскрутке статьи. Соглашусь с критикой. Тем более что и статей на эту тему полно, не особо ясно, зачем была ещё одна.

    Reply
  13. YPermitin

    (12) каюсь 🙁

    Reply
  14. 3vs

    (13)»Обидеть художника может каждый…»! 🙂

    Злобный народ пошёл какой-то!

    Человек показывает интересное решение, нет,

    надо задавить своей учёностью…

    Критикуешь, как говорится, предлагай, выкатывайте

    свои статьи!

    Reply
  15. FirePyres

    Спасибо большое.

    Reply
  16. logos

    (8) На правах рекламы, в моей публикации во внешней обработке есть пример асинхронных xhr запросов на чистом js.

    Reply
  17. AMS_Guskov_VL

    (14) уже давно на ИС аналог гита (или сам гит), для открытых разработок, чтобы «злобные комменты» о том что кто-то не умеет кодить решались коммитами, а не двумя разворотами экрана =)

    Reply
  18. Rustig

    (0) реально ли организовать интернет-продажи через заказы в розничном магазине? то есть в магазине есть продажи в розницу, магазин имеет интернет-сайт, запилили в 1С обмен заказами с сайтом. Уперлись в онлайн-оплату и определение актуальных остатков.

    В момент оформления заказа на сайте и онлайн-оплате на кассе может стоять человек с тем же товаром.Поэтому после оплаты заказа на сайте, может выясниться,что остаток в магазине отличается…

    Получение остатков на сайте, думаю, делать через http-сервис.

    Но организационно вижу пробел.

    Есть мнение?

    Reply
  19. 3vs

    (17)Это кирпич в чей огород, Юрия? 🙂

    Reply
  20. YPermitin

    (19) стоило показать код пятилетней (точно ее помню) давности, теперь все считают что я так и пишу 😀

    Пусть он такой тут и останется. Мои ошибки — мое богатство:)

    Reply
  21. AMS_Guskov_VL

    (19)Это идея для Инфостарта. Было приколько иметь на сайте аналог pasteBin (в меньшей степени) или CodePen, что бы можно было размещать не стены кода, но и «играться» с ним. Зачем мне кидаться камнями тут, удобнее это на стаковерфлоу =)

    Reply
  22. 3vs

    (21)

    но и «играться» с ним.

    Играться надо дома! 🙂

    А вот иметь на ИС аналог гита для разработчиков 1С на серверах внутри России, это правильная идея,

    только ведь у нас, как правило, бесплатного ничего нет…

    Reply
  23. DrZombi

    Неубедил

    Reply
  24. Rustig

    (0) я лишь хотел учочнить своим предыдущим постом, был ли у вас опыт развертывания http-сервиса для обмена с 1с заказами с сайта или получения остатков товаров для интернет-сайтов?

    Reply
  25. YPermitin

    (24) да, такой опыт есть. Вариант рабочий, но есть различные тех. и организационные ограничения.

    Я знаю коробочное решение, которое так и работает 🙂

    Reply
  26. adhocprog

    Автору огромное спасибо!

    Побольше бы таких статей )

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *