Прокси хранилища 1С (IIS, OneScript)

В этом посте мы поговорим о том, как избавиться от версионной зависимости, проверять комментарии, вызывать веб-хуки и делать красивые пути. И все это на привычном IIS и понятном OneScript.

Для чего, зачем и почему

Во всех командах разработки рано или поздно возникает потребность привязки изменений кода к номерам задач. В итоге команды приходят к решению в виде комментариев в хранилище 1С. Но часто ли разработчики соблюдают это правило? Также может срабатывать человеческий фактор: можно ошибиться буквой, цифрой, задачей, ее статусом, исполнителем, системой, местом работы. Приходится перевыкладывать хранилище через gitsync, переделывать связи, тратить лишнее время и силы.

А ещё хочется в работу добавить эстетики. Например, при подключении к хранилищу через HTTP — почему бы не убрать уродливые *.1ccr и не сделать красивый адрес хранилища?

При подключении через tcp на разных версиях 1С надоело угадывать, на каком порту какая версия находится.

Плюс есть, например, у меня потребность начать синхронизацию с git-репозиторием по факту помещения очередной версии в хранилище. Прилепим вызов синхронизации? Запросто!

Появилась еще одна возможность встроиться в механизм транспорта между конфигуратором и хранилищем 1С, а по пути проверять комментарии, вызывать сторонние сервисы и делать прочие полезности. И все это на знакомом нам, одинэсникам, языке программирования и веб-сервере.

Если совсем коротко

На Windows-сервере располагаются файловые хранилища конфигураций 1С. Для доступа к ним на том же сервере настроен tcp-сервер хранилища 1С (crserver.exe). Также настроен доступ к хранилищу по HTTP на IIS. Предполагаем, что после внедрения доступ к хранилищам остается только по HTTP. Хранилища более не будут доступны по tcp или файловым шарам.

В IIS добавляется расширение от Microsoft «URL Rewrite» для запрета доступа к оригинальной публикации хранилища (чтобы нельзя было действовать в обход «прокси»), а также для красивых адресов хранилищ.

На сервер устанавливается OneScript. Будем использовать его механизм HTTP-сервисов. В IIS добавляется публикация на OneScript. Внутри публикации создается файл .os, в нем на OneScript происходит обработка запросов конфигуратора. Запросы можно парсить, проверять комментарии, возвращать нативную для конфигуратора ошибку, во время запроса вызывать сторонние сервисы (например, gitsync), осуществлять маршрутизацию между версиями 1С и прочее.

fc0b401567e572aeb1531c7f5c133d4d.png

Не все так страшно, как кажется!

А теперь подробности

Настройка доступа tcp

Справка на ИТС тут, статья от WiseAdvice здесь.

Допустим, все наши хранилища лежат на сервере в D:\Confstores

1С рекомендует настраивать следующим образом:

crserver.exe -instsrvc | -rmsrvc -usr <пользователь> -pwd <пароль>
                    -start | -stop
                    -port <порт> -d <каталог>

Этот метод нам не подходит.

При вызове с ключом -initsrvc старая служба заменяется новой. Нам же нужно запустить сразу несколько версий 1С. Поэтому лучше воспользоваться sc.exe, где в параметрах указывать нужные версии. Каждую версию вешаем на свободный tcp-порт:

sc.exe create <имя сервиса, например "1c_storage_8_3_21_1624"> binPath="C:\Program Files\1cv8\8.3.21.1624\bin\crserver.exe -srvc -port 1542 -d D:\ConfStores"

Например, так:

7dec5fc5b182648b3c391065b6f6e257.png

Отлично! Теперь мы можем подключаться из любой используемой версии 1С по соответствующему порту:

tcp://<имя_сервера>:<порт>/<имя_хранилища>

Например tcp://server:1542/erp

Доступ по HTTP

Справка ИТС.

Создадим несколько публикаций на IIS, каждая их которых смотрит на свои tcp-порты в соответствии с версиями.

