В процессе автоматизации деятельности предприятия при помощи 1С: Предприятие часто
возникают задачи интеграции и обмена с оборудованием и другими сторонними информационными
системами, например, банками, веб-сайтами, информационными системами партнеров.
Традиционно 1С: Предприятие выступает в качестве потребителя услуг, и реже – в качестве
поставщика. До этого момента у разработчиков популярными технологиями при получении
информации от 1C были COM и веб-сервисы, которые появились только в версии 8.1.
У обеих технологий (COM и веб-сервисы) есть свои минусы. Главные недостатки заключаются
в следующем. COM-технология позволяет в каждый момент времени выполнять только один
запрос. Обращение возможно только внутри своей локальной сети. Обработка одновременных
запросов для COM возможна, но требует затрат на организацию пула соединений и отладку
многопоточного приложения. Веб-сервисы же сложны по настройке и негибкие для программирования:
жестко привязаны к SOAP-стандартам. Подключение устройств, обменивающихся простыми
пакетами, работающих по http-протоколу, невозможно.
Новая идея организации веб-сервера внутри 1С, предложенная в статье, опирается на
проверенную временем богатую функциональность .Net Framework. Решение на основе
идеи лишены недостатков COM и веб-сервисов 1С. По сравнению с COM http-сервер можно
использовать вне локальной сети, поддерживается одновременная обработка нескольких
запросов. По сравнению с веб-сервисами 1С решение на базе http-сервера обладает
большей гибкостью, так как программист волен сам выбирать формат ответа сервера
(в том числе HTML, JSON, графические изображения, RSS и т.д.), а также контролировать
при ответе URL-адрес, идентификацию пользователей, коды ошибок, куки, кодировку,
осуществлять кеширование. Настройка же http-сервера внутри 1С сводится к простому
запуску внешней обработки.
Описание примера
Пример, приложенный к статье, состоит из двух файлов: http-сервера (внешняя обработка
1С 8.2 HttpServer82) и тестового приложения эмуляции одновременных запросов к серверу
(внешняя обработка 1С 8.2 TestHttpServer82). Обе обработки выполнены на основе управляемых
форм. По умолчанию оба приложения настроены на работу с портом 8082.
Сервер и тестовое приложение разработаны на 1С: Предприятие 8.2 и используют .Net
Framework 4.0 и компонент Elisy .Net Bridge 4. Соответственно, для работы примера
требуется установленный .Net Framework 4.0 и версия библиотеки Elisy .Net Bridge
v.4.0.2.0 и выше. Elisy .Net Bridge позволяет гармонично использовать классы и технологии
.Net Framework на 1С, ведущую роль оставляя 1С.
Для проверки работы достаточно запустить внешнюю обработку HttpServer82.epf из 1С: Предприятие
8.2. Если у вас Windows с включенным UAC, то запустить 1С: Предприятие необходимо
под администратором, иначе у приложения не будет достаточно прав на прослушивание
запросов.
Внешняя обработка позволяет задать порт, по которому будет осуществляться прослушивание
и число создаваемых потоков для обработки одновременных запросов. По умолчанию установлен
порт 8082 и 50 потоков.
После нажатия на кнопку Старт сервер переходит в рабочее состояние и осуществляет
обработку запросов на заданный порт. Например, теперь можно обратиться из вашего
браузера по адресу localhost:8082 и открыть
страницу, которую вернет сервер. В запросе же можно передавать параметры, например,
так: localhost:8082/test?x=1
Для проверки сервера в многопоточном режиме придумана внешняя обработка TestHttpServer82.epf,
которая осуществляет одновременный запуск запросов в цикле. В основу обработки для
организации параллельной работы положена замечательная технология PLINQ (Parallel
LINQ) из .Net framework 4.
Запускать тестовую обработку TestHttpServer82.epf следует из отдельного от сервера
сеанса 1С: Предприятие, иначе одновременный запуск в одном сеансе двух обработок
приведет к зависанию. В качестве параметров тестового примера выступают адрес запроса,
число одновременных запросов и число циклов. По умолчанию запуск алгоритма приведет
к 3 циклам обращений по адресу localhost:8082
с 20 одновременными запросами (надо отметить, что число одновременных запросов ограничено
числом ядер процессора).
Принцип работы
.Net framework предлагает своим разработчикам класс HttpListener, отвечающий за прослушивание http-протокола. Используя HttpListener,
вы можете создать прослушивание http-трафика, которое отвечает на http-запросы.
Вы можете использовать этот класс только на операционных системах Windows XP SP2
или Windows Server 2003 и выше. Попытка использования класса на более ранних системах
вызовет исключение.
Ниже приведен пример кода для 1С, который инициализирует объект типа HttpListener,
настраивая его на прослушивание всех URL по порту 8082. При запуске в 1С работа
программы приостанавливается, пока не последует запрос на порт, например, из браузера.
Как только вы пошлете из браузера запрос, например, 127.0.0.1:8082/ 1С вернет описанную в программе html-строку.
За что ответственен каждый участок кода, можно понять из комментариев, приведенных
внутри исходного кода.
ПодключитьВнешнююКомпоненту("Elisy.NetBridge4");
AddIn = New("AddIn.ElisyNetBridge4");
net = AddIn.GetNet();
Если НЕ net.GetStatic("System.Net.HttpListener","IsSupported") Тогда
Сообщить("Для класса HttpListener нужна ОС Windows XP SP2/2003 и выше");
Возврат;
КонецЕсли;
listener = net.New("System.Net.HttpListener");
listener.Prefixes.Add("http://*:8082/");
listener.Start();
Сообщить("Прослушивание...");
//Метод GetContext блокирует выполнение программы пока ждет запрос.
context = listener.GetContext();
request = context.Request;
// Получить объект ответа
response = context.Response;
// Создать ответ - HTML-строку
responseString = "Ответ от HttpListener";
buffer = net.GetStatic("System.Text.Encoding", "UTF8").GetBytes(responseString);
// Получить поток ответа и записать ответ в него.
response.ContentLength64 = buffer.Length;
output = response.OutputStream;
output.Write(buffer,0,buffer.Length);
// Необходимо закрыть выходной поток и остановить прослушивание
output.Close();
listener.Stop();
Это самый простой пример. Главный смысл простейшего примера в том, что 1С превращается
в полнофункциональный http-сервер, а 1С-программист получает инструмент для гибкой
настройки ответа с сервера. Он может вернуть его в любом формате (html, рисунок
или JSON), затребовать идентификацию пользователя или вообще вернуть ошибку. Но
вместе с тем есть недостатки: блокирование всего приложения, обработка только одного
запроса. Пример к статье содержит более сложный код, за счет чего устранены недостатки
простейшего примера из листинга.
От простого к сложному
В готовый пример вошел видоизмененный код работы с HttpListener, в котором вызов
метода listener.GetContext() заменен вызовом асинхронных аналогов listener.BeginGetContext()
и listener.EndGetContext(). Кроме этого создаются N отдельных потоков и заложена
синхронизация между потоками с вызовом кода-обработки запроса на стороне 1С: Предприятие.
Достоинством предлагаемой реализации http-сервера является возможность одновременной
обработки N запросов в разных потоках с передачей логики обработки в метод формы
1С: Предприятие. Пример при каждом запросе возвращает html с описанием источника
и URL запроса. Решение очень гибкое, так как все доработки можно делать прямо из
конфигуратора 1С. Никакие дополнительные проекты (например, C# или VB.Net) не задействованы.
Пример условно можно разделить на 2 части: код, который выполняется на стороне 1С: Предприятие
и код, который выполняется на стороне .Net framework. При этом .Net framework взял
на себя все, что нельзя реализовать средствами 1С: Предприятие, например, создание
и синхронизацию потоков.
Http-сервер оформлен в виде управляемой формы. При нажатии на кнопку Старт происходит
создание объектов классов HttpServer и Helper. Оба класса описываются на C# в макете
обработки ИсходныйКод и компилируются «на лету» в обработчике ПриОткрытии формы.
Класс Helper отвечает за перенаправление .Net-события в функцию ОбработатьЗапрос
на форме 1С и формирование сообщение об ошибке.
Опустив не относящиеся к теме задачи по инициализации формы и вспомогательных классов,
следует остановиться на методе ОбработатьЗапрос, который отвечает за возврат результата
http-клиенту. Вызывается обработчик из класса Helper, в качестве параметра принимает
объект контекста, может возвратить сообщение об ошибке, которое будет возвращено
клиенту.
Объект контекста типа HttpListenerContext, передаваемый в метод ОбработатьЗапрос
содержит два важных свойства: Request и Response, отвечающие за информацию о запросе
и ответе соответственно.
Свойство Request позволяет получить параметры и содержимое, переданные клиентом
при запросе. Информация о запросе, помещенная в свойство Request содержит такие
основные свойства как:
AcceptTypes – MIME-типы, поддерживаемые клиентом
ContentEncoding – информация о кодировке при ответе
Headers – набор заголовков
HttpMethod – метод HTTP, определенный клиентом
InputStream – поток, содержащий данные тела, пришедшие от клиента
IsAuthenticated – булево значение, показывающее идентифицирован ли пользователь
IsLocal – значение, показывающее локальный ли запрос (через localhost)
QueryString – строка запроса из запроса
RawUrl – информация об URL без хоста и порта
UrlReferrer – URL ресурса-источник данного перехода
UserAgent – информация о агенте-браузере пользователя
Через свойство Response осуществляется возврат контента клиенту, передача информации
об ошибке или перенаправление. Сопровождаться все может выставлением необходимых
заголовков. Многие свойства ответа (Response) схожи со свойствами запроса. Значимые
среди них следующие:
ContentEncoding – информация о кодировке при ответе
Headers – набор заголовков
OutputStream – поток, в который будет записан ответ (например, текст Html, XML или
байтовый массив изображения)
RedirectLocation – свойство отвечает за HTTP-заголовок Location и позволяет перенаправить
вызов
StatusCode – код статсуса при возврате клиенту, например: 200 (OK), 404 (ресурс не
найден)
StatusDescription – описание статуса при возврате клиенту
Следующий код метода ОбработатьЗапрос преобразует сформированную строку РезультатHTML
с HTML-кодом в набор байт и записывает этот набор байт в выходной поток, который
будет возвращен клиенту. Ссылка на выходной поток получена через параметр метода.
ответСервера = context.Response;
массивБайт = net.GetStatic("System.Text.Encoding", "UTF8").GetBytes(РезультатHTML);
ответСервера.ContentLength64 = массивБайт.Length;
выходнойПоток = ответСервера.OutputStream;
выходнойПоток.Write(массивБайт, 0, массивБайт.Length);
выходнойПоток.Close();
Основой обработки является класс HttpServer, который создает объект HttpListener
и нужное число потоков для обработки. При вызове метода Start происходит запуск
всех потоков-обработчиков и отдельного потока для работы HttpListener. Благодаря
этому, можно продолжать работать с 1С во время работы http-сервера. При поступлении
запроса HttpListener помещает запрос в очередь, где каждый запрос последовательно
обрабатывает первый свободный поток. При обработке потока срабатывает цепочка вызовов:
событие HttpServer.ProcessRequest, обработчик события Helper. HttpServer_ProcessRequest,
1С-функция Форма. ОбработатьЗапрос. Код C# класса HttpServer при запуске 1C из макета
ИсходныйКод компилируется «на лету».
_listener.AuthenticationSchemes = authenticationScheme;
_listener.Prefixes.Add(String.Format(@"http://+:{0}/", port));
_listener.Start();
_listenerThread.Start();
for (int i = 0; i < _workers.Length; i++)
{
_workers[i] = new Thread(Worker);
_workers[i].Start();
}
Начало получения запросов происходит в методе HandleRequests до тех пор, пока обработка
не прекратится пользователем. При поступлении запроса, запрос передается в метод
ContextReady и работа процесса прослушивания продолжается.
private void HandleRequests()
{
while (_listener.IsListening)
{
var context = _listener.BeginGetContext(ContextReady, null);
if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle }))
return;
}
}
private void Worker()
{
WaitHandle[] wait = new[] { _ready, _stop };
while (0 == WaitHandle.WaitAny(wait))
{
HttpListenerContext context;
lock (_queue)
{
if (_queue.Count > 0)
context = _queue.Dequeue();
else
{
_ready.Reset();
continue;
}
}
try { ProcessRequest(context); }
catch (Exception e) { Console.Error.WriteLine(e); }
}
}
Заключение
Многопоточный сервер, описанный в статье, по простоте настройки и функциональности
превосходит традиционные методы доступа извне к информационным базам 1С: Предприятие
8.х. Это простой по запуску метод, позволяющий одновременно обрабатывать несколько
запросов. При этом приложение 1С не блокируется, и пользователь может продолжать
работу после запуска сервера.
Основное преимущество предложенного подхода – полная подконтрольность программисту
процесса от получения запроса до формирования ответа. Например, на этапе получения
запроса может быть выполнен парсинг URL, получена информация о том, как себя идентифицировал
пользователь, а также полная информация о клиенте (поддерживаемые языки, записанные
куки, заголовки, метод доступа). Ответ же можно вернуть практически любой, начиная
от ошибки 404 Not found, заканчивая разными графическими форматами, форматами Word,
Excel и популярными форматами на основе XML (JSON, HTML, RSS).
Пример, приложенный к статье, спроектирован так, что его функциональность можно
легко расширить. Например, для организации кеша применить System.Web.Caching.Cache
класс из .Net framework. А при парсинге URL попробовать поработать с классом RouteCollection
из Asp.Net MVC. При создании RSS-ленты вам поможет класс System.ServiceModel.Syndication
.SyndicationFeed. А при Json-сериализации обратите внимание на класс System.Runtime.Serialization.Json.DataContractJsonSerializer.
Конкретно для предложенного подхода на данный момент не выявлено явных недостатков.
Есть вероятность того, что 1С: Предприятие в силу своих ограничений не сможет обеспечить
на своей стороне должного распараллеливания и значительного увеличения производительности.
Тем не менее опыты проведенные ранее для 1С: Предприятие 8.2 показали, что в схожем
применении 1С достигается увеличение производительности, и 1С работает при этом
стабильно.
Необходимо обратить внимание разработчиков еще на несколько моментов. Любая публикация
информации в Интернете связана с риском взлома вне зависимости от способа публикации.
Но в предложенном способе благодаря гибкости есть больше возможностей противостоять
угрозам извне. Например, в запросе теперь доступен IP-адрес клиента, который можно
блокировать по каким-то правилам (осуществляя поиск в черном списке локально или
в специализированных сервисах). Параметры запроса доступны в виде строк и их можно
анализировать на стороне 1С или .Net framework и блокировать опасное содержимое.
Кроме этого есть несколько специализированных .Net-библиотек, доступных для решения
данной проблемы, которые можно привлечь в 1С, например AntiXSS.
Второй момент заключается в том, что известные методы, в том числе и этот, предоставления
услуг со стороны 1С не предназначены для массовых обращений и будут всегда уступать
с этой точки зрения профессиональным серверам, например, IIS.
Примеры к статье:
TestHttpServer82.epf (8.30 kb)
Автор: Elisy