Часть 1. Создание HTTP-сервиса
Часть 2. Тестирование и отладка HTTP-сервиса с помощью SoapUI
Разработаем HTTP-сервис, который по уникальному идентификатору номенклатуры будет возвращать остаток этой номенклатуры на складе. HTTP-сервис должен отвечать следующим требованиям:
- Формат запроса и ответа - JSON;
- Запрос принимается методом POST;
- В заголовках запроса должна присутствовать информация о клиенте (клиент - другая информационная система);
- Сервис должен быть версионируемым (что бы клиенты не страдали от изменений в сервисе, а по возможности переходили на следующую версию).
Первым делом, создаем HTTP-сервис HTTPСервисОстатки, в корневом URL указываем значение balance.
Рисунок 1. Основные свойства http-сервиса |
В шаблонах URL добавляем шаблон /nomenclature/{version} под именем Номенклатура. В шаблоне имеется параметр version, который заключен в фигурные скобки, он нам нужен, что бы выполнить требование №4 (см. рисунок 2).
Рисунок 2. Шаблон URL |
Для шаблона добавляем HTTP-метод POST и создаем обработчик для этого метода, это требование №2 (см. рисунок 3).
Рисунок 3. HTTP-метод POST |
В разрабатываемый HTTP-сервис закладываем две версии - первая будет доступна по url: /nomenclature/v1 и вторая по url: /nomenclature/v2. Первая версия будет работать только с одним уникальным идентификатором номенклатуры, а вторая версия с массивом уникальных идентификаторов. Структура JSON-строки запроса и ответа для каждой версии приведены в таблице ниже.
/nomenclature/v1 | /nomenclature/v2 | |
---|---|---|
Запрос |
{ "Период": "", "UUID": "" } |
{ "Период": "", "UUID": [] } |
Ответ |
{ "Версия": 0, "НаДату": "", "Остаток": 0 } |
{ "Версия": 0, "НаДату": "", "Остатки": [ { "UUID": "", "Остаток": 0 } ], "Резерв": null } |
Приступим к программной реализации обработчика HTTP-метода POST.
Если клиент обращается к неизвестной версии HTTP-сервиса, то необходимо об этом как-то ему сообщить, да что бы было понятно в чем дело, поэтому для выполнения требования №4, не достаточно ввести параметр version в шаблон URL, его значение нужно дополнительно анализировать в обработчике HTTP-метода. В случае неизвестного значения возвращаем в ответ ошибку 404 с описанием причины, что такой URL серверу неизвестен или версия сервиса неизвестна.
// Определение запрашиваемой версии сервиса.
// Если версия не известна, то отклоняем запрос с описанием причины.
ДопустимыеВерсииСервиса = "v1,v2";
ВерсияСервиса = Запрос.ПараметрыURL["version"];
Если СтрРазделить(ДопустимыеВерсииСервиса, ",").Найти(ВерсияСервиса) = Неопределено Тогда
Ответ = Новый HTTPСервисОтвет(404);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Версия не существует'"));
Возврат Ответ;
КонецЕсли;
Что бы выполнить требование №1, проверяем в заголовках HTTP-запроса наличие заголовка Content-Type, его значение должно равняться application/json. Это стандартный заголовок и один из самых известных, используется для определения типа передаваемых данных. Если данный заголовок будет отсутствовать, то по умолчанию считаем что пришли данные типа текст в формате JSON, иначе анализируем значение заголовка и принимаем решение, если это не текстовые данные в формате JSON, выкидываем в ответ ошибку с кодом 415.
// Чтение значения заголовка Content-Type.
// Если значение заголовка не соответствует допустимому,
// то отклоняем запрос с описанием причины.
ТипДопустимогоКонтента = "application/json";
ContentType = Запрос.Заголовки.Получить("Content-Type");
Если ContentType <> Неопределено Тогда
Если СтрНайти(ContentType, ТипДопустимогоКонтента) = 0 Тогда
Ответ = Новый HTTPСервисОтвет(415);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Ожидаемый формат - JSON'"));
Возврат Ответ;
КонецЕсли;
КонецЕсли;
Что бы выполнить требование №3, проверяем в заголовках HTTP-запроса наличие заголовка Client-Name-1C, это собственный заголовок, мы его придумали сами и используем для своих целей, допустим, фиксируем в журнале регистрации значение заголовка. Наличие данного заголовка обязательно, если он отсутствует, то выкидываем в ответ ошибку с кодом 406.
// Чтение обязательного заголовка Client-Name-1C.
ClientName1C = Запрос.Заголовки.Получить("Client-Name-1C");
Если ClientName1C = Неопределено Тогда
Ответ = Новый HTTPСервисОтвет(406);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Отсутствует обязательный заголовок'"));
Возврат Ответ;
Иначе
// Запись в журнал регистрации о клиенте.
// ...
КонецЕсли;
Теперь самое основное, реализуем логику работы HTTP-сервиса. Получаем тело HTTP-запроса как строку, в нашем случает это JSON-строка (см. требование №1). Что бы было удобно работать с данными, которые содержит в себе JSON-строка, читаем JSON-строку в коллекцию значений. Спасибо разработчикам платформы 1С за процедуры и функции по работе с JSON, которые появились в версии 8.3.
// Читаем тело запроса как JSON-строку.
СтроковоеТело = Запрос.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтроковоеТело);
// Когда задана функция восстановления, то третий параметр игнорируется, хотя в СП написано наоборот.
ДанныеЗапроса = ПрочитатьJSON(ЧтениеJSON, , , , "ФункцияВосстановленияЗначений", РаботаJSON);
ЧтениеJSON.Закрыть();
Для каждой версии HTTP-сервиса (см. требование №4) пишем код в соответствии с логикой работы каждой версии. В первой версии работаем с одним уникальным идентификатором, во второй с массивом. Данные для ответа формируем в виде коллекции значений.
ДанныеОтвета = Новый Структура;
Если ВерсияСервиса = "v1" Тогда
// Обрабатываем запрос.
Период = ДанныеЗапроса.Период;
Номенклатура = Справочники.Номенклатура.ПолучитьСсылку(ДанныеЗапроса.UUID);
// Выполняем расчет остатков.
// ...
// Формируем ответ.
ДанныеОтвета.Вставить("Версия", 1.04);
ДанныеОтвета.Вставить("НаДату", Период);
ДанныеОтвета.Вставить("Остаток", 10);
ИначеЕсли ВерсияСервиса = "v2" Тогда
// Обрабатываем запрос.
Период = ДанныеЗапроса.Период;
СписокНоменклатур = Новый Массив;
Для Каждого ТекЭлемент Из ДанныеЗапроса.UUID Цикл
СписокНоменклатур.Добавить(Справочники.Номенклатура.ПолучитьСсылку(ТекЭлемент));
КонецЦикла;
// Выполняем расчет остатков.
// ...
ОстаткиПоНоменклатурам = Новый Массив;
Для Каждого ТекЭлемент Из СписокНоменклатур Цикл
Остаток = Новый Структура("UUID,Остаток", ТекЭлемент.УникальныйИдентификатор(), 10);
ОстаткиПоНоменклатурам.Добавить(Остаток);
КонецЦикла;
// Формируем ответ.
ДанныеОтвета.Вставить("Версия", 2.15);
ДанныеОтвета.Вставить("НаДату", Период);
ДанныеОтвета.Вставить("Остатки", ОстаткиПоНоменклатурам);
КонецЕсли;
Сформированные данные для ответа записываем в JSON-строку, на основании которой создаем HTTP-ответ с кодом 200. В заголовках HTTP-ответа обязательно указываем заголовок Content-Type, что бы клиент точно знал тип данных и их формат в ответе.
// Сохраняем ответ в формате JSON-строки.
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеОтвета, , "ФункцияПреобразованияЗначений", РаботаJSON);
СтроковоеТело = ЗаписьJSON.Закрыть();
// Отвечаем.
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", ТипДопустимогоКонтента + "; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(СтроковоеТело);
В запросе и ответе участвуют уникальные идентификаторы. Проблема в том, что 1Сные процедуры и функции по работе с JSON не умеют преобразовывать уникальный идентификатор из строки в тип УникальныйИдентификатор и обратно, но их этому можно научить. Делается это с помощью параметра ИмяФункцииПреобразования для процедуры ЗаписатьJSON и с помощью параметра ИмяФункцииВосстановления для процедуры ПрочитатьJSON. Реализацию функций для перечисленных параметров рассматривать не будем, так как это выходит за рамки создания HTTP-сервиса.
Полный код обработчика для метода POST:
Функция ШаблонURLМетодPOST(Запрос)
// Определение запрашиваемой версии сервиса.
// Если версия неизвестна, то отклоняем запрос с описанием причины.
ДопустимыеВерсииСервиса = "v1,v2";
ВерсияСервиса = Запрос.ПараметрыURL["version"];
Если СтрРазделить(ДопустимыеВерсииСервиса, ",").Найти(ВерсияСервиса) = Неопределено Тогда
Ответ = Новый HTTPСервисОтвет(404);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Версия не существует'"));
Возврат Ответ;
КонецЕсли;
// Чтение значения заголовка Content-Type.
// Если значение заголовка не соответствует допустимому,
// то отклоняем запрос с описанием причины.
ТипДопустимогоКонтента = "application/json";
ContentType = Запрос.Заголовки.Получить("Content-Type");
Если ContentType <> Неопределено Тогда
Если СтрНайти(ContentType, ТипДопустимогоКонтента) = 0 Тогда
Ответ = Новый HTTPСервисОтвет(415);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Ожидаемый формат - JSON'"));
Возврат Ответ;
КонецЕсли;
КонецЕсли;
// Чтение обязательного заголовка Client-Name-1C.
ClientName1C = Запрос.Заголовки.Получить("Client-Name-1C");
Если ClientName1C = Неопределено Тогда
Ответ = Новый HTTPСервисОтвет(406);
Ответ.УстановитьТелоИзСтроки(НСтр("ru = 'Отсутствует обязательный заголовок'"));
Возврат Ответ;
Иначе
// Запись в журнал регистрации о клиенте.
// ...
КонецЕсли;
// Читаем тело запроса как JSON-строку.
СтроковоеТело = Запрос.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтроковоеТело);
// Когда задана функция восстановления, то третий параметр игнорируется, хотя в СП написано наоборот.
ДанныеЗапроса = ПрочитатьJSON(ЧтениеJSON, , , , "ФункцияВосстановленияЗначений", РаботаJSON);
ЧтениеJSON.Закрыть();
ДанныеОтвета = Новый Структура;
Если ВерсияСервиса = "v1" Тогда
// Обрабатываем запрос.
Период = ДанныеЗапроса.Период;
Номенклатура = Справочники.Номенклатура.ПолучитьСсылку(ДанныеЗапроса.UUID);
// Выполняем расчет остатков.
// ...
// Формируем ответ.
ДанныеОтвета.Вставить("Версия", 1.04);
ДанныеОтвета.Вставить("НаДату", Период);
ДанныеОтвета.Вставить("Остаток", 10);
ИначеЕсли ВерсияСервиса = "v2" Тогда
// Обрабатываем запрос.
Период = ДанныеЗапроса.Период;
СписокНоменклатур = Новый Массив;
Для Каждого ТекЭлемент Из ДанныеЗапроса.UUID Цикл
СписокНоменклатур.Добавить(Справочники.Номенклатура.ПолучитьСсылку(ТекЭлемент));
КонецЦикла;
// Выполняем расчет остатков.
// ...
ОстаткиПоНоменклатурам = Новый Массив;
Для Каждого ТекЭлемент Из СписокНоменклатур Цикл
Остаток = Новый Структура("UUID,Остаток", ТекЭлемент.УникальныйИдентификатор(), 10);
ОстаткиПоНоменклатурам.Добавить(Остаток);
КонецЦикла;
// Формируем ответ.
ДанныеОтвета.Вставить("Версия", 2.15);
ДанныеОтвета.Вставить("НаДату", Период);
ДанныеОтвета.Вставить("Остатки", ОстаткиПоНоменклатурам);
КонецЕсли;
// Задаем настройки для записи коллекции значений в JSON-строку.
НастройкиСериализацииJSON = Новый НастройкиСериализацииJSON;
НастройкиСериализацииJSON.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.ЛокальнаяДатаСоСмещением;
НастройкиСериализацииJSON.СериализовыватьМассивыКакОбъекты = Ложь;
НастройкиСериализацииJSON.ФорматСериализацииДаты = ФорматДатыJSON.ISO;
// Сохраняем ответ в формате JSON-строки.
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеОтвета, НастройкиСериализацииJSON, "ФункцияПреобразованияЗначений", РаботаJSON);
СтроковоеТело = ЗаписьJSON.Закрыть();
// Отвечаем.
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", ТипДопустимогоКонтента + "; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(СтроковоеТело);
Возврат Ответ;
КонецФункции
Комментарии
Отправить комментарий