Для каждой версии должно быть по 2 файла:

b19145eedc98613dffaa972a55d77b43.png

Содержание web.config:



	
	  
   
    
        
            
        
        
            
                
            
        
    

maxAllowedContentLength=»4294967295» — запросы конфигуратора и ответы ему могут быть довольно большими, поэтому увеличиваем допустимый размер передаваемых данных до максимального.

Каждый файл должен быть под «свою» версию. Внутри 2 места для исправления: "add name="8_3_21_1484"" и путь к wsisapi.dll

Содержание storage.1ccr:


Ссылается на текущий сервер, на порт 2543. Не забудьте поменять порт на тот, который вы указали для текущей версии при настройке доступа по tcp.

Добавим под каждую версию свой пул приложения (Application pool) в IIS:

db861ea3d0eb48f22905a90f03b73511.png

Далее в Sites —> Default Web Site надо найти папки, которые мы создавали ранее, через контекстное меню нажать «Convert to application». Не забудьте выбрать нужный Application pool, иначе по умолчанию установится DefaultAppPool:  

2b89a76a1737bed75d495be38c922812.png

Теперь можно пробовать подключаться к хранилищу по http:

http://<имя_сервера>/<имя_публикации>/storage.1ccr/<имя_хранилища>

Например http://server/pool_8_3_21_1484/storage.1ccr/erp

Учим IIS понимать OneScript

Для начала его нужно установить. Проверка: в cmd набрать слово oscript:  

d0d52faa2767fcb949873b9b734f951d.png

Для управления обменом с хранилищем будем использовать механизм http-сервисов OneScript.

Как это работает в двух словах: при обращении по адресу http://<имя_сервера>/<имя_публикации>/<имя_файла>.os после всех настроек будет вызываться указанный нами далее файл *.os, в котором обязательно должна быть экспортная функция ОбработкаВызоваHTTPСервиса(Запрос).

Эта функция должна возвращать ответ типа HTTPСервисОтвет (например, Новый HTTPСервисОтвет(200), где 200 — код состояния HTTP).

Между запросом и ответом и будет выстроена вся наша деятельность: анализ тела запроса, вызов сервисов, подмена ответа либо отправка оригинального ответа конфигуратора.

Чтобы сделать публикацию HTTP-сервиса OneScript, нам понадобится сделать:

1. Новую папку в C:\inetpub\wwwroot. В нашем примере предлагаю называть ее storage. Внутри структура будет следующая:

d79d10da5433e92bda5f014887bb36e7.png

2. В папку bin нужно поместить файлы, которые можно взять из bin установленного OneScript:

ASPNETHandler.dll            
DotNetZip.dll                
ICSharpCode.AvalonEdit.dll   
Newtonsoft.Json.dll          
OneScript.DebugProtocol.dll  
OneScript.DebugServices.dll  
OneScript.Language.dll       
ScriptEngine.dll             
ScriptEngine.HostedScript.dll
ScriptEngine.NativeApi.dll   
ScriptEngine.NativeApi32.so  
ScriptEngine.NativeApi64.dll
ScriptEngine.NativeApi64.so

3. Файл web.config:



	
      
         
      
	  
	  
   
    
        
			
			
        
        
            
                
            
        
    
	
		
		
	

Здесь, грубо говоря, описано, какими dll обрабатывать файлы с расширением .os, допустымые размеры данных, а также пара настроек для движка OneScript в конце.

4. Под описание storage.os выделяю отдельный следующий раздел.

5. В IIS конвертировать публикацию storage в приложение, определить для него отдельный Application pool с pipeline mode — classic и .NET CLR Version v4.0.

Создание скрипта .os

В общем случае скрипт выглядит так:

Функция ОбработкаВызоваHTTPСервиса(Запрос) Экспорт
    Возврат Новый HTTPСервисОтвет(200);
КонецФункции

