• Программирование
  • C++
  • Веб-сервисы, защищенные посредством промежуточного программного обеспечения, ориентированного на обработку сообщений

Веб-сервисы, защищенные посредством промежуточного программного обеспечения, ориентированного на обработку сообщений

ОГЛАВЛЕНИЕ

Данная статья описывает метод доставки сообщений Soap (простой протокол доступа к объектам), преобразуемых в последовательную форму с помощью сервисов веб-клиента ASP.NET через MSMQ (очередь сообщений Майкрософт) и MQ (очередь сообщений)

 •    Скачать исходные файлы и исполняемые файлы – 792Кб

Введение

Данная статья описывает использование подключаемой транспортной инфраструктуры, входящей в состав веб-сервисов ASP.NET, позволяющей разработчикам доставлять сообщения Soap, встроенные в документ/буквенный формат, к конечной точке посредством механизмов, отличных от стандартной передачи HTTP.

В данной статье рассматривается каркас, поддерживающий добавление альтернативных механизмов физической доставки, и включены две такие реализации – MSMQ и Websphere MQ, выбранные для демонстрации возможности промежуточного хранения для более гарантированной доставки запроса веб-сервисов. Разрабатываемый каркас включает в себя модернизацию веб-сервисов (WSE) 1.0 для защищенной двусторонней передачи сообщений посредством механизмов не HTTP, тем самым позволяя разработчику выбирать 'доверенную' (незашифрованное сообщение) или 'сомнительную' (зашифрованное сообщение) доставку до выбранной конечной точки.

Soap был разработан как транспортный нейтральный протокол – для использования в сочетании с различными транспортными протоколами, такими как HTTP(S), SMTP, FTP и т.д. – для доставки структурированных и типизированных данных между двумя участниками. Много работы ушло на встраивание в веб-сервисы ASP.NET поддержки формирования сообщений Soap способом, способствующим взаимодействию в разнородных средах (например, .NET, использующий сервер BEA Weblogic, на котором размещается веб-сервис), но до сих пор стандартный доступ к веб-сервисам в сфере Microsoft был связан практически исключительно с HTTP.

Soap посредством HTTP

Передача HTTP имеет достоинства и недостатки. Несомненный плюс – развитость связанной с ней инфраструктуры безопасности, появившейся в результате повсеместности интернета. Недостатком же, наоборот, является отсутствие какой-либо формы гарантированной и надежной (только одиночное сообщение) доставки внутри протокола, породившего такие решения, как HTTPR от IBM. К тому же серверная служба должна быть физически подключена к сети, чтобы запрос был получен по HTTP, и существуют веские причины для поиска альтернативных способов передачи сообщения.

Soap посредством альтернативных способов передачи

Хотя ASP.NET делает много для продвижения ориентированного на HTTP связывания с веб-сервисами, HTTP – не единственный протокол, доступный для передачи Soap. В среде разработки .NET имеется инфраструктура веб-сервисов, позволяющая  изменять механизм доставки сообщения и конечную точку статически (время разработки) или динамически (время выполнения). Настоящая мощь этой инфраструктуры – называемой "подключаемыми протоколами" Microsoft – в том, что метод передачи может изменяться под запрос логического клиента для направления Soap через любую оптимальную среду, которую разработчик реализует.

Данный разбор примеров показывает возможность эффективно переключать средства передачи под клиентом ASP.NET между HTTP, MSMQ и Websphere MQ. Он покажет, как просто поменять цель запроса веб-сервиса Soap со стандартного размещенного на IIS типа на слушателя, ожидающего очереди MSMQ или MQ. Демонстрируется применение API с использованием стандартных типов, таких как строки, массивы, двоичные данные и пользовательские упорядочиваемые типы. Также демонстрируется реализация данной поддержки в асинхронном режиме.

Обеспечение безопасности Soap

Протоколы передачи с организацией очередей годятся для использования внутри предприятия, т.е. внутри надежной сети организации с соизмеримыми (обычно уменьшенными) требованиями защиты. Однако при использовании средства доставки не по HTTP потенциально убирается слой испытанной защиты, которому нужно создать замену. К счастью, было разработано несколько единых стандартов для нового поколения протоколов веб-сервисов, охватывающих (среди прочего) защиту, маршрутизацию сообщений и вложения. Эта инициатива сосредоточена на обеспечении защиты сообщений между множеством узлов обработки, участвующих в бизнес-операции независящим от передачи образом – не опираясь на такие технологии, как SSL, работающих хорошо лишь в двухточечном режиме. Эти усилия недавно завершились выпуском реализации Microsoft вышеупомянутых стандартов в форме пакета модернизации веб-сервисов (WSE) 1.0. Разбор примеров также покажет, как реализация защиты WS внутри WSE 1.0 может применяться для защиты (в нашем случае цифровой подписью и симметричным шифрованием) сообщений, генерируемых клиентами и серверами ASP.NET, вместо использования собственных механизмов безопасности, присущих двум продуктам формирования очередей. Также рассматривается поддержка DIME, предоставляемая WSE в качестве альтернативного метода отправки двоичных данных, разработанная для передачи больших потоков двоичных данных.

Цели статьи

К концу разбора примеров будут разъяснены следующие аспекты:
•    Как писать новые протоколы доставки в рамках инфраструктуры подключаемых протоколов ASP.NET
•    Как подключаться к MSMQ и Websphere MQ из C# изнутри данной инфраструктуры
•    Как объединять WSE 1.0 с веб-сервисами на высоком и низком (прямое использование конвейеров программы работы с файлами WSE) уровнях для защиты данных
•    Как использовать WSE при передаче двоичных данных
•    Как запускать асинхронную доставку сообщений Soap с организацией очередей от клиента

Смысл работы

Подлинный смысл работы подразумевал пробный проект, направленный на определение того, насколько легко клиент .NET может подключиться к существующему ориентированному на Java-серверу с использованием веб-сервисов для взаимодействия, но через IBM Websphere MQ, а не HTTP. Эта статья дает ориентированный на .NET сервер на том основании, что при этом для усвоения статьи нужны лишь навыки работы с .NET, и что при наличии WSE на обоих концах легче продемонстрировать защищенную связь. Но особенности взаимодействия с реализацией сервиса Java указываются в соответствующих местах по всей статье.

Системные требования

