Предыстория:
Не так давно мне понадобилось буквально по кусочкам собирать информацию в совершенно различных источниках о том, как же все-таки реализовать сервис управления списками баз для лаунчера 1сestart через JSON. Некоторые нюансы выявлял самостоятельно. В процессе исследований обнаружил, что часть пользователей вообще не подозревает о таком варианте. Поэтому решил собрать и изложить свой опыт тут.
4 запроса:
Лаунчер 1cestart для варианта JSON посылает 4 запроса:
1- HEAD запрос на эндпоинт "WebCommonInfoBases". Название зашито в лаунчере, он добавляет его сам к указанному в настройках URl. На этот запрос необходимо отправить ответ с кодом 200, только в таком случае мы получим ожидаемый запрос на проверку обновления списка в нужном формате. В противном случае мы получим SOAP запрос.
2- GET запрос на эндпоинт "WebCommonInfoBases". Тут мы должны указать необходимость обновления списка баз и адрес сервиса, возвращающего этот список в формате v8i:
Пример тела ответа:
{
"root":
{
"InfoBasesChanged": true,
"URL": "http:\\СервисПолученияСписка_v8i"
}
}
3- HEAD запрос на указанный ранее сервис. Принцип все тот же, отвечаем с кодом 200
4- GET запрос на указанный ранее сервис. Возвращаем тело в формате JSON
Пример тела ответа:
{
"root":
{
"ClientID": "none",
"InfoBasesCheckCode": "none",
"InfoBases":"Описание в формате v8i"
}
}
Настройка лаунчера
В настройках диалога запуска необходимо прописать путь к публикации первого эндпоинта по которому будет происходить проверка необходимости обновления и получение URL второго эндпоинта
Реализация
Создаем 2 эндпоинта:
Корневой URL у первого эндпоинта должен быть именно таким: "WebCommonInfoBases"
Пример тела модуля первого эндпоинта:
#Область ПрограммныйИнтерфейс
Функция CheckInfoBases_GET(Запрос)
Попытка
ClientID = Строка(Запрос.ПараметрыЗапроса["ClientID"]);
// Может быть пустым
InfoBasesCheckCode = Строка(Запрос.ПараметрыЗапроса["InfoBasesCheckCode"]);
// Может передать веб-сервер
ЛогинПользователя = Запрос.ПараметрыЗапроса.Получить("UserID");
ИдентификаторПользователя = Запрос.ПараметрыURL.Получить("user_guid");
Если ПустаяСтрока(ClientID) Тогда
Ответ = Новый HTTPСервисОтвет(404);
Возврат Ответ;
КонецЕсли;
Если Не ЗначениеЗаполнено(ИдентификаторПользователя)
И Не ЗначениеЗаполнено(ЛогинПользователя) Тогда
Ответ = Новый HTTPСервисОтвет(404);
Возврат Ответ;
КонецЕсли;
InfoBasesChanged = Ложь;
URL = "";
СтруктураОтвета = CheckInfoBases(ClientID, InfoBasesCheckCode, InfoBasesChanged, URL
, Запрос.БазовыйURL, ЛогинПользователя, ИдентификаторПользователя);
Тело = СтроковыеФункцииКлиентСервер.КонвертироватьВJson(СтруктураОтвета);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(Тело);
Ответ.Заголовки["Content-Type"] = "application/json;charset=utf-8";
Ответ.Заголовки["Cache-Control"] = "no-cache";
Возврат Ответ;
Исключение
ЗаписьЖурналаРегистрации("CheckInfoBases.JSON", УровеньЖурналаРегистрации.Ошибка,
,,СлужебныйКлиентСервер.ПодробныйТекстОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедуры
Функция ПустойИдентификатор()
Возврат "00000000-0000-0000-0000-000000000000";
КонецФункции
Функция CheckInfoBases(ClientID, InfoBaseCheckCode, InfoBasesChanged, URL, БазовыйURL, ЛогинПользователя, ИдентификаторПользователя)
InfoBasesChanged = Ложь;
// TODO: Тип доступа опряделяет список для пользователя (для типа web и rdp разные списки)
// Должен определяться по эндпоинту. Нужны разные эндпоинты для разных типов доступа
ТипДоступа = "rdp";
УстановитьПривилегированныйРежим(Истина);
АдресЗащищенногоВС = Константы.URLПолученияСпискаБаз.Получить();
Если ЗначениеЗаполнено(ИдентификаторПользователя) Тогда
АдресЗащищенногоВС = АдресЗащищенногоВС + ИдентификаторПользователя+"/";
КонецЕсли;
Если ClientID = ПустойИдентификатор()
И InfoBaseCheckCode = ПустойИдентификатор() Тогда
// это первое обращение клиента
InfoBasesChanged = Истина;
URL = АдресЗащищенногоВС;
СтруктураОтвета = СтруктурыДанныхСервер.СтруктураОтветаПроверкаОбновления(InfoBasesChanged, URL);
КонецЕсли;
СтруктураОтвета = ФункцииУправленияСервер.ПроверитьПотребностьОбновления(ЛогинПользователя, ИдентификаторПользователя
, InfoBaseCheckCode, ТипДоступа);
Возврат СтруктураОтвета;
КонецФункции
Функция RootHEAD(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
//Ответ.Заголовки["Content-Type"] = "application/json;charset=utf-8";
//Ответ.Заголовки["Cache-Control"] = "no-cache";
Возврат Ответ;
КонецФункции
#КонецОбласти
Пример тела модуля второго эндпоинта:
Функция GetInfoBasesGetInfoBases_GET(Запрос)
// Пропускаем запросы HEAD
// Заглушка, необходимая 1С стартеру для отправки JSON
Если Запрос.HTTPМетод = "" Тогда
Ответ = Новый HTTPСервисОтвет(200);
// Работает даже без заголовков
//Ответ.Заголовки["Content-Type"] = "application/json;charset=utf-8";
//Ответ.Заголовки["Cache-Control"] = "no-cache";
Возврат Ответ;
ИначеЕсли Запрос.HTTPМетод = "GET" Тогда
Попытка
ClientID = Запрос.ПараметрыЗапроса["ClientID"];
InfoBasesCheckCode = Запрос.ПараметрыЗапроса["InfoBasesCheckCode"];
// Может передать веб-сервер
ЛогинПользователя = Запрос.ПараметрыЗапроса.Получить("UserID");
ИдентификаторПользователя = Запрос.ПараметрыURL.Получить("user_guid");
InfoBases = "";
GetInfoBases(ClientID, InfoBasesCheckCode, InfoBases, Запрос.БазовыйURL, ЛогинПользователя, ИдентификаторПользователя);
ДанныеОтвета = Новый Структура("root", Новый Структура("ClientID,InfoBasesCheckCode,InfoBases"
, ClientID, InfoBasesCheckCode, InfoBases));
Тело = СтроковыеФункцииКлиентСервер.КонвертироватьВJson(ДанныеОтвета);
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(Тело);
Ответ.Заголовки["Content-Type"] = "application/json;charset=utf-8";
Ответ.Заголовки["Cache-Control"] = "no-cache";
Возврат Ответ;
Исключение
ЗаписьЖурналаРегистрации("GetInfoBases.JSON", УровеньЖурналаРегистрации.Ошибка,,
,СлужебныйКлиентСервер.ПодробныйТекстОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки
Иначе
Ответ = Новый HTTPСервисОтвет(405);
Возврат Ответ;
КонецЕсли;
КонецФункции
#Область СлужебныеПроцедуры
Процедура GetInfoBases(ClientID, InfoBaseCheckCode, InfoBases, БазовыйURL, ЛогинПользователя, ИдентификаторПользователя)
УстановитьПривилегированныйРежим(ИСТИНА);
ТипДоступа = "rdp";
ВспомогательныеФункцииСервер.ИнициализироватьВнешнийИсточник(Константы.АдресСервера.Получить());
Если ЛогинПользователя <> Неопределено Тогда
РезультатJSON = ВнешниеИсточникиДанных.wcib_db.public_get_v8i_list_uid(ЛогинПользователя, ТипДоступа);
Иначе
РезультатJSON = ВнешниеИсточникиДанных.wcib_db.public_get_v8i_list_guid(ИдентификаторПользователя, ТипДоступа);
КонецЕсли;
СтруктураОтвета = СтроковыеФункцииКлиентСервер.КонвертироватьJsonВСтруктуру(РезультатJSON);
InfoBaseCheckCode = СтруктураОтвета.version;
Если ClientID = ПустойИдентификатор() Тогда
ClientID = Строка(Новый УникальныйИдентификатор());
КонецЕсли;
InfoBases = ПолучитьСписокИБ(СтруктураОтвета.infobases);
КонецПроцедуры
Функция ПолучитьСписокИБ(ДанныеСписка) Экспорт
Если ДанныеСписка = Неопределено Тогда
ДанныеСписка = Новый Массив;
КонецЕсли;
ЧислоИБ = ДанныеСписка.Количество();
УстановитьПривилегированныйРежим(Истина);
//собираем в дерево
//соответствие(Группа): массив(Список ИБ): структура(Элемент ИБ)
Результат = "";
ИспользоватьФайл = ЧислоИБ > 1000;
Если ИспользоватьФайл Тогда
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("v8i");
ЗТ = Новый ЗаписьТекста(ИмяВременногоФайла, КодировкаТекста.UTF8);
КонецЕсли;
Для Каждого JSONСтруктураБазы Из ДанныеСписка Цикл
JSONСтруктураБазы = СтрЗаменить(JSONСтруктураБазы, "\", "\\");
СтруктураБазы = СтроковыеФункцииКлиентСервер.КонвертироватьJsonВСтруктуру(JSONСтруктураБазы);
Группа = Новый Массив();
ОписаниеИБ = Новый Структура("Наименование,Строки", СтруктураБазы.name, Новый Массив());
СтрокиИБ = ОписаниеИБ.Строки;
ConnectString = ВспомогательныеФункцииСервер.СформироватьСтрокуПодключения(СтруктураБазы);
СтрокиИБ.Добавить(ConnectString);
СтрокиИБ.Добавить("ID=" + СтруктураБазы.base_id);
СтрокиИБ.Добавить("OrderInList=" + СтруктураБазы.order_in_list);
Если СтруктураБазы.app_arch <> Неопределено Тогда
СтрокиИБ.Добавить("AppArch=" + СтруктураБазы.app_arch);
КонецЕсли;
СтрокаДополнительныхПараметров = "";
Если СтруктураБазы.use_ib_autorization = Истина Тогда
ИмяПользователя = СтруктураБазы.iblogin;
Пароль = ?(ЗначениеЗаполнено(СтруктураБазы.ibpwd), СтруктураБазы.ibpwd, "");
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/N " + ИмяПользователя + " ";
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/P " + Пароль + " ";
КонецЕсли;
// Авторизация на веб-сервере (для получения списка баз)
Если СтруктураБазы.wslogin <> Неопределено Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/WSN " + СтруктураБазы.wslogin + " ";
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/WSP " + СтруктураБазы.wspwd + " ";
КонецЕсли;
// Если используется прокси для WS
Если СтруктураБазы.use_proxy = 2 Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/Proxy ";
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "-PSrv " + СтруктураБазы.psrv + " ";
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "-PPort " + Формат(СтруктураБазы.pport, "ЧГ=") + " ";
Если СтруктураБазы.puser <> Неопределено Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "-PUser " + СтруктураБазы.puser + " ";
КонецЕсли;
Если СтруктураБазы.ppwd <> Неопределено Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "-PPwd " + СтруктураБазы.ppwd + " ";
КонецЕсли;
КонецЕсли;
Если СтруктураБазы.technical_specialist_mode = Истина Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/TechnicalSpecialistMode ";
КонецЕсли;
Если СтруктураБазы.main_window_mode <> Неопределено Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/MainWindowMode " + СтруктураБазы.main_window_mode + " ";
КонецЕсли;
Если СтруктураБазы.clear_cache = Истина Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/ClearCache ";
КонецЕсли;
Если СтруктураБазы.update_ib = Истина Тогда
СтрокаДополнительныхПараметров = СтрокаДополнительныхПараметров + "/C ЗапуститьОбновлениеИнформационнойБазы";
КонецЕсли;
Если Не ПустаяСтрока(СтрокаДополнительныхПараметров) Тогда
СтрокиИБ.Добавить("AdditionalParameters=" + СтрокаДополнительныхПараметров);
КонецЕсли;
Группа.Добавить(ОписаниеИБ);
//рендерим дерево в v8i
//если дерево больше 1000, используем файл как буфер, потому что конкатенация длинных строк в платформе работает медленно
Для Каждого ОписаниеИБ из Группа Цикл
Если ИспользоватьФайл Тогда
ЗТ.ЗаписатьСтроку("[" + ОписаниеИБ.Наименование + "]");
Иначе
Результат = Результат + "[" + ОписаниеИБ.Наименование + "]" + Символы.ПС;
КонецЕсли;
Для Каждого СтрокаИБ из ОписаниеИБ.Строки Цикл
Если ИспользоватьФайл Тогда
ЗТ.ЗаписатьСтроку(СтрокаИБ);
Иначе
Результат = Результат + СтрокаИБ + Символы.ПС;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Если ИспользоватьФайл Тогда
ЗТ.Закрыть();
ЧТ = новый ЧтениеТекста(ИмяВременногоФайла, КодировкаТекста.UTF8);
Результат = ЧТ.Прочитать();
ЧТ.Закрыть();
УдалитьФайлы(ИмяВременногоФайла);
КонецЕсли;
Возврат Результат;
КонецФункции
Функция ПустойИдентификатор()
Возврат "00000000-0000-0000-0000-000000000000";
КонецФункции
#КонецОбласти