При вызове http-сервиса, например, через браузер, IIS будет искать, какие dll запускать для файла с расширением .os, dll будут искать функцию ОбработкаВызоваHTTPСервиса и на возврате ожидать HTTPСервисОтвет.

Чтобы из файла сделать прокси хранилища 1С, допишем всего пару строк:

Самый простой прокси

Функция ОбработкаВызоваHTTPСервиса(ЗапросКонфигуратора) Экспорт
    //наш файл 1ccr нужной версии 1С
    АдресХранилища = "http://127.0.0.1/storage_8_3_20_1789/storage.1ccr";
    
    //внутри запрос конфигуратора текстом; можно извлекать двоичные данные, но строку анализировать проще
    ТелоЗапроса = ЗапросКонфигуратора.ПолучитьТелоКакСтроку();
    
    //создаем запрос в хранилище
    ЗапросВХранилище = Новый HTTPЗапрос;
    ЗапросВХранилище.УстановитьТелоИзСтроки(ТелоЗапроса);
    
    //соединение с хранилищем
    Соединение = Новый HTTPСоединение(АдресХранилища);
    ОтветХранилища = Соединение.ОтправитьДляОбработки(ЗапросВХранилище);
    Ответ_ДвоичныеДанные = ОтветХранилища.ПолучитьТелоКакДвоичныеДанные();
    
    //хранилище всегда отправляет 200 вне зависимости от успеха операции
    ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
    ОтветКонфигуратору.УстановитьТелоИзДвоичныхДанных(Ответ_ДвоичныеДанные);
    
    //встроенная функция Onescript. Следует использовать, чтобы процесс нашего
    //application pool IIS (w3wp.exe) не разрастался в памяти, т.к. запрос между конфигуратором
    //и хранилищем может быть большого размера, а мы его еще и распаковываем в строку.
    //Неизвестно когда сборщик мусора решит провести уборку, поэтому уберемся принудительно
    ВыполнитьСборкуМусора();
     
    Возврат ОтветКонфигуратору;
КонецФункции

Здесь все тривиально: получили запрос, отдали в хранилище, получили ответ, отправили конфигуратору.

Пространство для маневра открывается невообразимое:)

Моей изначальной целью был контроль комментариев. Конфигуратор при помещении версии в хранилище использует метод DevDepot_commitObjects, а при изменении старой версии DevDepot_changeVersion. Допишу функцию разбора запроса конфигуратора для извлечения интересующих меня атрибутов. Также сразу произведу задел на маршрутизацию по версиям 1С:

Прокси немного посложнее

Функция ОбработкаВызоваHTTPСервиса(ЗапросКонфигуратора) Экспорт
	
	АдресХранилища = "";
	ТелоЗапроса = ЗапросКонфигуратора.ПолучитьТелоКакСтроку();
	ПараметрыЗапроса = ПолучитьПараметры(ТелоЗапроса);
	Если ПараметрыЗапроса.ВерсияПлатформы = "8.3.21.1624" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_21_1624/storage.1ccr";
	ИначеЕсли ПараметрыЗапроса.ВерсияПлатформы = "8.3.21.1484" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_21_1484/storage.1ccr";
	ИначеЕсли ПараметрыЗапроса.ВерсияПлатформы = "8.3.20.1789" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_20_1789/storage.1ccr";
	Иначе
		ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
		СтрокаОтвета =
		"Версия платформы " + ПараметрыЗапроса.ВерсияПлатформы + " в настоящее время не поддерживается.
		|Необходимо настроить работу версии на сервере <имя_сервера>";
		ТекстОтвета = ОписаниеОтвета(СтрокаОтвета);
		ОтветКонфигуратору.УстановитьТелоИзСтроки(ТекстОтвета);
		ВыполнитьСборкуМусора(); //перед каждым возвратом
		Возврат ОтветКонфигуратору;
	КонецЕсли;
	ЗапросВХранилище = Новый HTTPЗапрос;
	ЗаполнитьЗаголовки(ЗапросВХранилище, ЗапросКонфигуратора);
	ЗапросВХранилище.УстановитьТелоИзСтроки(ТелоЗапроса);
	Соединение = Новый HTTPСоединение(АдресХранилища);
	ОтветХранилища = Соединение.ОтправитьДляОбработки(ЗапросВХранилище);
	Ответ_ДвоичныеДанные = ОтветХранилища.ПолучитьТелоКакДвоичныеДанные();
	ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
	ЗаполнитьЗаголовки(ОтветКонфигуратору, ОтветХранилища);
	ОтветКонфигуратору.УстановитьТелоИзДвоичныхДанных(Ответ_ДвоичныеДанные);
	ВыполнитьСборкуМусора();
	Возврат ОтветКонфигуратору;
