Каталог решений - Самый примитивный HTTP-сервис в мире

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

Самый примитивный 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-сервисов!

has been added to your cart:
Оформление заказа