Собранное решение требует следующей среды развертывания:
1. Windows 2000 SP2 или Windows XP Pro с установленными MSMQ и IIS
2. Среда разработки .NET 1.0 (SP1 или выше)
3. Среда выполнения WSE 1.0 SP1
4. (Только для поддержки MQ) Пробная версия Websphere MQ 5.3
5. (Только для поддержки MQ) Среда выполнения VB6
Для повторной сборки двоичных файлов решения необходимы следующие дополнительные инструменты:
6. VS.NET (C#)
7. WSE 1.0 SP1 – рекомендуется полная установка
8. Инструмент установки VS.NET WSE (по желанию)

Требуемые навыки

Читатель должен иметь следующие навыки:
•    знание C# на среднем уровне, знакомство с написанием и использованием базового веб-сервиса в .NET, так как будет довольно подробно рассматриваться клиентская инфраструктура (включая посредник, генерируемый WSDL).
•    желательно наличие опыта работа в области веб-сервисов второго поколения, таких как защита WS и вложения WS / DIME.
•    если вы хотите использовать Websphere MQ в качестве средства передачи (его можно отключить в конфигурационном файле пробного приложения), настоятельно рекомендуется установить пробную версию и настроить стандартные настройки.
•    начальное понимание обозначений унифицированного языка моделирования (UML).

Цели статьи

Цель – добиться успеха в следующих сферах:
•    Разделить то, что на первый взгляд кажется прочной связью между преобразованием сообщения Soap в последовательную форму и доставкой упомянутого сообщения посредством http до конечной точки.
•    Повторно использовать блок сериализации веб-сервисов .NET, чтобы не пришлось самостоятельно вручную делать Soap.
•    Подключиться к части доставки инфраструктуры с целью доставки запросов Soap к очереди и получению ответов Soap от очереди. Поддерживать MSMQ и Websphere MQ.
•    Для реализации этого клиент должен уметь отправлять нужную ему очередь, чтобы доставлять запросы к очереди максимально стандартным способом, т.е. с минимальными отличиями от способа описания URL.
•    После достижения этого нужно выяснить, какое влияние / выгоды дает использование WSE для обработки сообщений Soap – в обеспечении безопасности потоков сообщений и в обеспечении передачи двоичных вложений вне оболочки Soap.
•    Научиться упаковывать результирующий код способом, минимизирующим работу разработчиков, желающих включить ориентированные на формирование очередей средства передачи в свои решения.
•    Убедиться, что основной каркас функционирует в среде множественного одновременного использования.


Создание посредника WSDL и веб-сервисов в .NET

Соглашения интерфейсов описываются с использованием WSDL - сродни IDL(язык определения интерфейсов) для COM.  Как только этот WSDL становится доступен в URL или в файле, класс посредника веб-сервисов генерируется путем прямого использования отдельного инструмента WSDL.EXE, поставляемого с SDK (набор средств для разработки ПО), или неявно – при использовании средства «Добавить веб-ссылку» Visual Studio .NET:

Рисунок 1. Процесс посредника WSDL-2

Сгенерированный файл посредника (обычно называемый reference.cs) помещается в подпапку проекта подпапки с именем \Web References и содержит класс, унаследованный от SoapHttpClientProtocol. Сгенерированный класс скрывает значительную часть сложности сериализации и передает специальные сообщения, относящиеся к вызову стандартных веб-сервисов XML. В получаемом сгенерированном файле посредника находится сочетание синхронных и асинхронных форм методов – ниже показан пример базового сгенерированного (синхронного) метода:

 [SoapDocumentMethodAttribute(...)]
public string IsCollaboratorRegistered(string vstrEnvUUID)
{
  object[] results = this.Invoke("IsCollaboratorRegistered", new object[]
                     { vstrEnvUUID });
  return ((string)(results[0]));
}

При наличии стандартного посредника клиенты могут подключаться к веб-сервису по соответствующему URL с помощью следующего кода:

// Получаем экземпляр посредника
localhost.RegService objWSReg = localhost.RegService();    
// Конечная точка может меняться во время выполнения, и т.д.
objWSReg.Url = "http://192.168.32.1/MyService.asmx";    
// Выполняем вызов
string strRetXML
     = objWSReg.IsCollaboratorRegistered("12345678901234567890123456789012");

Умение менять URL и описывать этот URL в виде "схемы протокола" и "конечной точки" является ключевой составляющей метода.

Во время выполнения одна строка в посреднике, содержащая вызов this.Invoke( …), скрывает много работы, разбиваемой на следующие основные части:
•    Выбор средства передачи / подтверждение поддержки
•    Сериализация вызова интерфейса программирования приложений Soap
•    Запуск транспортного протокола

Это разбирается далее в статье, чтобы показать, как инфраструктура подключаемого протокола объединяется с сериализацией Soap для построения и доставки сообщений. В частности, в посреднике есть несколько методов, которые можно переопределить, чтобы подключиться или заменить процесс протокола передачи сообщений.

Определение интернета и других сетевых транспортных протоколов для веб-сервисов

Хотя в реальности протоколом доставки сообщений Soap является HTTP, в среде разработки .NET имеется инфраструктура, позволяющая изменять механизмы доставки сообщений статически (время разработки) или динамически (время выполнения).

Среда разработки .NET использует три специальных класса для предоставления информации, требуемой для подключения к ресурсам интернета/сети посредством модели запрос/ответ:

1. Класс Uri, содержащий URI(универсальный код ресурса) интернет-ресурса, который вы ищете (включая схему протокола, например http://www.microsoft.com/webservices/stockcheck.asmx);
2. Класс WebRequest, инкапсулирующий запрос на доступ к ресурсу сети;
3. Класс WebResponse, предоставляющий контейнер для поступающего ответа от ресурса сети.

Клиентские приложения создают экземпляры WebRequest путем передачи URI ресурса сети методу WebRequest.Create(). Этот статический метод создает экземпляр WebRequest для конкретного протокола, например, HTTP. Возвращаемый экземпляр WebRequest обеспечивает доступ к свойствам, контролирующим запрос к серверу и доступ к потоку данных, отправляемому при выполнении запроса. Метод GetResponse() из экземпляра WebRequest отправляет запрос из клиентского приложения серверу, указанному в URI.

[Примечание: Универсальный идентификатор ресурса (URI) – компактная строка символов для идентификации абстрактного или физического ресурса, как определено в RFC2396. Схема – стандартная часть URI, которую можно разбить на несколько отдельных частей. Все сетеыве идентификаторы могут быть сопоставлены к одной из частей. Схема – идентификатор протокола, например, http, ftp и т.д.]

Имеется встроенная поддержка трех специальных схем протокола - "http:", "https:" и "file:". Будут созданы новые версии классов WebRequest и WebResponse для обеспечения поддержки дополнительных двух схем:

•    "msmq:" Поддерживает модель запрос-ответ, в которой целью является локальная или удаленная очередь MSMQ
•    "mq:" Поддерживает модель запрос-ответ, в которой целью является комбинация администратора очередей/имени очереди MQ

Можно будет добавить дополнительную поддержку для других схем, таких как "mailto:" или "ftp:" для SMTP и FTP соответственно. Попытка создать WebRequest для Uri, содержащего неподдерживаемую схему:

Uri uri = "mq://accountq1";
WebRequest newWebRequest = WebRequest.Create(uri);

выдаст исключение в виде NotSupportedException, означающее, что схема запроса, заданная в requestUri, не зарегистрирована. Данный вопрос нужно решить следующим образом, чтобы успешно вводить новые схемы. Нужно дать возможность клиенту идентифицировать очередь как Uri запроса и уметь выполнять вызов метода по тому Uri, как он выполнялся бы по основанной на http конечной точке.

Определение пользовательских транспортных протоколов

Классы WebRequest и WebResponse образуют основу инфраструктуры подключаемых протоколов. Для определения нового протокола типа "xxx://" нужно создать порождения каждого из этих базовых классов, плюс класс, реализующий IWebRequestCreate . Следующая диаграмма изображает основную модель, задействованную в создании новых протоколов:

Рисунок 2. Объектная модель для подключаемых протоколов

Были включены основные операции и атрибуты, чтобы показать ключевые аспекты любой реализации.

Общие аспекты

Несколько атрибутов и операций, которые могут быть переопределены потомками классов WebRequest и WebResponse, являются избыточными при реализации не свойственных интернету протоколов передачи. К тому же, имеется много общего в двух реализациях очереди. Это позволяет абстрагировать данную общность и избыточность в один промежуточный класс. Например, свойство Method, используемое в составе любого веб-запроса (GET, POST и т.д.), неуместно в предлагаемых реализациях новых схем.

Определяются следующие новые производные типы для расширения иерархии:

Тип Базовый тип Назначение
MyBaseWebRequest WebRequest Содержит переопределения, общие для реализаций MQ и MSMQ
MyBaseWebResponse WebResponse Содержит переопределения, общие для реализаций MQ и MSMQ
MSMQWebRequest MyBaseWebRequest Реализация MSMQ WebRequest (закрыта)
MSMQWebResponse MyBaseWebResponse Реализация MSMQ WebResponse (закрыта)
MQWebRequest MyBaseWebRequest Реализация MQ WebRequest (закрыта)
MQWebResponse MyBaseWebResponse Реализация MQ WebResponse (закрыта)

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

1. Подготовка к регистрации схемы

Реализация должна включать в себя версию интерфейса IWebRequestCreate, например для MSMQ:

public class MSMQRequestCreator : IWebRequestCreate
{
  public WebRequest Create(Uri uri)
  {
    // Создаем новый объект запроса MSMQ
    return new MSMQWebRequest(uri);
  }
}

2. Регистрация схемы и создание Uri

Классы-потомки WebRequest, реализующие новые протоколы, должны быть зарегистрированными в классе WebRequest, чтобы быть допущенными к управлению деталями выполнения реальных подключений к нестандартным ресурсам сети, таким как очереди.

Обычно это выполняется аналогично следующему:

// Регистрируем префикс "mq:" здесь для WebRequest
MQRequestCreator objCreator = new MQRequestCreator();
WebRequest.RegisterPrefix("mq", objCreator);

Это обеспечивает последующее подключение кода создания для экземпляра новой схемы, такой как в вышеприведенном коде:

Uri uri = "mq://accountq1";
WebRequest newWebRequest = WebRequest.Create(uri);

Будет работать, так как новая схема известна инфраструктуре веб-сервисов .NET.

3. Реализация основных переопределенных членов

Производные классы должны переопределять абстрактные методы и свойства WebRequest и WebResponse для предоставления подключаемого интерфейса. Эти члены хорошо документированы для .NET Framework 1.0 – значимая подгруппа показана ниже:

Член

Назначение

Method

Обычно связан с формой метода протокола для использования в этом запросе, например PUT, GET и т.д. Не используется здесь.

Headers

Получает или устанавливает набор пар имя заголовка/значение, связанный с запросом. Не используется здесь.

ContentLength

Содержит количество байтов данных, отправленных интернет-ресурсу экземпляром WebRequest.

ContentType

При переопределении в классе-потомке получает или устанавливает тип содержимого отправляемых данных запроса. В нашем случае всегда text/xml.

Credentials

Получает или устанавливает сетевые реквизиты, используемые для проверки подлинности запроса к интернет-ресурсу. Не используется в нашем случае.

PreAuthenticate

Показывает, нужно ли предварительно проверять подлинность запроса. Не используется в нашем случае.

Proxy

Получает или устанавливает сетевой посредник, используемый для обращения к этому интернет-ресурсу. Не используется в нашем случае.

GetRequestStream

Создает и возвращает Stream(поток) для записи данных (сообщение Soap) для интернет-ресурса. Реализован в нашем базовом классе.

BeginGetRequestStream

Асинхронная версия вышеуказанного. Не поддерживается в нашем случае.

EndGetRequestStream

Асинхронная версия вышеуказанного. Не поддерживается в нашем случае.

GetResponse

Возвращает ответ на запрос. Это переопределение образует основной компонент наших реализаций MSMQ и MQ, так как оно вызывает специфичный для протокола код.

BeginGetResponse

Асинхронная версия вышеуказанного. Не поддерживается в нашем случае.

EndGetResponse

Асинхронная версия вышеуказанного. Не поддерживается в нашем случае.

Abort

Отменяет асинхронный запрос, начатый с метода BeginGetResponse. Не поддерживается в нашем случае.

Будут реализованы все переопределения, общие для версий MQ и MSMQ в MyBaseWebRequest /MyBaseWebResponse , и особенности в соответствующих производных классах.

Выполнение производного от WebRequest экземпляра

Здесь изображена карта общего процесса клиентской сериализации и передачи в отдельных вызовах, делаемых инфраструктурой веб-сервисов .NET во время вызова синхронных веб-методов.
Диаграмма изображает следующую последовательность событий на пути выполнения запроса:

Рисунок 3. Диаграмма взаимодействия для WebRequest

Ключевые особенности операции включают в себя:

1.    Переопределение GetWebRequest в генерируемом посреднике Soap (полученном из SoapHttpClientProtocol) позволяет перехватывать регистрацию (для схем msmq / mq) и создание надлежащего экземпляра WebRequest. В случае схемы "msmq" данное создание выполняется с помощью экземпляра MSMQRequestCreator.
2.    Инфраструктура веб-сервисов .NET вызывает закрытый метод, BeforeSerialize(), запускающий получение и установку различных членов данных внутри экземпляра общего класса, производного от WebRequest, устанавливающий метод (ненужный, но требуемый для обеспечения операции – возращение исключения NotSupported заставляет инфраструктур вебсервисов .NET отклонять весь запрос!) и получающий (пустой) набор веб-заголовков.
3.    Затем метод GetRequestStream() вызывается в классе, производном от WebRequest, где создается поток (в данном случае MemoryStream) и возвращается инфраструктуре.
4.    На данном этапе инфраструктура начинает процесс сериализации Soap и помещает результаты в поток. На данном этапе серьезная проблема заключается  в том, что при выталкивании упорядоченного потока в сеть для передачи стандартная обработка требует закрытия потока! Учитывая, что еще не достигнут  этап, когда придется использовать поток для проталкивания в очередь, на первый взгляд это непреодолимое препятствие!! Пока пренебрежем им. Раздел под названием "Подключение к упорядоченному потоку Soap" разъясняет проблемы и возможные решения.
5.    После завершения подготовки потока инфраструктура вызывает переопределенный GetResponse() в экземпляре класса MSMQWebRequest. Здесь применяются особенности работы с очередями с помощью надлежащего механизма (System.Messaging для MSMQ или предоставленная IBM библиотека COM для MQ). Это подразумевает запись сообщения Soap и ожидание ответа на него от серверной службы. Особенности протоколов передачи MSMQ и MQ рассмотрены в следующем разделе. Подключение к потоку на данном этапе снова обойдено молчанием.
6.    Вне зависимости от особенностей работы MSMQ / MQ, результатом должна быть доставка объекта ответа надлежащего типа (например, MSMQWebResponse), или возбуждается экземпляр WebException, если передача не удалась.

Эти типы исключений обсуждаются далее в статье.

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


Реализация протоколов передачи MSMQ и Websphere MQ

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

Разные общие свойства запроса

Несколько переопределенных свойств не требуются для операций и может быть разрешено выбрасывание исключения NotSupported, например:

public override IWebProxy Proxy 
{
  /* переопределить */ get { throw new NotSupportedException(); }
  /* переопределить */ set { throw new NotSupportedException(); }
}           
           
Другие, как ContentLength, ContentType и Timeout и т.д., необходимы и устанавливаются правильно:
public override int Timeout
{
  /* переопределить */ get { return m_intTimeout; }
  /* переопределить */ set { m_intTimeout = value; }
}           

Эти свойства, общие для реализаций MSMQ и MQ, выделяются в абстрактный класс MyBaseWebRequest.

Предоставление потока для сериализации

MyBaseWebRequest содержит защищенный член данных (доступен в производных классах), именуемый m_RequestStream и устанавливаемый при вызове клиентской инфраструктурой веб-сервисов:

public override Stream GetRequestStream()
{   
    if (m_RequestStream == null)
        m_RequestStream = new MemoryStream();
    else
        throw new InvalidOperationException("Request stream already retrieved(Поток запроса уже возвращен)");
    return m_RequestStream;       
}

Так как не предполагается предоставление потока два раза в ходе одного вызова метода веб-сервисов, и не предполагается делить экземпляры WebRequest между несколькими посредниками, обеспечивается защита от такого происшествия.

Примечание: Мы сами не выполняем сериализацию. Мы предоставляем контейнер (поток) для инфраструктуры веб-сервисов, чтобы она выполняла сериализацию.

Разные общие свойства ответа

Кроме того, MyBaseWebResponse используется без изменений реализациями MSMQ и MQ. Этот класс содержит информацию, более похожую на используемую интернет-ресурсом (код состояния, описание состояния, и т.д.), и так как все ошибки выбрасываются в виде исключений в стек, единственными ключевыми методами в этом классе являются SetDownloadStream()- позволяющий вызывающему оператору WebRequest помещать поток ответа Soap в экземпляр класса, и GetResponseStream()– возвращающий этот поток ответа вызывающей инфраструктуре обработки ответа вебсервисов .NET. При условии успешного вызова после возврата потока происходит стандартное преобразование в параллельную форму (десериализация) Soap.

Общие особенности ориентированной на очереди передачи

Имеется несколько функциональных аспектов, которые следует встроить в управление очередью.

Лимит времени запроса

Стандартный сгенерированный посредник содержит член данных лимита времени, имеющий значение по умолчанию и устанавливаемый перед вызовом фактического вызова веб-сервиса. Этот лимит времени должен соблюдаться реализациями формирования очереди, и они должны возбуждать исключения, как делали бы стандартные обработчики протокола в случае превышения лимита времени.

Сопоставление запроса-ответа (на уровне протокола)

В многопользовательском сценарии много запросов будут ожидать обработки единовременно, и ответы на эти запросы будут приниматься одновременно. В этой ситуации необходимо суметь сопоставить ответ с относящимся к нему запросом.

Существуют два варианта реализации такого средства:

1. Поддерживать некоторую форму идентификатора запроса в API уровня приложения (т.е. в WSDL), в соответствии с которым может действовать сервис.
2. Использовать любую встроенную поддержку, предоставляемую инфраструктурой, для сопоставления запроса с ответом.

Несмотря на то, что первый вариант позволяет обеспечить не зависящий от протокола передачи способ сопоставления сообщений, он осуществляется за счет обработчиков протокола очереди, которым придется эффективно просматривать все сообщения в очереди для идентификации ответа. Выбор варианта 1 портит чистоту API приложения, так как каждый вызов метода должен поддерживать дополнительный параметр для обеспечения выполнения объединения. Обе формы организации очереди обеспечивают сопоставление путем включения элемента CorrelationID в свою версию сообщения из очереди. Протокол должен обрабатывать сопоставление сообщений (это решение может потребоваться пересмотреть при добавлении дополнительных протоколов передачи, и если решение изменяется, результатом становится добавление пользовательских заголовков в оболочку Soap для поддержки метода сопоставления). Данная обработка сопоставления должна выполняться внутри метода GetResponse() каждой реализации.

Uri ответа

Хотя инфраструктура передачи была разработана явно для поддержки нестандартных протоколов, она преимущественно сосредоточена на целевой конечной точке и предполагает единую точку контакта по доставке запроса и ответа. Когда дело касается формирования очереди, нужно быть в состоянии дать клиенту возможность описывать Uri запроса (конечная точка для попадания) и Uri ответа, который сервис конечной точки может эффективно возвращать с результатами вызова. Нужно быть в состоянии описывать очередь запроса и очередь ответа для цели запроса и цели ответа соответственно.

 

Рисунок 4. Объектная модель для MySoapClientProtocol

Лучшим подходом оказывается получение из SoapHttpClientProtocol и введение строгого интерфейса (ISoapClientProtocol) для контролирования управления новым членом данных, именуемым ResponseUrl ( назван так  для привнесения симметрии в пару Url запроса-ответа).

Полученный класс называется MySoapHttpClientProtocol. Введение данного класса требует, чтобы каждый генерируемый посредник получался из него, чтобы суметь получить доступ к новому свойству. Это потребует ручного повторного указания местоположения сопутствующего файла reference.cs.

Любые изменения, вносимые в указанный посредник, теряются во время восстановления посредника с помощью команды меню VS.NET "Обновить веб-ссылку" или при удалении веб-ссылки. Следует создавать резервную копию файла reference.cs после ручного внесения изменений в посредник, чтобы в дальнейшем их можно было снова применить, если, например, необходимо расширить исходное определение API веб-сервиса, и поэтому посредник требуется заново генерировать.

Примером использования информации об очереди ответов может служить реализация внутри метода GetResponse() обработчиков, ожидающих в соответствующей очереди ответов сообщения, соответствующего отправленному обработчиком запросу. Это обычно подразумевает использование специфичного для очередей API, такого как ReceiveByCorrelationID().

Способ задания Uri очереди

В разделе под названием "Определение интернета и других сетевых протоколов передачи для веб-сервисов" было сказано, что класс Uri является средством идентификации сервиса конечной точки с использованием синтаксиса URI. Синтаксис URI зависит от схемы протокола. Абсолютные URI записываются так:

<scheme>:<scheme-specific-part>

Синтаксис URI не требует, чтобы специфичная для схемы часть имела какую-либо общую структуру или набор семантики, являющийся общим среди всех URI. Однако подгруппа URI имеет общий синтаксис для обозначения иерархических отношений внутри пространства имен. Этот базовый синтаксис URI состоит из последовательности 4 основных составляющих:

<scheme(схема)>://<authority(полномочия)><path(путь)>?<query(запрос)>

Известные примеры включают в себя форматы http, такие как:

http://www.microsoft.com/webservices/testservice.asmx?wsdl

Чтобы использовать инфраструктуру подключаемых протоколов, необходимо преобразовать имена очередей в стиле MSMQ и MQ в данный синтаксис. Как показано в следующем разделе, такое преобразование выполняется с разной степенью сложности в зависимости от рассматриваемой реализации формирования очередей.

Обработка ошибок

В составе общей инфраструктуры подключаемых протоколов веб-сервисов .NET версии классов WebRequest и WebResponse выбрасывают системные исключения, такие как ArgumentException, и специфичные для протокола передачи исключения (обычно они имеют вид WebExceptions, выбрасываемых конкретным переопределенным методом GetResponse()). Нужно преобразовать связанные с очередью ошибки в эту структуру исключения, так как она хорошо скрывает различия между сферами отображения исключений MQ и MSMQ. Класс WebException получается из System.InvalidOperationException и содержит несколько расширений для поддержки подробной информации об ошибке:

 

Рисунок 5. Объектная модель WebException

Два протокола формирования очередей поддерживают данный подход, использующий свойство Состояние для отражения следующих перечисленных возвращаемых данных WebExceptionStatus:

Состояние

Описание

ConnectFailure

С удаленным сервисом невозможно соединиться на транспортном уровне. В нашем случае это значит, что открытие очереди запроса не удалось по причине, отличной от невозможности преобразовать имя очереди в ресурс.

NameResolutionFailure

Служба имен не может разрешить имя хоста. В нашем случае это значит, что очередь запроса (или менеджер очереди в случае MQ – смотрите ниже), указанную в Uri, не удалось преобразовать в ресурс.

ProtocolError

Ответ, полученный от сервера, был полным, но показал ошибку на уровне протокола. В нашем случае это значит, что действительный объект, производный от WebResponse, был возвращен, но что он содержит информацию, касающуюся ошибки в соответствующем протоколе передачи очереди. Данный объект предоставляется в составе WebException.

ReceiveFailure

Полный ответ не был получен от удаленного сервера. В нашем случае это значит, что считывание очереди ответа не удалось по причине, отличной от превышения лимита времени или невозможности преобразовать имя очереди в ресурс.

SendFailure

Полный запрос не был отправлен удаленному серверу. В нашем случае это значит, что поместить сообщение в очередь запроса не удалось после успешного открытия очереди (подключения).

Timeout

Ответ не был получен в течение периода времени ожидания, установленного для запроса. В нашем случае это значит, что запрос был успешно помещен в очередь запроса соответствующим транспортным протоколом, но соответствующий ему ответ не был идентифицирован в очереди ответа до истечения периода времени ожидания.

Эти коды состояний преобразуются из специальных кодов ошибок MSMQ / MQ в соответствующих специфичных для протоколов разделах ниже.

Специфичная для MSMQ реализация

Вся работа выполняется в переопределенном методе GetResponse().

Вызов функций MSMQ

Чтобы использовать MSMQ, сборку System.Messaging нужно включить в ссылки для проекта. Она предоставляет простую в применении объектную модель для взаимодействия с MSMQ, охватывающую подключение и управление локальными и удаленными очередями сообщений, а также отправку и прием сообщений посредством данных очередей.

Рисунок 6. System.Messaging

Формат имени очереди

Имеется несколько способов идентификации очереди MSMQ – по пути, по названию формата или по метке очереди. Название формата - собственное, уникальное обозначение очереди. Путь – удобное обозначение очереди, которую нужно преобразовывать в название формата с помощью службы каталога MSMQ. Формат спецификации пути очереди, понятный для MSMQ, имеет одну из следующих форм:

Тип очереди

Синтаксис пути

Общая очередь

MachineName\QueueName

Закрытая очередь

MachineName\Private$\QueueName, .\Private$\QueueName

Журнальная очередь

MachineName\QueueName\Journal$

Машинная журнальная очередь

MachineName\Journal$

Машинная очередь зависших сообщений

MachineName\Deadletter$

Машинная очередь не доставленных транзакционных сообщений

MachineName\XactDeadletter$

Нужно уметь выражать данный тип информации уровня пути в формате Uri, чтобы работать с инфраструктурой подключаемого протокола. К сожалению, это не очень хорошо подходит для имен путей MSMQ в данном случае. RFC 2396 под названием "Универсальные идентификаторы ресурса (URI): базовый синтаксис" говорит, что несколько символов запрещено использовать в спецификации URI, и поэтому они удаляются во время синтаксического разбора такими реализациями, как класс Uri .NET. Один из таких символов - "\", что неприятно с учетом формата имени очереди MSMQ. Из-за этого приходится описывать ресурсы очереди MSMQ с помощью альтернативных средств – в нашем случае используется "/" для разделения на части иерархии имени очереди. Следующее хранится на уровне Uri в подходящей форме:

msmq://./private$/QueueName

Хотя такая форма записи хорошо работает с Uri, она требует две вещи:
•    Разработчик должен не забывать выполнять данное изменение перед вызовом функции веб-сервиса с использованием этого транспортного протокола.
•    Реализация WebRequest должна преобразовывать данный формат, удобный для Uri, обратно в формат, пригодный для использования MSMQ.

Нужно описать очередь стандартным образом – так как она может использоваться в другом месте в более широком контексте приложения, например, считываться из конфигурационного файла, и т.д. Поэтому нужно предоставить некоторую помощь. Так как в идеале не требуется делать различие между вызывающим кодом, делающим запросы по MSMQ, и кодом, делающим запросы по MQ (или же HTTP, и т.д.), добавляется новый метод под названием FormatCustomUri() в интерфейс ISoapClientProtocol и в класс реализации MySoapHttpClientProtocol. Это позволяет вызывающим операторам посредника продолжать использовать удобные для MSMQ имена очередей, но обертывает простое удобное для Uri изменение перед передачей строки классу Uri:

// Получаем имя очереди (например, из конфигурационного файла)
string strMyQ = ".\\private$\\myq1";

// Создаем посредник сервиса
objHelloWorldService = new localhost.Service1();

// Устанавливаем URI
objHelloWorldService.Url = objHelloWorldService.FormatCustomUri("msmq://"
                           + strMyQ);



// Выполняем вызов
string[] arrRes = objHelloWorldService.HelloWorldArr("Simon");

Вызов FormatCustomUri() действует как проход для спецификаций HTTP и MQ – влияя только на форматы имени очереди MSMQ.

Обработка MSMQ

Данный раздел описывает внутреннее устройство метода GetResponse().
Сначала нужно поместить сообщение Soap в очередь MSMQ, описанную в Uri запроса. Для MSMQ формат Uri имени очереди является неудобным и требует преобразования в удобный для MSMQ формат:

// Создаем сообщение
System.Messaging.Message objMsg = new System.Messaging.Message();

// Перехватываем поток как тело сообщения
objMsg.BodyStream = m_RequestStream;

// Анализируем Uri MSMQ
string strQueueName = this.m_RequestUri.LocalPath.Replace("/",  "\\");
if (strQueueName.ToUpper().IndexOf("PRIVATE$") > = 0)
strQueueName = "." + strQueueName;

// Открываем очередь для записи
objQueue = GetQ(strQueueName);

// Отправляем сообщение в одиночной внутренней транзакции MSMQ
objQueue.Send(objMsg, MessageQueueTransactionType.Single);

// Освобождаем ресурсы в главной очереди
objQueue.Close();

Использование одиночного типа транзакции MSMQ гарантирует доставку одного (и только одного) сообщения в очередь. Для поддержки более сложных разнородных транзакций (например, включающих в себя проверку отправки сообщения в базу данных) нужно объединить данную базовую работу с корпоративными сервисами .NET для использования транзакций типа COM+. Так как используется специфичное для протокола передачи сопоставление, идентификатор сообщения MSMQ исходящего сообщения фиксируется и запоминается на данном этапе. Серверный процесс используется для предоставления данного идентификатора в качестве "идентификатора соответствия" (MSMQ и MQ поддерживают эту функцию), что облегчает поиск соответствующего ответа:

// Получаем идентификатор исходящего сообщения для использования в качестве 
// идентификатора соответствия,
// чтобы искать его в любых входящих сообщениях
string strCorrelationID = objMsg.Id;

После помещения сообщения в очередь ожидается ответ на сообщение в очереди, определяемой Uri ответа с совпадающим идентификатором соответствия. Ждем в течение периода, определяемого вызывающим оператором, способным влиять на период времени ожидания при создании посредника:

// Открываем очередь ответа MSMQ и ждем сообщения
try
{
   …

   // Ждем ответ с правильным идентификатором соответствия
   TimeSpan tWaitResp = new TimeSpan(0, 0, m_intTimeout / 1000);
   objQueue = GetQ(strQueueNameResp);       
   msg = objQueue.ReceiveByCorrelationId(strCorrelationID, tWaitResp);
}
catch (Exception e)
{
   …           
}

В результате ожидания в очереди ответа произойдет одно из двух событий:

1. Происходит ошибка (ошибка доступа к исходящей / входящей очереди, истечение срока ожидания ответа на сообщение от сервера, и т.д.). В данном случае ошибка, получаемая от управляемого поставщика MSMQ, перенаправляется непосредственно вызывающему оператору. Ссылаясь на таблицу общих кодов состояний WebException, представленную в разделе под именем "Обработка ошибок", ниже мы перечисляем конкретное сопоставление основных сценариев ошибок MSMQ с соответствующими кодами состояний WebException:

Состояние WebException

Запускающее событие

ConnectFailure

Не удалось открыть очередь запроса в MSMQWebRequest::GetQ()

NameResolutionFailure

Не удалось найти очередь запроса или ответа в MSMQWebRequest::GetQ()

SendFailure

Не удалось поместить сообщение в очередь запроса в MSMQWebRequest::GetResponse()

Timeout

Не удалось получить соответствующее сообщение из очереди ответа до истечения срока ожидания в MSMQWebRequest::GetResponse()

ReceiveFailure

Не удалось получить соответствующее сообщение из очереди ответа по не связанной с истечением срока ожидания причинеMSMQWebRequest::GetResponse()

ProtocolError

Любая другая ошибка в MSMQWebRequest/MSMQWebResponse. В данном случае создается MSMQWebResponse, описывающий точные данные ошибки.

В последнем случае создается MSMQWebResponse и передается в качестве параметра конструктору класса WebException – он также обертывает фактическое исключение, становящееся 'InnerException(внутреннее исключение)' из WebException:

catch( … )
{
   // Было ли данное исключение обработано каким-то образом?
   if ((e is System.Net.WebException) == false)
   {
      // Нет - Создаем MSMQWebResponse – это специфичная для протокола ошибка
      objMSMQWebResponse = new MSMQWebResponse(
            (into)QueueTransportErrors.UnexpectedFailure,
            e.Message,
            e.StackTrace);

       // Выбрасываем исключение
       throw new WebException(
            "MSMQ Pluggable Protocol failure.",
            e,
            WebExceptionStatus.ProtocolError,
            objMSMQWebResponse);
   }
}

2. Сообщение Soap принимается в виде потока, и экземпляр MSMQWebResponse создается и вставляется непосредственно в возвращаемый поток при подготовке к его обработке клиентской инфраструктурой веб-сервисов:

// Создание возвращаемого потока
objMSMQWebResponse = new MSMQWebResponse();
objMSMQWebResponse.SetDownloadStream(msg.BodyStream);           

return objMSMQWebResponse;

Реализация, специфичная для Websphere MQ

Как и в случае поддержки MSMQ, вся работа выполняется в переопределенном методе GetResponse().

Рисунок 7. Классы IBM ActiveX для MQ

Вызов функций MQ

Единственный доступный надежный вариант использования MQ в Websphere MQ 5.3 – обертка COM, предоставляемая с клиентом MQ (MQ не обязателен. Демонстрационное приложение может работать на любой комбинации протоколов передачи HTTP, MSMQ и MQ. Это настраивается в демо). Обертка COM должна быть включена в проект в виде межоперационной ссылки COM. Раздел дополнительных примечаний описывает альтернативу, повышающую общую производительность при использовании MQ, пока же будет достаточно обертки COM.

Полученная межоперационная сборка под названием Interop.MQAX200.dll не подписывается при добавлении таким образом, и поэтому любые сборки, использующие ее, нельзя будет помещать в глобальный кэш сборок, если не будет создана подписанная версия межоперационной сборки.

Подписание выполняется следующим образом:
•    определить местоположение COM DLL mqax200.dll, расположенного при установке по умолчанию в папке C:\Program Files\IBM\WebSphere MQ\bin
•    если строгое имя еще не используется в группе разработки, использовать sn –k <KEYFILENAME> для генерации файла, содержащего пару ключей строгого имени
•    запустить утилиту tlbimp на файле COM DLL, указав соответствующий файл ключей в качестве опции/keyfile<KEYFILENAME> и задав имя для выходной подписанной результирующей сборки с помощью/out<OUTFILENAME>

После выполнения этого и включения результирующей сборки в проект в качестве ссылки главный проект может быть подписан и развернут в GAC(глобальный кэш сборок) должным образом.

Доступ к имени очереди и его формат

Модель доступа к очереди MQ отличается от указанной модели в MSMQ. Тогда как иерархия объектов MSMQ довольно плоская по сути, MQ использует иерархическую структуру, задействующую 3-уровневый доступ, состоящий из:
•    получения сессии MQ
•    подключения к названному менеджеру очереди MQ через сессию
•    запроса доступа к названной очереди MQ через менеджер очереди

Имя очереди формируется из двух частей:

<имя менеджера очереди>/<имя очереди> ,например, QM_yoda/queue32

В отличие от MSMQ, имена объектов чувствительны к регистру. Есть несколько «глюков» при работе с комбинацией класса Uri (который, если вы помните, содержит имена хостов в нижнем регистре). При использовании среды разработки явным признаком проблемы именования является WebException с кодом состояния NameResolutionFailure, показывающим, что менеджер очереди или очередь не удается разрешить (подробнее смотрите ниже). Далее приводится несколько примеров доступа к очереди.

При подключении к очереди с именем "fred" в стандартном менеджере очереди используется следующая запись Uri:

objHelloWorldService.Url = "mq://fred"

При подключении к очереди с именем "bill" в менеджере очереди с именем "QM_john" используется следующая запись Uri:

objHelloWorldService.Url = "mq://QM_john/bill"

Эти примеры показывают виды обозначений очереди MQ. Для преобразования несвязанного Uri в комбинацию имени менеджера очереди / имени очереди предоставляется служебный метод с именем ResolveManagerAndQueueName(), используемый в передающем коде следующим образом:

// В какой менеджер очереди и имя очереди нужно направить сообщение?
ResolveManagerAndQueueName(
    this.m_RequestUri,
    out strQueueManagerName,
    out strQueueName);

Обработка MQ

Не считая явных семантических различий, описанных в прошлом разделе, главные различия между этой реализацией и версией MSMQ заключаются в синтаксисе при отправке сообщений и ожидании в очереди ответа. В то время как MSMQ поддерживает прямое использование потоков при отправке и приеме сообщений, для MQ такая поддержка отсутствует (по крайней мере, при использовании обертки ActiveX), и поэтому приходится применять отправку и прием массивов байтов.

Поскольку MQ не поддерживает автоматическое использование потоков в качестве тела сообщения, приходится преобразовывать поток в массив байтов, используемый затем для вставки исходящего сообщения:

// Подключается к упорядоченному потоку Soap как к массиву байтов
byte[] bytBody = new Byte[m_RequestStream.Length];
m_RequestStream.Read(bytBody, 0, bytBody.Length);

При наличии тела Soap и успешном разрешении комбинаций менеджер очереди/имя очереди запроса и ответа (показано выше), первое, что надо сделать, - получить сессию MQ. Это требуется сделать до выполнения работы по формированию очереди:

// Подключаемся к сессии
objMQSession = new MQAX200.MQSessionClass();
Далее пытаемся подключиться к менеджеру очереди и запрашиваем у него объект очереди с заданным именем:
// Получаем именованный менеджер очереди
objQueueManager = GetQManager(objMQSession, strQueueManagerName, true);

// Получаем именованную очередь от менеджера
objQueue = GetQ(objQueueManager, strQueueName, true);

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

// Устанавливаем сообщение для отправки
objMsg = (MQAX200.MQMessage)objMQSession.AccessMessage();
objPutMsgOpts =
          (MQAX200.MQPutMessageOptions)objMQSession.AccessPutMessageOptions();
// Нужно отправлять массив байтов, чтобы скрыть двоичные вложения, и т.д....
objMsg.Write(bytBody);

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

// Помещаем сообщение в очередь
objQueue.Put(objMsg, objPutMsgOpts);

[Примечание: При преобразовании в MQ, работающем на AS/400 и оперируемом в EBCDIC, сообщение должно явно указывать, что оно имело формат ASCII до отправки - objMsg.Format = "MQSTR " ;]

Так как используется специфичное для протокола передачи сопоставление, идентификатор сообщения MSMQ исходящего сообщения фиксируется и запоминается на этой стадии. Применяется серверный процесс для предоставления данного идентификатора в качестве "идентификатора соответствия" (MSMQ и MQ поддерживают данную функцию) для облегчения поиска соответствующего ответа:

// Получаем идентификатор исходящего сообщения для использования в качестве //идентификатора соответствия для поиска в любых входящих сообщениях
string strCorrelationID = msg.MessageId;

После того как сообщение помещено в очередь, ожидается ответ на сообщение в очереди, определенной Uri ответа с совпадающим индикатором соответствия. Ожидаем в течение периода, определенного вызывающим оператором, способным влиять на время ожидания при создании посредника:

try
{
    // Открываем очередь ответа MQ и ожидаем сообщения
    objQueueResponse = GetQ(objQueueManager, strQueueNameResp, false);
    objMsgResp = (MQAX200.MQMessage)objMQSession.AccessMessage();
    objMsgResp.CorrelationId = strCorrelationID;
    objGetMsgOpts = (MQAX200.MQGetMessageOptions)
    objMQSession.AccessGetMessageOptions();
    objGetMsgOpts.Options = (int)MQAX200.MQ.MQGMO_SYNCPOINT +
                            (int)MQAX200.MQ.MQGMO_WAIT;
    objGetMsgOpts.WaitInterval = this.Timeout;
    objQueueResponse.Get(
        objMsgResp,
        objGetMsgOpts,
        System.Reflection.Missing.Value);

    // Используем(т) подходящий считыватель
    strMsgRecv = objMsgResp.MessageData.ToString();
}
catch (Exception e)
{
     ...           
}

В результате ожидания в очереди ответа происходит одно из двух событий:

1. Происходит ошибка (ошибка доступа к исходящей / входящей очереди, истечение срока ожидания ответа на сообщение от сервера, и т.д.). В этом случае ошибка, полученная от управляемого поставщика MSMQ, перенаправляется непосредственно вызывающему оператору. Ссылаясь на таблицу общих кодов состояний WebException, представленную в разделе под именем "Обработка ошибок", ниже мы перечисляем конкретное сопоставление основных сценариев ошибок MSMQ с кодами состояний WebException:

Состояние WebException

Запускающее событие

ConnectFailure

Не удалось открыть менеджер очереди запроса в MQWebRequest::GetQManager() или не удалось открыть очередь запроса в MQWebRequest::GetQ()

NameResolutionFailure

Не удалось найти очередь запроса или ответа в MQWebRequest::GetQ()

SendFailure

Не удалось поместить сообщение в очередь запроса в MQWebRequest::GetResponse()

Timeout

Не удалось получить соответствующее сообщение из очереди ответа до истечения срока ожидания в MQWebRequest::GetResponse()

ReceiveFailure

Не удалось получить соответствующее сообщение из очереди ответа по не связанной с истечением срока ожидания причине вMQWebRequest::GetResponse()

ProtocolError

Любая другая ошибка вMQWebRequest / MQWebResponse. В данном случае создается MQWebResponse,описывающий точные данные ошибки.

2. Сообщение Soap принимается в виде массива байтов, преобразуется в поток, и экземпляр MQWebResponse создается и вставляется непосредственно в преобразованный поток, при подготовке к его преобразованию клиентской инфраструктурой веб-сервисов:

// Преобразуем результат в массив байтов
byte[] buf = new System.Text.UTF8Encoding().GetBytes(strMsgRecv);
MemoryStream stResponse = new MemoryStream();
stResponse.Write(buf, 0, buf.Length);

// Создается возвращаемый поток
objMQWebResponse = new MQWebResponse();
objMQWebResponse.SetDownloadStream(stResponse);           

return objMQWebResponse;

Подключение к упорядоченному потоку Soap

При реализации класса, производного от WebRequest, нужно предоставлять поток для использования сериализацией Soap. Перед отправкой потока запроса Soap любому ресурсу сети посредством одного из новых протоколов передачи сначала нужно получить доступ к этому упорядоченному потоку.

Введенный выше класс, производный от WebRequest, из которого получаются версии MSMQ и MQ, содержит член данных под именем m_RequestStream, имеющий базовый тип Stream, переносящий исходящий поток Soap. Инфраструктура будет вызывать переопределенный метод GetRequestStream() в подходящее время для создания и предоставления данного потока во время фазы исходящей доставки вызова веб-сервиса. Первая попытка будет использовать переменную члена типа MemoryStream.

Из пошагового исполнения потока кода следует, что в порождениях WebRequest нет прямого доступа к потоку запроса, пока не вызвано переопределение GetResponse()(в какой точке поток запроса уже был уничтожен – смотрите пример свойства Length в m_RequestStream):

Рисунок 8. Обращение к потоку запроса Soap

Изучение диаграммы последовательности UML, представленной выше в разборе примеров, подтверждает, что инфраструктура уже закрыла поток в точке, когда он нужен. Нужно придумать другой способ получения доступа к потоку до его уничтожения. Оказывается, что инфраструктура Microsoft вынуждена закрывать методом Close() предоставляемый вами поток, чтобы вытолкнуть данные в сеть – это стандартная модель программирования для WebRequests.

Есть два следующих варианта:
•    Подключение к потоку сразу после сериализации путем создания и использования SoapExtension и сопутствующего атрибута. Можно перехватить этап обработки SoapMessageStage.AfterSerialize для сохранения упорядоченного потока Soap. Это добавит дополнительные классы в набор, вызовет дополнительную переделку (повторно) генерируемого посредника и может породить проблемы при дальнейшей разработке каркаса для включения WSE.
•    Предоставить пользовательский поток и переопределить Close() для изменения поведения потоков – это эффективно откладывает фактическое закрытие потока до тех пор, пока не появится возможность получить доступ к нему. Это предпочтительный подход, так как он лучше подходит для каркаса.

Рисунок 9. MySoapStream

Предоставляется класс под именем MySoapStream, запрещающий фактическое закрытие потока и предоставляющий альтернативу под именем InternalClose(), фактически закрывающий нижележащий поток.

public override void Close() 
{
    // Не закрываем, а перематываем!
    m_Stream.Position = 0;
}

internal void InternalClose()
{
    m_Stream.Close();
}

Меняем реализацию метода GetRequestStream(), чтобы использовать новый тип потока:

public override Stream GetRequestStream()
{   
   if (m_RequestStream == null)
      m_RequestStream = new MySoapStream(new MemoryStream(), true, true, true);
   else
      throw new InvalidOperationException("Request stream already retrieved.");

   return m_RequestStream;       
}

Переработка обработчика MSMQ WebRequest в качестве примера (,) и слегка измененное переопределение GetResponse() (показана версия MSMQ, но применимы обе версии) выглядит так:

public override WebResponse GetResponse() 
{
    …

    // Создаем сообщение с использованием потока
    objMsg.BodyStream = m_RequestStream;
    objMsg.Recoverable = true;

    …

    // Открываем очередь для записи
    objQueue = GetQ(strQueueName);

    // Отправляем сообщение в одиночной внутренней транзакции MSMQ
    objQueue.Send(objMsg, MessageQueueTransactionType.Single);

    …

    // Закрываем поток Soap
    m_RequestStream.InternalClose();

    …
   
    // Ожидаем ответа

    …
}

В данном случае поток может использоваться напрямую в качестве тела сообщения MSMQ с помощью свойства BodyStream последнего. Несмотря на то, что отсутствует эквивалентная прямая связь потоков в MQ, требуется выполнить лишь еще один шаг для считывания потока в массив байтов, записываемый прямо в сообщение MQ.
После отправки сообщения поток закрывается с помощью метода InternalClose().

Подключение новых протоколов к сгенерированным классам посредника

Имея два новых обработчика транспортных протоколов, будем их использовать из класса посредника. Полученный посредник немного отличается от исходной заново сгенерированной формы:

public class Service1 : MySoapClientProtocol
{
    ...       
       
    // Отдельные методы API не изменяются по сравнению с их сгенерированной формой
    [System.Web.Services.Protocols.SoapDocumentMethodAttribute(...)]
    public string[] HelloWorldArr(string name)
    {
           object[] results = this.Invoke("HelloWorldArr", new object[] {name});
        return ((string[])(results[0]));
    }
   
    // Отдельные методы API не изменяются по сравнению с их сгенерированной формой

    ...
       
    // Переопределенные методы для направления сообщений Soap
    protected override WebRequest GetWebRequest(Uri uri)
    {
           // Передает полномочия общему генератору для новых протоколов
        return ProxyCommon.GetWebRequest(uri, this);
    }

    protected override WebResponse GetWebResponse(WebRequest webReq)
    {
        // Передает полномочия общему генератору для новых протоколов
        return ProxyCommon.GetWebResponse(webReq);
    }
}

Посредник теперь получается из MySoapClientProtocol, включая переопределения для вызовов GetWebRequest() и GetWebResponse(). Требуются лишь указанные изменения. Эти изменения будут теряться, и их придется снова применять в случае последующей повторной генерации посредника по любой причине.

Использование нового посредника из клиента

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

localhost.Service1 objHelloWorldService = null;
string strRequestUrl = "msmq://.\\private$\\myreq";
string strResponseUrl = "msmq://.\\private$\\myresp";

try
{
    // Создаем посредник сервиса
    objHelloWorldService = new localhost.Service1();

    // Устанавливаем очередь запроса с помощью Uri
    objHelloWorldService.Url
                  = objHelloWorldService.FormatCustomUri(strRequestUrl);

    // Устанавливаем другую очередь ответа, отличную от "<QUEUENAME>_resp"
    objHelloWorldService.ResponseUrl
                  = objHelloWorldService.FormatCustomUri(strResponseUrl);

    // И время ожидания
    objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;

    // Запускаем метод и распечатываем вывод
    Console.WriteLine(objHelloWorldService.HelloWorld("Simon"));
}
catch(Exception e)
{
    ...
}

Поддержка асинхронной доставки сообщений

Есть множество статей, описывающих, как использовать преимущества асинхронной поддержки в веб-сервисах (подробные данные смотрите в конце статьи), поэтому данная тема здесь подробно не разбирается. Достаточно сказать, что имеются три основных варианта реализации асинхронного вызова:
•    Опрос об окончании
•    Использование обработчиков ожидания
•    Использование обратных вызовов

Независимо от того, какой метод выбирается, все они основаны на доступе к маркеру, возвращаемому им после начала асинхронного вызова. Сгенерированные классы посредника имеют синхронные и асинхронные версии каждого вызова метода – асинхронные версии содержат два метода в форме:

public System.IAsyncResult BeginHelloWorldArr(
    string name,
    System.AsyncCallback callback,
    object asyncState)
{
    return this.BeginInvoke("HelloWorldArr", new object[] {name}, callback,
                           asyncState);
}

public string[] EndHelloWorldArr(System.IAsyncResult asyncResult)
{
    object[] results = this.EndInvoke(asyncResult);
    return ((string[])(results[0]));
}

Вызов метода BeginXXX сразу же возвращает маркер типа IAsyncResult, используемый для опроса или для ожидания окончания запроса.

Не будучи очень сложной, поддержка асинхронных запросов отлично инкапсулирована в сборке AsyncUIHelper, описанной в статье MSDN . Использование данной библиотеки означает, что транспортные протоколы очереди не должны реализовывать асинхронные функции для себя (пока).

Основная особенность поддержки – класс под именем Asynchronizer:

private Asynchronizer m_ssiReturnImage = null;

Вызывающий оператор метода использует асинхронизатор следующим образом, в данном примере сначала перехватывая основанный на делегировании обратный вызов, а затем вызывая стандартный метод ReturnImage():

// Создаем посредник сервиса
objHelloWorldService = new localhost.Service1();

// Устанавливаем URI
objHelloWorldService.Url = objHelloWorldService.FormatCustomUri(vstrURI);

// И время ожидания
objHelloWorldService.Timeout = vintTimeoutInSecs * 1000;

// Создаем новый асинхронный маркер
m_ssiReturnImage = new Asynchronizer(
                             new AsyncCallback(this.ReturnImageCallback),
   objHelloWorldService);

// Начинаем вызов метода
IAsyncResult ar = m_ssiReturnImage.BeginInvoke(
                new ReturnImageEventHandler(objHelloWorldService.ReturnImage),
                null);

Любые асинхронные ответы направляются обработчику события на основе делегата, для которого устанавливается тип возвращаемой переменной стандартной синхронной версии вызова асинхронизируемого метода:

protected delegate byte[] ReturnImageEventHandler();            

При этом ответы на вызов (в данном случае данные изображения) доступны через член данных в асинхронном маркере:

protected void ReturnImageCallback(IAsyncResult ar)
{
    localhost.Service1 objHelloWorldService
                                        = (localhost.Service1)ar.AsyncState;

    ...

    AsynchronizerResult asr = (AsynchronizerResult) ar;
    byte[] bytImage = (byte[])asr.SynchronizeInvoke.EndInvoke(ar);

    ...
}

Имитация серверной службы

До сих пор рассматривались средства, заставляющие сторону клиента правильно взаимодействовать с очередями запросов и ответов. Это бессмысленно, если нет некой формы сервиса, отбирающего сообщения и обрабатывающего их. В реальных решениях используются веб-сервисы, нацеленные на неоднородные среды, в которых клиент и сервер работают на разных платформах, а веб-сервисы объединяют две платформы.

Но из-за желания ограничить набор навыков, требуемых для усвоения статьи, выполнимой величиной на стороне сервера используется приложение .NET для обработки сообщений и выработки (пустых) ответов для тестирования приема сообщений клиентов. Сервис служит лишь контрапунктом для разработанного каркаса клиента, и поэтому сервис не делается надежным или масштабируемым – он лишь демонстрирует возможности клиента.

В описываемой не связанной с WSE сфере сервис может быть простым. Его задача – выполнять следующие действия:
•    Считывать конфигурационный файл, содержащий очереди MSMQ / MQ для ожидания
•    Зацикливать очереди, ожидающие сообщения, в течение короткого периода времени
•    Для любых точно идентифицированных сообщений вручную генерировать поток ответа Soap и помещать в очередь ответов для захвата клиентом

Логика содержится внутри одного класса BEService.

Он сначала считывает передаваемый файл app.config для всех параметров уровня приложения (то есть для настроек, заданных внутри узла <appsettings>):

// Считываем параметры конфигурации
NameValueCollection colSettings = ConfigurationSettings.AppSettings;
Каждая очередь, найденная в конфигурационном файле, добавляется в соответствующую область памяти:
string[] arrVals = colSettings.GetValues(strKeyName);

if (arrVals[0].ToLower().IndexOf("msmq://") == 0)
    arrMSMQQueuesToMonitor[intNumMSMQQueues++]
                    = arrVals[0].Replace("msmq://", string.Empty);
else
if (arrVals[0].ToLower().IndexOf("mq://") == 0)
    arrMQQueuesToMonitor[intNumMQQueues++] = arrVals[0].Replace("mq://",
                                                          string.Empty);

Очереди MSMQ и MQ извлекаются из набора, так как имеется задержка опроса (серверная служба в настоящий момент не использует пул потоков). Затем программа входит в следующий простой цикл:

// Ожидаем в нескольких очередях - несколько MSMQ и несколько MQ...
for (;;)
{
    // Сначала обрабатываются очереди MSMQ
    foreach (string strName in arrMSMQQueuesToMonitor)
    {
        WaitOnMSMQQ(strName, intPollDelay);
    }

    // Теперь обрабатываются очереди MQ
    foreach (string strName in arrMQQueuesToMonitor)
    {
        WaitOnMQQ(strName, intPollDelay);
    }
}

Реализация WaitOnMSMQ

Данный метод простой. Сначала вся работа инкапсулируется в блоке попытка-перехват, чтобы перехватить все исключения и отсеять безопасные, такие как истечение срока ожидания сообщения запроса:

try
{
    … // Здесь выполняется работа
}
catch(Exception e)
{
    // Игнорируем истечения срока ожидания, так как они означают лишь отсутствие
// сообщения очереди
    if (e.Message.IndexOf("Timeout for the requested operation has expired") < 0)
        TSLog("Exception caught in WSAltRouteBEFake[MSMQ]: " + e.Message +
                                                    e.StackTrace);
}

Сначала немного ждем:

// Нужно немного подождать
TimeSpan tWaitResp = new TimeSpan(0, 0, 0, 0, vintPollTimerDelay);
 
// Захватываем очередь запросов
MessageQueue objQueue = GetQ(vstrQueueName);       
 
// Ожидаем сообщение
Message objRequestMsg = objQueue.Receive(
    tWaitResp,
    MessageQueueTransactionType.Single);
 
// Закрываем очередь
objQueue.Close();

При условии получения сообщения, т.е. если не было выброшено никаких исключений, открывается сообщение, и содержащиеся в нем данные преобразуются в массив байтов во время отбора нескольких характерных свойств, таких как потенциальный идентификатор соответствия и имя очереди ответов:

// Получаем содержимое сообщения прямо из BodyStream
string strResp = string.Empty;
byte[] bufIn = new Byte[objRequestMsg.BodyStream.Length];
objRequestMsg.BodyStream.Position = 0;
objRequestMsg.BodyStream.Read(bufIn, 0, (int)msg.BodyStream.Length);
string strCorrelationId = objRequestMsg.Id;

// Закрываем поток
objRequestMsg.BodyStream.Close();

// Получаем очередь ответов – нужно брать стандартную, но
// надеяться, что клиент идентифицировал очередь для обратного вызова?
string strResponseQueue = vstrQueueName + "_resp";
if (objResponseMsg.ResponseQueue != null)
    strResponseQueue = objRequestMsg.ResponseQueue.QueueName;

Затем проверяем, с целью выяснить, является ли данное сообщение известным, и если да, строим сообщение ответа для него. Метод сопоставления или генерации сообщения ответа не особо разумны – они существуют лишь для обслуживания кода ответа клиента.

// Имитируем ответ на сообщение strResp = BuildFakeResponseFromBytes("MSMQ" , bufIn, strResponseQueue);

В случае идентификации запроса и успешной генерации ответа на него ответ отсылается обратно клиенту через очередь ответов с использованием идентификатора соответствия:

// Если имеется правильный ответ, используем его
if (strResp.Length > 0)
{
    // Получаем очередь ответа
    objQueue = GetQ(strResponseQueue);

    // Отправляем сообщение обратно
    MemoryStream stResp = new MemoryStream();
    stResp.Write(new UTF8Encoding().GetBytes(strResp), 0, strResp.Length);
    Message objMsg = new Message();
    objMsg.BodyStream = stResp;
    objMsg.Recoverable = true;
    objMsg.CorrelationId = strCorrelationId;
   
    // Отправляем сообщение в одиночной внутренней транзакции MSMQ
    objQueue.Send(objMsg, MessageQueueTransactionType.Single);

    // Освобождаем ресурсы в очереди ответов
    objQueue.Close();
}

Здесь снова используется свойство BodyStream сообщения MSMQ, поэтому содержимое строки ответа записывается в поток. Он снова отправляется в виде одиночной транзакции MSMQ, и затем убирается очередь ответов.

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

Как модернизация веб-сервисов (WSE) изменяет шаблон клиента и сервера

WSE – первоначальная реализация Microsoft некоторых из первых спецификаций веб-сервисов GXA 2-го поколения – защита WS, вложения WS (посредством DIME) и маршрутизация WS / передача WS. Он был выпущен в декабре 2002 года, после 4 месяцев бета-тестирования. Волна стандартизации продолжает инициативу по улучшению возможностей взаимодействия / совместимости в стеке веб-сервисов, и реализации данных конкретных стандартов формируют основу для приложений базового уровня для торговых партнеров, базирующихся в интернете.

Хотя ожидается, что Soap посредством транспортного протокола с очередями найдет применение в надежных сетях отдельной организации, это не отрицает вероятность того, что данные потребуется защищать при передаче между корпоративными приложениями (например, конфиденциальные данные, релевантные для расчета зарплаты).

Основной важный аспект WSE коренится в реализации спецификации защиты WS; то есть имеется в виду возможность делать следующее:
•    контролировать "действительное время существования" сообщения Soap,
•    генерировать "маркер имени пользователя" с сопутствующим паролем, идентифицирующим пользователя для проверки,
•    подписывать полезную нагрузку Soap маркером для обеспечения проверки на искажение,
•    шифровать полезную нагрузку тела Soap с помощью симметричного ключа (именуемого "разделеный секрет", так как серверная служба имеет доступ к тому же самому ключу),
•    защищать запросы и ответы Soap для создания сквозного решения.

Данная функциональность является подгруппой того, что WSE способен предоставлять. Не используемые здесь интересные возможности включают в себя использование двоичных маркеров доступа (которые могут содержать билеты Kerberos) и асимметричное шифрование с использованием сертификатов X509.

Так как используется WSE, и WSE реализует спецификацию вложений WS, можно попробовать расширить демонстрационное приложение для изучения возможности добавления двоичных вложений посредством поддержки DIME. Становится ясно, что поддержка DIME не придерживается стандартной модели обработки Soap – ожидается изменение ее реализации в течение следующих 12 месяцев.


Обзор WSE

WSE – механизм обработки данных, подвергающий (по инструкции) серии преобразований исходящие сообщения Soap, после их сериализации инфраструктурой веб-сервисов .NET, и, наоборот, подвергает дополнительным преобразованиям входящие сообщения Soap перед их десериализацией в объекты / параметры. Эти преобразования подразумевают добавление дополнительных заголовков Soap и иногда (как при шифровании) также изменения тела сообщения Soap.
WSE предоставляет относительно низкоуровневый API, подходящий для подключения и воздействия на работу клиента и сервера. Отличная статья, рассматривающая внутренние механизмы WSE, находится в . В следующих разделах перечислены основные моменты, образующие контекст изменений, производимых в клиенте и сервере.

Физическая реализация WSE 1.0 – единственная установленная в GAC сборка под именем Microsoft.Web.Services.dll, реализующая большинство из трех упомянутых выше спецификаций.

Архитектура конвейера фильтра WSE

Единственное важнейшее изменение по сравнению с бета-версией программного обеспечения (именуемого WSDK, выпущенного в августе 2002 года) в WSE 1.0 состоит в том, что функциональность, осуществляющая преобразования сообщений Soap, была перекомпонована в серию фильтров. Эти фильтры принадлежат  одному из двух видов. Выходные фильтры сосредоточены на обработке исходящего сообщения Soap, тогда как входные фильтры обрабатывают входящие сообщения Soap.

Входной фильтр

Выходной фильтр

Назначение

TraceInputFilter

TraceOutputFilter

Запись сообщения в журнал для отладки

SecurityInputFilter

SecurityOutputFilter

Поддержка аутентификации, подписи и шифрования

TimestampInputFilter

TimestampOutputFilter

Поддержка метки времени

ReferralInputFilter

ReferralOutputFilter

Динамические обновления направлений маршрутизации

RoutingInputFilter

RoutingOutputFilter

Маршрутизация сообщений

Каждый набор фильтров сгруппирован в конвейер входного или выходного фильтра, по умолчанию запускающий фильтры в следующем порядке (для потока от клиента к серверу):

Рисунок 11. Архитектура конвейера фильтра подробнее (поток клиент -> сервер)

Это гарантирует, что шифрование и подписание (защита) применяются относительно полезной нагрузки Soap, что все остальные модификации были завершены, и что защита данных выполняется непосредственно перед отправкой запроса, тогда как расшифровка и проверка подлинности подписи выполняются сразу после приема запроса и до выполнения любых чувствительных к данным операций.

Каждый конвейер производит действия над оболочкой Soap, чтобы выработать разное содержимое внутри данной оболочки.

Концепция конвейера в WSE является очень мощной, так как конвейерами можно управлять программно для удаления или переупорядочивания стадий, и новые пользовательские стадии конвейера могут вводиться на основе предоставляемых WSE базовых классов SoapOutputFilter и / или SoapInputFilter.

Объединение выполнения конвейера WSE входного и выходного фильтров со стандартными механизмами сериализации и доставки Soap управляется двумя разными способами:
•    Для клиентов веб-сервиса ASP.NET управление осуществляется путем наследования генерируемого класса посредника от нового базового класса посредника под именем Microsoft.Web.Services.WebServicesClientProtocol.
•    Для веб-сервисов ASP.NET было предоставлено новое серверное расширение Soap в форме Microsoft.Web.Services.WebServicesExtension.

Назначение нового базового класса посредника и серверного расширения – располагаться между протоколом передачи и модулями сериализации/десериализации Soap, перехватывая сообщения и строя / анализируя заголовки Soap по необходимости. В результате крайне вероятно, что при использовании, например, проверки подлинности подписи в веб-сервисе, в случае если данная проверка обнаружит искажение и сгенерирует соответствующую ошибку Soap, ни одна строка программного кода приложения не будет вызвана к моменту генерации ошибки. Такой прием хорошо изолирует код приложения от кода инфраструктуры WSE, что является привлекательной выгодой.

SoapWebRequest и SoapWebResponse в WSE

WSE сам использует инфраструктуру подключаемого протокола веб-сервисов .NET для предоставления двух новых коммуникационных классов, именуемых SoapWebRequest и SoapWebResponse. Как и в случае версий MSMQ и MQ, эти классы получены из System.Net.WebRequest и System.Net.WebResponse соответственно.

Класс SoapWebRequest разбирает на части поток запроса, содержащий стандартное сериализованное сообщение Soap, и помещает его части в экземпляр другого предоставляемого WSE класса под именем SoapEnvelope, производного от System.Xml.XmlDocument. Затем он запускает стандартный конвейер выходного фильтра, обрабатывающий содержимое оболочки, как описано в разделе выше.

В среде разработки важное значение имеет объединение основанных на очереди классов с новыми классами интернет-запроса/ответа Soap, и особенно перехват получаемого потока Soap для использования обработчиками протоколов.
Однако для серверной службы SoapWebRequest и SoapWebResponse не нужны, так как работа происходит вне клиента или инфраструктуры сервера ASP.NET, и приходится вручную выполнять обработку WSE.

SoapContext WSE для коммуникации

Рассматривалось функционирование WSE, но не разбиралось, как на данную обработку влияет разрабатываемый код приложения. Класс SoapContext – механизм, при помощи которого код приложения и код форматирования WSE не прямо взаимодействуют. Это осуществляется одним из двух способов, в зависимости от точки в процессе:
•    При построении заголовков Soap в ходе обработки исходящих сообщений приложение сначала  вставляет атрибуты SoapContext, становящиеся инструкциями, по которым работают фильтры, т.е. в данном случае атрибуты SoapContext формируют содержимое исходящего сообщения Soap.
•    При анализе заголовков в ходе обработки входящих сообщений WSE вставляет SoapContext в результаты фильтрации входящих сообщений перед передачей управления серверному приложению, т.е. при этом содержимое входящего сообщения Soap формирует настройку SoapContext.

Класс содержит несколько нужных атрибутов:

Рисунок 12. Контекст WSE Soap

Оболочка содержит XML, маркирующий "выполняемую работу" во время работы фильтров. Атрибут защиты – место для прикрепления информации ключа шифрования и информации подписи. Вложения хранят вложения DIME (больше информации об этом далее).

Клиент и сервер поддерживают предоставление SoapContext через класс SoapWebRequest в клиенте и через статические классы HttpSoapContext.RequestContext и HttpSoapContext.ResponseContext в вебсервисе ASP.NET. Клиент должен уметь управлять SoapContext в надежде, что он будет влиять на фильтры. Обычно серверная служба не размещается на площадке ASP.NET, и следует надеяться, что есть другой способ извлекать SoapContext в данной среде.

Архитектура SecurityProvider

Как объясняется выше, инфраструктура WSE основана на перехвате. и она отделяет проверку безопасности от программного кода приложения. Это поднимает интересный вопрос, так как передающая сторона генерирует "маркер пользователя", встраивая имя пользователя и связанный с ним пароль (хешированный), подписывая и симметрично шифруя тело Soap, и т.д., но необходимо знать имя пользователя / пароль и соответствующий ключ для расшифровки на целевом абонентском пункте, если полезная нагрузка Soap подлежит успешной обработке.

WSE предоставляет зацепки в обработке входящих сообщений для ее упрощения. В частности, он предоставляет два интерфейса –  IPasswordProvider и IDecryptionKeyProvider. Первый реализуется кодом приложения, чтобы предоставлять пароль конкретному пользователю, обычно из базы данных пользователей. Второй реализуется, чтобы предоставлять ключ расшифровки. Оба интерфейса вызываются в ходе обработки входящего сообщения WSE, если заголовки Soap показывают, что сообщение было подписано и / или зашифровано:

Рисунок 13. Архитектура поставщика средств обеспечения безопасности WSE

WSE позволяет разработчику писать код для этих двух зацепок и сообщать о нем среде выполнения WSE через информацию в соответствующем конфигурационном файле приложения. Существует три возможных вида указанного файла 3 в зависимости от среды:
•    Для веб-сервисов ASP.NET  файл web.config
•    Для клиентов ASP.NET - файл app.config
•    Для автономных программ – конфигурационный файл приложения

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

<microsoft.web.services>
  <security>
    <passwordProvider type="WSCommon.WSESecurityProvider, WSAltRouteBEFake" />
    <decryptionKeyProvider
                      type="WSCommon.WSESecurityProvider, WSAltRouteBEFake" />
  </security>
</microsoft.web.services>

Каждый вход показывает имя типа (класс) и сборку, в которой находится тип. При двусторонней защите (т.е. сообщения запроса и ответа Soap) одинаковые входы находятся на конце сервиса (используется при приеме запроса Soap) и на конце клиента (используется при приеме ответа Soap).

Инструмент задания параметров WSE 1.0 для .NET

Единственная проблема с гибкостью перехвата этих поставщиков в том, что очень просто ошибиться. Для этого Microsoft создал неподдерживаемое VS.NET расширение , упрощающее добавление параметров. После его установки появляется новая команда контекстного меню при щелчке правой кнопкой мыши по узлу на уровне проекта в Проводнике решения. Эта команда позволяет разработчикам активировать защиту WSE, затем задавать информацию о поставщике в диалоговом окне и вставлять ее в соответствующий конфигурационный файл. В данном случае указывается, что серверная служба содержит единственный класс с именем WSCommon.WSESecurityProvider, обрабатывающий разрешение пароля и ключа расшифровки.

Рисунок 14. Инструмент задания параметров WSE

Авторы считают, что инструмент работает только с WSE 1.0, поэтому, вероятно, не будет работать с будущими обновлениями WSE, но к тому времени вы хорошо усвоите параметры конфигурации.

Обновление каркаса клиента для использования WSE

Необходимо внести определенный ряд изменений в каркас, чтобы он мог обрабатывать запросы WSE и запросы, не относящиеся к WSE.

Добавление дополнительного класса в MySoapClientProtocol

Так как WSE требует получения посредника из Microsoft.Web.Services.WebServicesClientProtocol для вызова обработки конвейера фильтра, нужно внедрить в каркас дополнительный класс с именем MyWSESoapClientProtocol, поддерживающий свойство ResponseUrl:

Рисунок 15. Доступ к WSE из клиента

Изменения в генерируемом посреднике

Единственные изменения в коде посредника подразумевают перебазирование в MyWSESoapClientProtocol и небольшое изменение вызова переопределенного метода GetWebRequest() и удаление вызова переопределенного метода

GetWebResponse():
public class Service1 : MyWSESoapClientProtocol
{
    // Все вызовы методов API остаются неизменными

    …

    // Переопределенные части
    protected override WebRequest GetWebRequest(Uri uri)
    {
        // Регистрируем схему если нужно
        ProxyCommon.RegisterRelevantScheme(uri, this);

        // Выполняем стандартные операции с использованием SoapWebRequest,
        // объединяемого с классами WebRequest / WebResponse MSMQ / MQ
        // когда нужно.
        return base.GetWebRequest(uri);
    }
}

Это работает благодаря SoapWebRequest, перехватывающему код обработки фильтра WSE, и затем передающему полученную оболочку Soap выбранному протоколу передачи дальше по линии для передачи. Остальной код каркаса (MSMQWebRequest, MQWebRequest, и т.д.) не меняется.

Изменения в вызывающем коде

Приложение должно устанавливать объект SoapContext, связанный с выполняемым вызовом. Это достигается путем добавления в клиент нового закрытого метода, добавляющего основную информацию в SoapContext. Затем вызывающий оператор запускает вызов следующим образом:

// Создаем посредник сервиса
objHelloWorldService = new localhostWSE.Service1();

// Устанавливаем URI
objHelloWorldService.Url = objHelloWorldService.FormatCustomUri(vstrURI);

// Пополняем контекст запроса средствами WSE посредством универсального алгоритма
// Он включает в себя:
//
//    * Время жизни 60с
//    * Данные имени пользователя и пароля
//    * Шифрование и подписывание
//
// Для достижения этого нужно добавить атрибуты в изначально пустой
// SoapContext!!
ProxyCommonWSE.SetupSOAPRequestContext(objHelloWorldService.RequestSoapContext);

// Выполняем метод и получаем возвращаемый объект
string strResult = objHelloWorldService.HelloWorld("Simon");

// Убеждаемся, что запрос SOAP был принят с правильными учетными данными
// и подписью посредством универсального алгоритма – путем проведения
// опроса сгенерированного SoapContext!!
ProxyCommonWSE.ValidateCredentials(objHelloWorldService.ResponseSoapContext);

Чтобы дать представление о том, как заполняется объект SoapContext, ниже приводится часть закрытого метода

SetupSOAPRequestContext():


// Получаем симметричный ключ шифрования, с помощью которого шифруется тело Soap
EncryptionKey encKey = GetEncryptionKey();



// Создаем маркер пользователя на основе счётчика времени и статическое имя
// пользователя
UsernameToken userToken = new UsernameToken(
                strUserName,
                CalculatePasswordForProxyUser(strUserName),
                PasswordOption.SendHashed);

// Добавляем маркер пользователя в контекст Soap
vobjSoapContext.Security.Tokens.Add(userToken);

// Запрашиваем подпись для сообщения (на основе маркера пользователя)
vobjSoapContext.Security.Elements.Add(
             new Microsoft.Web.Services.Security.Signature(userToken));

// Просим зашифровать тело Soap (с помощью отдельного симметричного ключа)
vobjSoapContext.Security.Elements.Add(
            new Microsoft.Web.Services.Security.EncryptedData(encKey));

// Устанавливаем окончание срока действия сообщения SOAP
vobjSoapContext.Timestamp.Ttl = SOAP_MSG_EXPIRY_TIME;    // 60с

Основным преимуществом является возможность отправлять 160-битный хэш пароля SHA-1 (необратимый). Хэш сравнивается с вычисленной версией на удаленной стороне, после того как зацепка SecurityProvider была вызвана на той стороне. Подписание и шифрование запрашиваются с использованием разных ключей, и срок действия сообщения устанавливается в SOAP_MSG_EXPIRY_TIME (60 секунд).

Нужно соблюдать осторожность при шифровании и / или подписании сообщения Soap. Можно задавать разную степень разбиения, с которой должно защищаться сообщение – от всего сообщения вплоть до отдельных элементов сообщения Soap. Хотя здесь не используются средства маршрутизации в WSE, читатель должен сознавать, что нужно соблюдать осторожность при защите сообщения, проходящего через промежуточные узлы по пути к конечному пункту назначения.

Причина в том, что модель обработки Soap позволяет удалять заголовки Soap на промежуточных стадиях. Поэтому, например, если все сообщение (включая заголовки) подписывается, и затем передается из обрабатывающего узла A к узлу C через узел B, есть вероятность, что узел B случайно сделает сообщение недействительным путем удаления заголовка, что вызовет неудачную проверку подлинности подписи в конечном пункте назначения.

Наблюдается странность при запуске посредника, полученного из класса WebServicesClientProtocol (через MyWSESoapClientProtocol) в сценарии, когда ничего не устанавливается в объекте SoapContext перед выполнением вызова. В этом случае следовало бы ожидать, что приложение-получатель без поддержки WSE должно суметь успешно использовать сообщение Soap. Однако, дело обстоит не так. В настоящее время замечены две проблемы, основанные на странном поведении заголовка:
•    Вы получаете исключение "Путь не содержит элемент <действие>" при запуске вызова любого метода веб-сервисов. По-видимому, это происходит только в случае пользовательского протокола передачи – стандартный протокол передачи HTTP внутренне справляется с данным глюком.
•    При установке пути явно посредством заполнения направления маршрутизации чем-то вроде vobjSoapContext.Path.Action = string.Empty предполагается, что больше ничего не будет добавлено в сообщение Soap, и что поэтому сервер не станет запускать набор инструментов приведения в соответствие с WSE, и, следовательно, сообщения будут успешно использоваться. Однако дело обстоит не так. Наряду с данными маршрутизации, добавляется информация заголовка метки времени, и серверные службы Java странно реагируют на это и на указанные данные маршрутизации, преимущественно на основании атрибута soap:mustUnderstand="1", вставленного в часть Soap, обозначающего данные маршрутизации: этот атрибут говорит приемнику “прервать обработку”, если он не может понять смысл конкретного заголовка, с которым связан этот атрибут.

Из-за указанных глюков вместо получения посредника из единственного класса (MyWSESoapClientProtocol) и использования наличия или отсутствия вызовов метода SetupSOAPRequestContext()для управления защитой сообщений Soap сейчас приходится создавать две версии посредника: первая получается из MyWSESoapClientProtocol, а вторая получается из MySoapClientProtocol. Нужно знать, когда использовать каждую версию (преимущественно) во время разработки. Эти недостатки будут устранены по мере развития стандартов.

На флаг soap:mustUnderstand может воздействовать отправитель сообщения, что позволяет обойти часть проблем.


Обновление серверной службы для использования WSE для защищенной связи

Сервис является всего лишь выполняемым файлом, отбирающим сообщения из очереди, он существует вне контейнера, предоставляемого веб-сервисами ASP.NET, и поэтому нужно использовать средства неформатированных конвейеров WSE для запуска конвейера входного фильтра при проверке подлинности подписей и расшифровке содержимого с целью добраться до полезной нагрузки. С другой стороны, так как поддерживается двусторонняя защита, также нужно запускать конвейер выходного фильтра, чтобы сервисы шифрования и подписывания активизировались перед возвратом ответа Soap (по сути, вся работа, бесплатно выполняемая в традиционно размещенном на HTTP веб-сервисе ASP.NET).

Подготовка SecurityProvider

Нужно установить значения конфигурации, рассмотренные ранее в разделе "Архитектура SecurityProvider". Они направляют WSE в нужное место при запрашивании пароля пользователя и симметричного ключа для расшифровки.

Вызов конвейера входящего сообщения для запросов

При условии, что сообщение получено, т.е. отсутствуют выброшенные исключение, открывается сообщение, и содержащиеся в нем данные помещаются в массив байтов, в то же время отбираются несколько основных атрибутов, таких как потенциальный идентификатор соответствия и имя очереди ответов:

// Получаем содержимое сообщения прямо из BodyStream
string strResp = string.Empty;
byte[] bufIn = new Byte[objRequestMsg.BodyStream.Length];
objRequestMsg.BodyStream.Position = 0;
objRequestMsg.BodyStream.Read(bufIn, 0, (int) objRequestMsg.BodyStream.Length);
string strCorrelationId = objRequestMsg.Id;

// Обрабатываем через WSE – это может быть сквозной операцией,
// если WSE не использовался для создания потока
               
// Нужно выяснить, основан поток на DIME или нет
// Для этого нужно выяснить, удается ли прочитать поток
// как поток DIME – оболочка создается в любом случае
SoapEnvelope env = InputStreamToEnvelope(objRequestMsg.BodyStream);

// Закрываем поток
objRequestMsg.BodyStream.Close();

// Создаем конвейер – с набором стандартных фильтров для ввода и вывода
// (т.е. стандартное поведение не переопределяется)
Pipeline objWSEPipe = new Pipeline();
               
// Запускаем стандартный входной конвейер
objWSEPipe.ProcessInputMessage(env);

// Извлекаем полученную оболочку
bufIn = new UTF8Encoding().GetBytes(env.OuterXml);

// Получаем очередь ответов – нужно брать стандартную, но
// надеяться, что клиент передал очередь для обратного вызова
string strResponseQueue = vstrQueueName + "_resp";
if (objResponseMsg.ResponseQueue != null)
    strResponseQueue = objRequestMsg.ResponseQueue.QueueName;

Выделенный полужирным шрифтом текст показывает формирование объекта SoapEnvelope из потока входящего сообщения. После получения данной оболочки (подробней о способе ее получения далее) создается конвейер WSE и запускаются стандартные входные фильтры на оболочке. Содержимое входного конвейера не имеет значения (так как маршрутизация не используется, можно программно удалить фильтры маршрутизации и направления из входного конвейера, что немного повышает скорость запуска конвейера).

Вызов ProcessInputMessage() является неделимой операцией, приводящей к записи чистой текстовой версии сообщения Soap в переменную env. В качестве побочного эффекта запуска свойство Context в объекте оболочки заполняется расшифровкой входящего сообщения Soap.

Поддержка SecurityProvider автоматически подключается к процессу в ходе запуска конвейера, если заголовки шифрования или подписи обнаруживаются во входящем сообщении Soap.

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

Вызов конвейера исходящего сообщения для ответов

После того как сообщение запроса было согласовано и сообщение ответа сгенерировано, нужно решить, защищать ли сообщение ответа. Синтезированное свойство env.Context показывает, участвовал ли WSE в формировании сообщения запроса. Это свойство – экземпляр класса SoapContext, рассмотренного выше:

// Выясняем, участвовал ли WSE...
if (vobjSoapContext != null)
{
    if (vobjSoapContext.Attachments.Count > 0 ||
         vobjSoapContext.Security.Elements.Count > 0 ||
        vobjSoapContext.Security.Tokens.Count > 0)
          strWSEIsInvolved = " {WSE}";
}

Поддерживаются двусторонние защищенные сообщения. Поэтому вышеприведенного алгоритма достаточно для решения, защищать ли сообщение ответа:

// Нужно ли применять WSE к этому сообщению ответа перед его передачей
// протоколу передачи? Это нужно делать, если имеется сообщение ответа
// для обработки, и запрос был обработан WSE
if (strResp.Length
> 0  && strWSEIsInvolved.Length
    >  0)
{
    // Да. Обрабатываем с помощью WSE

    // Создаем оболочку
    SoapEnvelope env = new SoapEnvelope();

    // Вставляем базовую информацию Soap в оболочку
    env.LoadXml(strResp);

    // Пополняем контекст запроса средствами WSE
    // Они включают в себя:
    //
    //    * Время жизни 60с
    //    * Данные имени пользователя и пароля
    //    * Шифрование и подписывание
    // Добавляем экземпляр зашифрованных данных в ответ SOAP.
    WSCommon.SetupSOAPRequestContext(env.Context);

    // Создаем конвейер – со стандартным набором фильтров для ввода и вывода
    // (т.е. стандартное поведение не переопределяется)
    Pipeline objWSEPipe = new Pipeline();

    // Запускаем стандартный выходной конвейер
    objWSEPipe.ProcessOutputMessage(env);

    // Извлекаем полученную оболочку
    strResp = env.OuterXml;
}

Свойство env.Context заполняется перед запуском выходного конвейера. Используется копия того же самого алгоритма SetupSOAPRequestContext(), описанного выше в разделе "Изменения в вызывающем коде", используемого стороной клиента.

Обновление серверной службы для использования WSE для двоичных вложений

Можно использовать WSE для отправки и приема двоичных вложений, которые не нужно шифровать в виде base64Binary внутри оболочки Soap. Во время чтения входящих сообщений Soap нужно искать признаки приема одного или более сообщений DIME.

DIME – спецификация, описывающая возможность переносить двоичное содержимое вне оболочки Soap и обращаться к ней изнутри. Это имеет плюсы и минусы – двоичные данные не шифруются и поэтому имеют меньший размер, чем эквивалентное представление веб-сервисами параметра API, например, типа byte[]. Однако перенос вне оболочки Soap порождает две главные проблемы: данные нельзя выразить в виде параметра или описать в WSDL, и хуже того, невозможно применять такую же модель обработки Soap (включая средства подписания и шифрования) к содержимому DIME. Пока придется смириться с такими ограничениями, но необходимо  следить за этой областью.

[Примечание: Сейчас выполняется работа по объединению метода работы DIME с требованием наличия единой модели обработки Soap. Ожидается, что двоичные вложения будут возвращаться назад в оболочке Soap, где они будут подчиняться тем же правилам, что и другие элементы. Ожидается, что они сохранят свой эффективный формат без шифрования Base64 несмотря на это изменение.]

Обработка DIME реализуется полностью независимо от обработки фильтра WSE (потому что модель обработки DIME полностью отличается от стандартной модели Soap, предназначенной для выполнения действий над содержимым оболочки Soap), поэтому требуются дополнительные усилия для правильного заполнения свойства набора SoapContext.Attachments. Это осуществляет метод InputStreamToEnvelope(), обойденный молчанием в разделе "Вызов конвейера входящего сообщения для запросов":

// Примечание – некоторая часть кода пропущена для уменьшения размера
SoapEnvelope env = new SoapEnvelope();

try
{
   // Создаем считыватель для сообщения DIME
   DimeReader dr = new DimeReader(vstInputMessage);

   // Пытаемся прочитать запись, содержащую сообщение SOAP. Если это не удается с
   // ошибкой версии DIME, скорее всего, поток не является DIME
   DimeRecord rec = dr.ReadRecord();

   // Читаем сообщение Soap из записи DIME
   env.Load(rec.BodyStream);

   // Добавляем все вложения, обнаруженные в этом потоке, в контекст Soap
   while (rec != null)
   {
      // Читаем следующую запись
      rec = dr.ReadRecord();

      // Проверяем, прочиталась ли запись
      if (rec != null)
      {
         // Извлекаем из записи двоичные данные, представляющие ее
         BinaryReader stream = new BinaryReader(rec.BodyStream);
         …
         // Сохраняем двоичные данные в новый поток
         …
         stNew.Write(bytAttachmentItem, 0, bytAttachmentItem.Length);

         // ... этот поток прикрепляется к контексту Soap
         DimeAttachment objBin = new DimeAttachment(
                rec.Id, rec.Type, rec.TypeFormat, stNew);
         env.Context.Attachments.Add(objBin);
      }
   }
}
catch(Exception)
{
   // Если здесь выдается исключение, поток не является DIME

   // Теперь загружаем весь поток в массив байтов
   byte[] bufIn = new Byte[vstInputMessage.Length];
   vstInputMessage.Read(bufIn, 0, (int)vstInputMessage.Length);

   // ...массив байтов заключаем в оболочку Soap
   env.LoadXml(new UTF8Encoding().GetString(bufIn));
}

return env;

После завершения вышеуказанной загрузки серверный код реагирует на содержимое свойства набора SoapContext.Attachments следующим образом:

// Ищем вложение DIME - должно быть только одно
DimeAttachmentCollection colAttachments = vobjSoapContext.Attachments;
   
// Найдены ли какие-либо вложения?
if (colAttachments.Count >  0)
{
    // Получаем только первое вложение – это всего лишь демо!
    // Извлекаем из вложения двоичные данные, представляющие его
    BinaryReader stream = new BinaryReader(colAttachments[0].Stream);
    byte[] bytAttachmentItem = new byte[stream.BaseStream.Length];
    stream.Read(bytAttachmentItem, 0, bytAttachmentItem.Length);
    stream.BaseStream.Close();
    stream.Close();

    // Записываем двоичные данные в файл – знаем, что это gif, но можем
    // проверить тип, как описано в объекте DimeAttachment.
    FileStream fs = new FileStream(vstrLocOfImages + "SendDIMEImage" +
                        vstrScheme +
                        strWSEIsInvolved + ".gif",
                        FileMode.Create);
    fs.Write(bytAttachmentItem, 0, bytAttachmentItem.Length);
    fs.Close();
}

Сводка опций клиента с WSE или без WSE

Таблица разъясняет требования, предъявляемые к генерируемому посреднику и его вызывающим операторам для различных сценариев, использующих основанные на очередях протоколы передачи, подробно описанные выше.

Сценарий

Требования к генерируемому посреднику

Требования к вызывающему оператору посредника

Без WSE

  • Получен из MySoapClientProtocol
  • Реализует переопределенный GetWebRequest()и GetWebResponse()
  • При желании устанавливает свойство ResponseUrl (по умолчанию Url + "_resp")

С WSE

  • Получен из MyWSESoapClientProtocol
  • Implement overridden GetWebRequest() only
  • При желании устанавливает свойство ResponseUrl (по умолчанию Url + "_resp")
  • Устанавливает свойство SoapContext с помощью SetupSOAPRequestContext()
  • Устанавливает конфигурационный файл, добавляя в него данные о SecurityProvider

Параллельность в формировании очередей

Описанное ниже пробное приложение было протестировано в режиме "несколько клиентов – один сервер". В этом режиме клиенты помещают сообщения в очередь "одиночного запроса" одновременно и ожидают ответа на разных "очередях ответа" одновременно. Цель – выделить все проблемы в идентификации очередей и сообщений и доступе при наличии нескольких пользователей.

При первом тестировании протокол передачи MQ работал безупречно при наличии до 30 клиентов, а протокол передачи MSMQ регулярно выбрасывал исключения вида:

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

Причина в том, что API ReceiveByCorrelationID()не транзакционный, и поэтому в момент, когда он берет поток активности для идентификации сообщения по идентификатору соответствия, указатель, помечающий сообщение, может стать недействительным. Наблюдение показало, что при наличии 20 процессов, каждый из которых ожидает 100 связанных сообщений на одной и той же очереди MSMQ, вышеприведенное исключение выбрасывается в среднем 2,7 раз за прогон (за 2000 считываний сообщений) – процент ошибок 0,135%.

Если такая ошибка происходит, прием нужно повторять. Поэтому в коде присутствует цикл.

Упаковка каркаса

Классы, описанные в статье, образующие каркас, упакованы в сборку под именем WSQTransports.dll:

Рисунок 16. Упаковка WSQTransports

Крайние левые типы обеспечивают поддержку для дополнительных средств, таких как ResponseUrl. Другие типы образуют реализацию основанных на очередях подключаемых протоколов. Сборка подписана, поэтому она пригодна для GAC.


Пробное приложение

Базовая демонстрационная программа показывает взаимодействие двух консольных приложений – клиента и сервера – обменивающихся сообщениями с помощью разработанного выше каркаса.

Демо показывает передачу незашифрованных и зашифрованных WSE сообщений Soap на основе различных стандартных типов данных и комбинаций DIME API. Несмотря на то, что демо простое, оно хорошо иллюстрирует различные принципы.

Рисунок 17. Демо, выполняющее двух клиентов и одну серверную службу MQ / MSMQ

Демо показывает серверную службу, обрабатывающую входящие сообщения Soap в очередях MSMQ и MQ и возвращаемые ответы. Обработка нетривиальных типов данных (вещественные сериализованные пользовательские объекты) также включена в демо. Снимок экрана показывает обработку составных типов на основе произвольного класса под именем "продукт". Он показывает сериализацию и передачу массивов «продуктов» между сервером и клиентом. На снимке экрана выше два клиента выполняют вызовы Soap на очередях и через HTTP.

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

Знакомство с приложением

Следующая диаграмма знакомит с отдельными частями пробного приложения и показывает их компоновку в модели клиент-сервер:

Рисунок 18. Результаты статьи

Существует два веб-сервиса ASP.NET (один с поддержкой WSE), поддерживающих базовый API, состоящий из:
•    вариантов HelloWorld, возвращающих строки и массивы строк
•    операций с двоичными данными – отправка и прием изображений
•    сериализации составных типов данных – ProductService, обслуживающий экземпляры продукта
•    [для версии сервиса с WSE] Отправка изображений с использованием поддержки DIME в WSE

Виртуальные каталоги IIS для этих сервисов называются WSAltRoute и WSAltRouteWSE.

Холостой сервис (WSAltRouteBEFake.exe) существует в области сервера – он функционально работает, подобно вышеупомянутым веб-сервисам ASP.NET, но реагирует на сообщения, принимаемые в очередях, а не через HTTP.
Код каркаса, реализующий подключаемые протоколы с формированием очередей, хранится в единственной сборке (WSQTransports.dll), чтобы его можно было использовать во множестве проектов.

Каркас управляется с помощью пробного консольного приложения (WSAltRouteTest.exe), тестирующего разные виды API через разные протоколы передачи.

Поддержка асинхронной работы (не показана) обеспечивается сборкой под именем AsyncHelper.dll.

Очереди MSMQ и MQ конфигурируются с помощью инструмента под названием WSQSetup.exe (не показан).

Установка приложения

zip архив содержит несколько проектов, описанных в разделе ниже. Основные действия при развертывании следующие:
•    Убедитесь, что требуемые ресурсы (особенно MQ и WSE) установлены
•    Разархивируйте файл, сохранив структуру папок
•    Найдите папку \Bin
•    Запустите исполняемый файл WSQSetup.exe для создания соответствующих очередей. Следуйте подсказкам на экране.
•    Найдите подпапку \Client
•    Отредактируйте файл WSAltRouteTest.exe.config, проверьте и внесите любые изменения.

Список элементов следующий:

Элемент

Описание

NoTrace

Если “Истина”, не делает дамп трассировки стека при возникновении исключения

EnableHTTPCalls

Если “Истина”, делает вызовы API через протокол передачи HTTP

EnableMSMQCalls

Если “Истина”, делает вызовы API через протокол передачи MSMQ

EnableMQCalls

Если “Истина”, делает вызовы API через протокол передачи MQ

HTTPUriNonWSE

Конечная точка веб-сервиса WSAltRoute

HTTPUriWSE

Конечная точка веб-сервисаWSAltRouteWSE

MSMQRequestQ

Конечная точка очереди запросов MSMQ

MSMQResponseQ

Конечная точка очереди ответов MSMQ

MQRequestQ

Конечная точка очереди запросов MQ

MQResponseQ

Конечная точка очереди ответов MQ

LocationOfImages

Путь для чтения и записи изображений

•    Например, если вы не хотите работать через HTTP, установите значение EnableHTTPCalls в “Ложь”.
•    Переместите папку \Bin
•    Найдите подпапку \Server
•    Отредактируйте файл WSAltRouteBEFake.exe.config, проверьте и внесите любые изменения.

Список элементов следующий:

Элемент

Описание

NoTrace

Если “Истина”, не делает дамп трассировки стека при возникновении исключения

QueueToMonitor1

Имя 1-й очереди для отслеживания на сообщения Soap

QueueToMonitor2

Имя 2-й очереди для отслеживания на сообщения Soap

QueueToMonitorX

Имя X-й для отслеживания на сообщения Soap

PollDelayTime

Задержка приема в мс.

LocationOfImages

Путь для чтения и записи изображений

•    Например, если вы хотите добавить очередь для отслеживания, добавьте QueueToMonitor3 и установите соответствующее значение в имени Uri очереди.
•    Если хотите протестировать протокол передачи HTTP:

o    найдите папку \WebServices
o    скопируйте две подпапки (WSAltRoute и WSAltRouteWSE) в вашу главную папку веб-сайта (обычно c:\inetpub\wwwroot).
o    Для первой папки создайте новый виртуальный каталог IIS под именем WSAltRoute для стандартного веб-сайта (порт 80), указывающий на целевую папку WSAltRoute.
o    Для второй папки создайте новый виртуальный каталог IIS под именем WSAltRouteWSE для стандартного веб-сайта (порт 80), указывающий на целевую папку WSAltRouteWSE.

•    Если хотите протестировать протокол передачи MSMQ:

o    Убедитесь, что очереди запросов и ответов MSMQ, определенные в WSAltRouteTest.exe.config клиентского приложения, существуют. Используйте средство управления компьютером Windows 2000 или Windows XP для проверки этого.

•    Если хотите протестировать протокол передачи MQ:

o    Убедитесь, что очереди запросов и ответов MQ, определенные в WSAltRouteTest.exe.config клиентского приложения, существуют. Используйте инструмент ‘Проводник’ Websphere MQ для проверки этого.

•    Переместите папку \Bin
•    Найдите подпапку \Images
•    Скопируйте два файла .gif, CD.gif и Help.gif в папку, заданную в WSAltRouteTest.exe.config клиента и WSAltRouteBEFake.exe.config сервера под ключевым именем LocationOfImages.
•    Чтобы запустить демо, сначала запустите серверный процесс WSAltRouteBEFake.exe, следя за правильным появлением пускового сообщения. Затем запустите клиентский процесс WSAltRouteTest.exe. После этого активность должна отобразиться в обеих консолях в течение следующих нескольких секунд, по мере того как различные API веб-сервиса выполняются через выбранные протоколы передачи. Не должно появляться никаких исключений.
•    Если исключения появляются, могут показываться сообщения, содержащиеся в исключении. Если это имеет место, можно включить трассировку стека путем изменения значения NoTrace в соответствующем конфигурационном файле приложения (обычно клиентского) на “Ложь”.

Компоновка приложения

Решение состоит из следующих проектов:

Имя проекта

Назначение

WSAltRouteTest

Программа клиента, тестирующая новые протоколы передачи.

WSAltRouteBEFake

Холостая программа сервера, отвечающая на запросы.

WSQTransports

Код, поддерживающий подключаемый транспортный каркас и реализации протоколов с формированием очередей.

AsyncHelper

Асинхронная поддержка бизнес-объектов из статьи MSDN

WSAltRoute

Веб-сервис ASP.NET сконфигурирован без WSE

WSAltRouteWSE

Веб-сервис ASP.NET сконфигурирован для работы с WSE

WSQSetup

Программа настройки для очередей – запускайте ее после установки MSMQ (и по желанию MQ), но до запуска демо.

WSSetupMQ

Поддержка создания очередей MQ – требует среду выполнения VB6

Файл решения должен загружаться мгновенно. Вам может понадобиться заново указать местоположение файлов .webinfo для двух ориентированных на HTTP веб-сервисов (по умолчанию занимающих порт 80), но в остальном проекты должны компилироваться прямо в подпапках \Bin\Client и \Bin\Server (при условии, что файлы внутри них не заблокированы).

Обзор разбора примеров

Ниже кратко перечислены главные характеристики решения:

Базовая поддержка

•    На первый взгляд, сгенерированный посредник Soap выглядит тесно связанным с протоколом передачи HTTP. В действительности дело обстоит не так.
•    Веб-сервисы ASP.NET включают в себя инфраструктуру, специально предназначенную для обеспечения поддержки альтернативных протоколов передачи, основанных на System.Net.WebRequest и System.Net.WebResponse.
•    Для поддержки новых протоколов в составе инфраструктуры предоставляется механизм регистрации схемы. После того как новые "схемы сети" зарегистрированы, клиент может использовать их при задании конечной точки.
•    MSMQ и Websphere MQ легко упаковываются в протокол передачи, и клиент просто задает конечную точку на основе схемы. Это минимизирует изменения кода, требуемые для наделения клиента возможностью использовать новый протокол передачи.

Поддержка защиты

•    WSE легко встраивается в клиенты и серверы ASP.NET, и в более необработанной форме в произвольные серверные службы .NET. В первом случае SoapWebRequest выполняет большую часть работы, такую как управление сериализацией сообщений Soap и последующая передача их конкретному протоколу передачи для доставки.
•    В код требуется внести немного изменений (каркас и клиент), чтобы перейти от режима незашифрованного текста к режиму защиты WSE.

Поддержка двоичных вложений

•    При наличии установленного WSE поддержка двоичных вложений посредством SoapWebRequest легко обеспечивается.

Дальнейшая работа

Данный раздел создается проще простого! Набор фактов для обдумывания включает:
•    В коде есть места, где строки соединяются, а не строятся с помощью StringBuilder. Можно попробовать использовать другую технику!
•    Нужно выполнить много действий  для защиты различных аспектов решения. Сразу на ум приходят два следующих:

o    Использовать StrongNameIdentityPermissionAttribute для явного контроля за тем, кто вызывает важные сборки (только вызывающие программы, подписанные конкретным сильным именным ключом)
o    Ключи шифрования должны храниться за пределами кода, в (хорошо защищенном) файле или в защищенной области реестра

•    Демонстрационная программа была построена на базе похожего на RPC протокола сообщений, в котором предполагаются ответы на сообщения. Изучение односторонних сообщений Soap было бы интересным – они лучше соответствуют операции отправки в форме очереди без необходимости приема соответствующего ответа и были бы полезным дополнением к библиотеке.
•    Данный каркас использовался для взаимодействия с сервером на Java, использующим набор инструментов Axis Toolkit 1.1 RC1 под J2SE 1.4.1.  К сожалению, добавление Java в набор привело бы к удвоению размера копии! В зависимости от реакции на данную статью, возможно написание новой статьи, более детально рассматривающей вопрос функциональной совместимости.
•    Было бы интересно добавить средство сжатия, как в библиотеке сжатия SmartLib  , в секцию DIME для больших вложений.
•    В каркасе можно реализовать другие протоколы передачи – SMTP, FTP, SQL2000 и т.д. являются хорошими кандидатами. Например, реализация TCP/IP в данном каркасе была выполнена весьма просто.