КонецФункции

Функция ПолучитьПараметры(ТелоЗапроса)
	ПроверяемыеМетоды = Новый Массив;
	ПроверяемыеМетоды.Добавить("DevDepot_commitObjects"); //помещение в хранилище
	ПроверяемыеМетоды.Добавить("DevDepot_changeVersion"); //изменение версии
	СтруктураОтвета = Новый Структура("ИмяСистемы,ИмяМетода,ВерсияПлатформы,Комментарий,Проверять");
	Чтение = Новый ЧтениеXML;
	Чтение.УстановитьСтроку(Сред(ТелоЗапроса, 2)); //удалить BOM
	Пока Чтение.Прочитать() Цикл
		Если Чтение.ТипУзла <> ТипУзлаXML.НачалоЭлемента Тогда
			Продолжить;
		КонецЕсли;
		Если Чтение.ЛокальноеИмя = "call" Тогда
			СтруктураОтвета.Вставить("ИмяСистемы", Нрег(Чтение.ЗначениеАтрибута("alias")));
			СтруктураОтвета.Вставить("ВерсияПлатформы", Чтение.ЗначениеАтрибута("version"));
			СтруктураОтвета.Вставить("ИмяМетода", Чтение.ЗначениеАтрибута("name"));
			Если ПроверяемыеМетоды.Найти(СтруктураОтвета.ИмяМетода) = Неопределено Тогда
				Прервать; //если метод не нужно проверять, не надо дочитывать весь XML до конца
			Иначе
				СтруктураОтвета.Вставить("Проверять", Истина);
			КонецЕсли;
		ИначеЕсли Чтение.ЛокальноеИмя = "comment" Тогда
			Чтение.Прочитать();
			СтруктураОтвета.Вставить("Комментарий", Чтение.Значение);
		КонецЕсли;
	КонецЦикла;
	Чтение.Закрыть();
	Чтение = неопределено;
	Возврат СтруктураОтвета;
КонецФункции

Процедура ЗаполнитьЗаголовки(Приемник, Источник)
	Для каждого Заголовок из Источник.Заголовки Цикл
		Приемник.Заголовки.Вставить(Заголовок.Ключ, Заголовок.Значение);
	КонецЦикла;
КонецПроцедуры

//функция для конвертации строки ответа в нативный ответ конфигуратора
Функция ОписаниеОтвета(Сообщение)
	СтрОтвет =
	"{
	|{3ccb2518-9616-4445-aaa7-20048fead174,""" + Сообщение + """,
	|{00000000-0000-0000-0000-000000000000},""core83.dll:0x0000000000085BA8 crcore.dll:0x0000000000035F7A crcore.dll:0x000000000010F02C VCRUNTIME140.dll:0x0000000000001030 VCRUNTIME140.dll:0x00000000000032E8 unknown:0x0000000000000000 crcore.dll:0x00000000000C133F crserver.exe:0x00000000000092F9 core83.dll:0x00000000002AC58B core83.dll:0x00000000002AC5BC core83.dll:0x000000000017413E ucrtbase.dll:0x0000000000000000 KERNEL32.DLL:0x0000000000000000 unknown:0x0000000000000000 "",""0000000000000000000"",00000000-0000-0000-0000-000000000000},4,
	|{""file://D:\ConfStores\erp"",0},""""}";
	ДД = ПолучитьДвоичныеДанныеИзСтроки(СтрОтвет, "UTF-8", Истина);
	Б64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ДД);
	ХМЛ = "" + Б64 + "";
	Возврат ХМЛ;
КонецФункции

Здесь чуть сложнее: запрос конфигуратора парсится на предмет вызываемого метода хранилища, версии платформы, комментария и имени хранилища. Если у нас настроена обрабатываемая версия 1С, отправим запрос конфигуратора по адресу именно этой версии. Можем, кстати, например, заблокировать помещение/изменение версий для всех версий 1С, кроме последней, а из прошлых версий 1С оставить только чтение. Это актуально, когда на dev-контуре последний релиз, а на prod еще старый (и мы не понимаем, как работает этот черный ящик хранилища, и пишем в него только последней версией 1С, а читаем любыми версиями, в конце концов, файловый доступ к хранилищу позволяет делать так).

По поводу нативного ответа. Понятия не имею, что означает вся эта требуха из гуидов и DLL. Я ее как-то распарсил, догадался как завернуть, и оно работает в таком виде на десятке хранилищ. Подозреваю, что от версии к версии может меняться.

Выглядит нативный ответ, например, так:

8b514160fd4e9dd88770d7b90a0a8a1a.png

Итоговое решение со всеми проверками будет выглядеть примерно следующим образом:

Финальное решение

Функция ОбработкаВызоваHTTPСервиса(ЗапросКонфигуратора) Экспорт
	
	АдресХранилища = "";
	ТелоЗапроса = ЗапросКонфигуратора.ПолучитьТелоКакСтроку();
	ПараметрыЗапроса = ПолучитьПараметры(ТелоЗапроса);
	Если ПараметрыЗапроса.ВерсияПлатформы = "8.3.21.1624" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_21_1624/storage.1ccr";
	ИначеЕсли ПараметрыЗапроса.ВерсияПлатформы = "8.3.21.1484" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_21_1484/storage.1ccr";
	ИначеЕсли ПараметрыЗапроса.ВерсияПлатформы = "8.3.20.1789" Тогда
		АдресХранилища = "http://127.0.0.1/storage_8_3_20_1789/storage.1ccr";
	Иначе
		ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
		ТекстОтвета = ОписаниеОтвета("Версия платформы " + ПараметрыЗапроса.ВерсияПлатформы + " в настоящее время не поддерживается. Необходимо настроить работу версии на сервере <имя_сервера>");
		ОтветКонфигуратору.УстановитьТелоИзСтроки(ТекстОтвета);
		ВыполнитьСборкуМусора();
		Возврат ОтветКонфигуратору;
	КонецЕсли;
	Если ПараметрыЗапроса.Проверять = Истина Тогда
		Сообщение = ПроверитьЗапрос(ПараметрыЗапроса);
		Если Сообщение <> "" Тогда
			ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
			ТекстОтвета = ОписаниеОтвета(Сообщение);
			ОтветКонфигуратору.УстановитьТелоИзСтроки(ТекстОтвета);
			ВыполнитьСборкуМусора();
			Возврат ОтветКонфигуратору;
		ИначеЕсли ПараметрыЗапроса.ИмяМетода = "DevDepot_commitObjects" Тогда
			//вызвать старт gitsync только при помещении в хранилище
			ВызватьСинхронизациюБамбу(ПараметрыЗапроса.ИмяСистемы);
		КонецЕсли;
	КонецЕсли;
	ЗапросВХранилище = Новый HTTPЗапрос;
	ЗаполнитьЗаголовки(ЗапросВХранилище, ЗапросКонфигуратора);
	ЗапросВХранилище.УстановитьТелоИзСтроки(ТелоЗапроса);
	Соединение = Новый HTTPСоединение(АдресХранилища);
	ОтветХранилища = Соединение.ОтправитьДляОбработки(ЗапросВХранилище);
	Ответ_ДвоичныеДанные = ОтветХранилища.ПолучитьТелоКакДвоичныеДанные();
	ОтветКонфигуратору = Новый HTTPСервисОтвет(200);
	ЗаполнитьЗаголовки(ОтветКонфигуратору, ОтветХранилища);
	ОтветКонфигуратору.УстановитьТелоИзДвоичныхДанных(Ответ_ДвоичныеДанные);
	ВыполнитьСборкуМусора();
	Возврат ОтветКонфигуратору;
КонецФункции

//У меня gitsync работает на ферме bamboo. Можно быстро и просто переделать под свои родные костыли
Процедура ВызватьСинхронизациюБамбу(ИмяСистемы)
	СтруктураВеток = Новый Структура;
	СтруктураВеток.Вставить("erp",	"PROJECT-BRANCH1");
	СтруктураВеток.Вставить("buh",	"PROJECT-BRANCH2");
	СтруктураВеток.Вставить("ut",	"PROJECT-BRANCH3");
	Ветка = неопределено;
	СтруктураВеток.Свойство(ИмяСистемы, Ветка);
	Если Ветка <> неопределено Тогда
		Соединение = Новый HTTPСоединение("https://bamboo.local.server", ,"login", "password");
		Запрос = Новый HTTPЗапрос;
		Запрос.АдресРесурса = "rest/api/latest/queue/" + СтруктураВеток[ИмяСистемы];
		Результат = Соединение.ОтправитьДляОбработки(Запрос);
	КонецЕсли;
КонецПроцедуры

Процедура ЗаполнитьЗаголовки(Приемник, Источник)
	Для каждого Заголовок из Источник.Заголовки Цикл
		Приемник.Заголовки.Вставить(Заголовок.Ключ, Заголовок.Значение);
	КонецЦикла;
КонецПроцедуры

Функция ПроверитьЗапрос(Параметры)
	Сообщение = "";
	//иногда нужно быстро и без номера задачи
	Если нрег(Параметры.Комментарий) = "hotfix" Тогда
		Возврат Сообщение;
	КонецЕсли;
	Если Параметры.ИмяСистемы = "erp" Тогда
		РВ = Новый РегулярноеВыражение("^ERP-[0-9]+$");
		Если НЕ РВ.Совпадает(Параметры.Комментарий) Тогда
			Сообщение = "Комментарий '" + Параметры.Комментарий + "' не является задачей Jira проекта ERP";
		КонецЕсли;
	ИначеЕсли Параметры.ИмяСистемы = "buh" Тогда
		РВ = Новый РегулярноеВыражение("^BUH-[0-9]+$");
		Если НЕ РВ.Совпадает(Параметры.Комментарий) Тогда
			Сообщение = "Комментарий '" + Параметры.Комментарий + "' не является задачей Jira проекта BUH";
		КонецЕсли;
	ИначеЕсли Параметры.ИмяСистемы = "ut" Тогда
		РВ = Новый РегулярноеВыражение("^UT-[0-9]+$");
		Если НЕ РВ.Совпадает(Параметры.Комментарий) Тогда
			Сообщение = "Комментарий '" + Параметры.Комментарий + "' не является задачей Jira проекта UT";
		КонецЕсли;
	КонецЕсли;
	Возврат Сообщение;
КонецФункции

Функция ПолучитьПараметры(ТелоЗапроса)
	ПроверяемыеМетоды = Новый Массив;
	ПроверяемыеМетоды.Добавить("DevDepot_commitObjects");
	ПроверяемыеМетоды.Добавить("DevDepot_changeVersion");
	СтруктураОтвета = Новый Структура("ИмяСистемы,ИмяМетода,ВерсияПлатформы,Комментарий,Проверять");
	Чтение = Новый ЧтениеXML;
	Чтение.УстановитьСтроку(Сред(ТелоЗапроса, 2)); //удалить BOM
	Пока Чтение.Прочитать() Цикл
		Если Чтение.ТипУзла <> ТипУзлаXML.НачалоЭлемента Тогда
			Продолжить;
		КонецЕсли;
		Если Чтение.ЛокальноеИмя = "call" Тогда
			СтруктураОтвета.Вставить("ИмяСистемы", Нрег(Чтение.ЗначениеАтрибута("alias")));
			СтруктураОтвета.Вставить("ВерсияПлатформы", Чтение.ЗначениеАтрибута("version"));
			СтруктураОтвета.Вставить("ИмяМетода", Чтение.ЗначениеАтрибута("name"));
			Если ПроверяемыеМетоды.Найти(СтруктураОтвета.ИмяМетода) = Неопределено Тогда
				Прервать; //если метод не нужно проверять, не надо дочитывать весь XML до конца
			Иначе
				СтруктураОтвета.Вставить("Проверять", Истина);
			КонецЕсли;
		ИначеЕсли Чтение.ЛокальноеИмя = "comment" Тогда
			Чтение.Прочитать();
			СтруктураОтвета.Вставить("Комментарий", Чтение.Значение);
		КонецЕсли;
	КонецЦикла;
	Чтение.Закрыть();
	Чтение = неопределено;
	Возврат СтруктураОтвета;
КонецФункции

Функция ОписаниеОтвета(Сообщение)
	СтрОтвет =
	"{
	|{3ccb2518-9616-4445-aaa7-20048fead174,""" + Сообщение + """,
	|{00000000-0000-0000-0000-000000000000},""core83.dll:0x0000000000085BA8 crcore.dll:0x0000000000035F7A crcore.dll:0x000000000010F02C VCRUNTIME140.dll:0x0000000000001030 VCRUNTIME140.dll:0x00000000000032E8 unknown:0x0000000000000000 crcore.dll:0x00000000000C133F crserver.exe:0x00000000000092F9 core83.dll:0x00000000002AC58B core83.dll:0x00000000002AC5BC core83.dll:0x000000000017413E ucrtbase.dll:0x0000000000000000 KERNEL32.DLL:0x0000000000000000 unknown:0x0000000000000000 "",""0000000000000000000"",00000000-0000-0000-0000-000000000000},4,
	|{""file://D:\ConfStores\baza"",0},""""}";
	ДД = ПолучитьДвоичныеДанныеИзСтроки(СтрОтвет, "UTF-8", Истина);
	Б64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ДД);
	ХМЛ = "" + Б64 + "";
	Возврат ХМЛ;
КонецФункции

Далее вы самостоятельно можете исправить/добавить код по вашим требованиям. Например, не только проверять номер задачи на принадлежность системе, а еще и статус задачи. Или исполнителя. При долгих операциях, не требующих их ожидания, можно воспользоваться механизмом фоновых заданий OneScript (мы же не хотим, чтобы разработчик ждал по 10 минут помещения в хранилище?).

Но пока что вы не сможете этим воспользоваться, потому что надо сделать…

Перенаправление запросов

В чем проблема:

На этом этапе у нас уже есть несколько публикаций в IIS:

https://<имя_сервера>/storage_8_3_20_1789/storage.1ccr
https://<имя_сервера>/storage_8_3_21_1484/storage.1ccr
https://<имя_сервера>/storage_8_3_21_1624/storage.1ccr
https://<имя_сервера>/storage/storage.os

Первые три мы можем использовать в конфигураторе. В строке подключения к хранилищу надо указать

https://<имя_сервера>/storage_8_3_20_1789/storage.1ccr/<имя хранилища>

Они будут работать напрямую с хранилищем, а нам это не интересно. Мы хотим, чтобы запросы проходили через наш OneScript файл.

Но вот к скрипту мы пока что подключиться не сможем, конфигуратор не поймет такое:

https://<имя_сервера>/storage/storage.os/<имя_системы>

И выдаст ошибку «хранилище не вижу, работать не буду». Все дело в том, как конфигуратор парсит строки подключения. Например, если вы укажете через tcp:

tcp://server/base

То конфигуратор поймет, что нужно подключиться к хосту server и в XML-содержимое запроса поместить имя хранилища «base».

Для HTTP подключения конфигуратор ищет имя системы обязательно по паттерну ».1ccr». Поэтому попытка заменить ».1ccr» на ».os» не увенчается успехом. Конфигуратор не знает, что такое ».os». Однако же есть третий вариант:

http://server/base

Конфигуратор понимает, что подключаться нужно по протоколу http, а выясняет имя хранилища как для tcp.

Я решил сделать так, чтобы у меня были красивые имена

http://server/base

Которые транслировались бы в

https://server/storage/storage.os

И поможет нам в этом…

URL rewrite

Официальное расширение от Microsoft для работы со строками запросов внутри веб-сервера IIS. Загрузить можно здесь. Установка «далее-далее-готово» на сервере, где расположен веб-сервер.

После установки оно появится тут:

6e8b6a3d0716afe515e92d966e539eb0.png

Теперь можно зайти на свой Default web site и начать создавать правила:

15b9eb46622dbfc201d03ce4ec75557f.png

На скриншоте их 4:

1. Красивые имена указанных хранилищ перенаправляются к файлу .os

2. 3. 4. Запрет прямого обращения к оригинальным публикациям.

Правило перенаправления выглядит следующим образом:

648001948348cf3710260d698114280c.png

Дословно означает следующее: когда пришел запрос (например http://server/ERP), то:

  • взять часть запроса после первого одиночного слэша;

  • сравнить ее с регулярным выражением ^ERP|BAZA1|UT$(^ — начало строки, слово ERP или слово BAZA1 или слово UT, $ — конец строки);

  • Ignore case — no comment;

  • Action type — Rewrite: подменить строку запроса на ту, которая указана в Rewrite URL (тело запроса при этом никуда не денется, просто поедет немного по другому пути).

Соответственно, под такое правило у нас попадут строки подключения:

http://server/erp, http://server/baza1, http://server/ut — то есть имена ваших хранилищ. После добавления этого правила можно указывать строку в таком формате в конфигураторе.

Бинго! Запросы к хранилищу обрабатываются с помощью OneScript.

Напоследок осталось запретить подключаться напрямую к «настоящим» публикациям хранилищ. Добавить 3 правила под каждую версию 1С:
 

2c465157f0eb5f9691d72ed862b962c8.png

Здесь, в отличие от предыдущего правила, в Conditions добавляется {REMOTE_ADDR} Does not match the pattern 127.0.0.1. То есть правило должно работать всегда, кроме случая, когда сам сервер обращается к себе (чем перенаправление и является). Также в Action type выбираем Custon Response со статусом кода 403 «Отказ доступа».

Заключение

Весь этот «троллейбус из буханки» работает уже полгода. За время работы были только проблемы с памятью в процессе IIS «w3wp.exe», обслуживающем Application pool для публикации прокси. Это вылечили функциями очистки мусора в скрипте.

15 разработчиков, ферма Bamboo и много аналитиков каждый день обращаются через этот прокси к 10 хранилищам конфигураций. Разницы по скорости и ожиданиям запросов с tcp нет. В некоторых моментах даже быстрее tcp. Видимо, tcp-серверу легче держать подключение с одним хостом, чем со многими.

Обязательность указания комментариев очень помогает в сборке релизов. Огромный плюс от ухода с версионности: достаточно добавить версию на одном сервере, а работать будет у всех сразу. Вызов синхронизации с git — одно удовольствие:)

В этой статье я хотел описать не как правильно делать, а как можно.

Как правильно — вы решите для себя сами.

Буду рад обсудить материал в комментариях :)

© Habrahabr.ru