Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Windows Web Services
Кенни Керр
Кенни КеррОдна из главных причин того, что многие разработчики с удовольствием перешли на Microsoft .NET Framework, а Java менее популярна, состоит в том, что эта инфраструктура заметно упрощает написание ПО для Интернета. Что бы вы ни создавали — HTTP-клиент или серверное приложение, .NET Framework предоставляла вам все классы для формирования HTTP-запросов и обработки XML. Вы могли даже генерировать SOAP-клиенты на основе WSDL-документов и реализовать SOAP-серверы с помощью ASP.NET. Когда стандарты веб-сервисов достигли своей зрелости, Microsoft разработала Windows Communications Foundation (WCF), тоже встроенную в .NET Framework, чтобы упростить использование усложняющихся веб-стандартов обработки различных транспортов, таких как TCP и UDP, и обеспечить применение более гибких вариантов защиты.
Однако разработчики на C++ остались в недоумении:писать веб-приложения на C++ сочли непрактичным? Microsoft предоставила несколько временных решений в виде ATL-классов и набора инструментов на основе COM, но они не поспевали за прогрессом в области управляемых стеков SOAP и в конечном счете были практически заброшены.
Несмотря на кажущуюся сосредоточенность исключительно на .NET Framework, в Microsoft не забывали о разработчиках на C++. И решения, помогающие разработчикам на C++, являются ключевой частью стратегии Microsoft для платформы Windows. Конечно, многие API, ориентированные на C++-разработчиков, также служат подпоркой для многих управляемых инфраструктур.
Хотя таких API слишком много, чтобы перечислять их в этой статье, о некоторых все же стоит упомянуть. Windows HTTP Services (WinHTTP) API предоставляет мощное и гибкое решение для написания HTTP-клиентов. Вы можете прочитать о WinHTTP в моей статье за август 2008 г. HTTP Server API (HTTP.sys) образует фундамент для построения высокопроизводительных HTTP-серверов без использования готовых веб-серверов наподобие Internet Information Services (IIS). Фактически IIS сама базируется на этом API. И, конечно, XmlLite API поддерживает компактный и быстрый анализатор XML для неуправляемого кода. Об этом я писал в своей статье за апрель 2007 г.
Но даже при всем при том написание SOAP-клиента или сервера на C++ было устрашающей задачей. Хотя развитие SOAP начиналось как «простого» (буква «S» в аббревиатуре так и расшифровывается), он недолго оставался таким. WinHTTP, HTTP.sys и XmlLite могут помочь в обработке HTTP-транспорта и в разборе XML, но все равно придется писать тонны кода для обработки коммуникационного уровня — таких вещей, как форматирование и интерпретация SOAP-заголовков, не говоря уже о поддержке других транспортов вроде TCP или UDP. Даже если вы сумеете как-то справиться со всем этим, вам по-прежнему понадобится разбирать SOAP-конверты вместо того, чтобы интерпретировать логические SOAP-операции как вызовы функций.
Что ж, вся эта головная боль остается в прошлом. С появлением Windows Web Services (WWS) API разработчики на C++ больше не должны чувствовать себя второсортными гражданами в мире веб-сервисов. WWS изначально ориентирована на реализацию SOAP с полностью неуправляемым кодом, включая поддержку многих протоколов WS-*. Строго говоря, WWS доступна через C API, что резко упрощает взаимодействие с другими языками и средами выполнения, но наибольший выигрыш от него получат именно разработчики на C++. По сути, небольшой помощи C++ будет достаточно, чтобы получать удовольствие от использования этого API; об этом и пойдет речь в моей статье.
Архитектура и принципы
WWS охватывает все, чего нет в библиотеках на основе .NET Framework. Она рассчитана на неуправляемый код и спроектирована так, чтобы вводить минимум зависимостей, использовать минимально возможный объем памяти и работать быстро. По-настоящему быстро. Группа, отвечающая за разработку WWS, прогоняла тесты на производительность при каждой новой сборке, сравнивая результаты с таковыми для WCF и RPC. RPC использовали в качестве своего рода базовой линии, так как более быстродействующей инфраструктуры нет и она позволяет надежно отслеживать даже малые уменьшения скорости работы. Однако отличия проявляются куда ярче при сравнении с WCF. На рис. 1 сравниваются рабочие наборы клиента, использующего WCF и WWS. Разница просто колоссальная, но, вероятно, не столь удивительная, если вспомнить об участии в первом случае .NET Framework. Но рис. 2 уж точно удивит вас, если вы, как и многие другие, считаете WCF эталоном на сегодняшний день. Здесь показана пропускная способность в количестве операций в секунду для сервера, использующего WCF и WWS соответственно. WWS быстрее более чем в два раза! Не поймите меня неправильно: в WCF или .NET Framework нет ничего ошибочного — просто, если вам нужно нечто компактное и быстрое, обогнать C++ и неуправляемый код невозможно. Но это вы и без меня знаете!
Рис. 1. Сравнение рабочих наборов клиента (чем меньше, тем лучше)
Исполняющая среда WWS упакована в WebServices.dll и включается в Windows 7 и Windows Server 2008 R2. Ее также можно скачать как системное обновление для Windows XP и более поздних версий ОС. Функции, экспортируемые из WebServices.dll, представляют WWS API, и вы можете получать доступ к ним, связывая свою программу с библиотекой WebServices.lib и включая заголовочный файл WebServices.h из Windows SDK. Пока все неплохо. Но как выглядит этот API? Ну, в отличие от API-интерфейсов в стиле COM вроде XmlLite или Direct2D этот C API требует от вас вообразить набор логических уровней и объектов, существующих «за кулисами». Для начала рассмотрим этот API в терминах уровней (layers). На рис. 3 представлены уровни функциональности, предоставляемые WWS API, где каждый следующий уровень опирается на предыдущий. Каждый уровень содержит ряд функций и структур и предоставляет набор абстракций. Как вы, вероятно, догадались, приложение может использовать любой из этих уровней, но по большей части вы предпочтете иметь дело с моделью сервисов, предоставляющей самую простую модель программирования и скрывающей многие детали. Уровень транспорта просто напоминает, что в основе всего этого лежит какой-то сетевой протокол. WWS будет использовать WinHTTP, HTTP.sys или Winsock в зависимости от выбранного транспорта и того, что именно вы реализуете — клиент или сервер.
Рис. 2. Сравнение пропускной способности сервера (чем выше, тем лучше)
Рис. 3. Многоуровневый API
Уровень модели сервисов полностью абстрагирует обмен SOAP-сообщениями и моделирует логические операции веб-сервиса как вызовы функций. Однако он не работает автономно, а полагается на использование утилиты командной строки Wsutil.exe из Windows SDK. При наличии WSDL-файла эта утилита генерирует заголовочный файл, а также файл исходного кода на C, содержащий большую часть кода, нужного как для подключения к веб-сервису с данным описанием, так и для реализации этого веб-сервиса; при этом он берет на себя корректную настройку привязки канала и должное форматирование сообщений. Это предельно простой подход, позволяющий использовать модель программирования, очень похожую на традиционный RPC.
С другой стороны, уровень канала открывает доступ к сообщениям, посылаемым и принимаемым по конкретному транспорту, но по-прежнему избавляет вас от самостоятельного форматирования сообщений. Здесь преимущество в том, что вы защищены от сложностей и деталей конкретного транспорта, а также используемой им кодировки. Уровень канала — как раз то место, где вы управляете информацией привязки и где можете защищать свои коммуникации для конфиденциальности или аутентификации.
Уровень XML обеспечивает доступ к форматированию сообщений и сериализации данных. Здесь вам полностью доступно содержимое сообщений, но о конкретной кодировке можно не думать, будь то текст, двоичное представление или MTOM. Возможно, вас удивило, что в WWS есть свой анализатор XML. Почему бы просто не использовать XmlLite? Хотя XmlLite действительно компактен и очень быстро работает, он не подходит по целому ряду причин. Самая очевидная из них — WWS нужно поддерживать различные кодировки, а XmlLite поддерживает только текст. Кроме того, SOAP-сообщения, как правило, кодируются с применением UTF-8, а XmlLite предоставляет все свойства с Unicode-строками, и это создавало бы лишние издержки при копировании значений. WWS также накладывает жесткие ограничения на объем используемой памяти (для этого даже предназначен отдельный API, как вы потом увидите), которым XmlLite не соответствует. Да и вообще группе WWS удалось реализовать собственный анализатор специально для SOAP, работающий значительно быстрее XmlLite. Учтите, что анализатор в WWS вовсе не заменяет XmlLite. Как универсальный XML-анализатор он вне конкуренции — XML-анализатор в WWS предоставляет разработчикам очень специфические средства, нацеленные на эффективные сериализацию данных в SOAP-сообщение и их обратную десериализацию с применением подмножества XML, требуемого SOAP.
Помимо функций и структур данных, логически принадлежащих этим трем уровням, WWS API поддерживает ряд механизмов, общих для всех уровней, в том числе обработку ошибок, асинхронное выполнение, отмену, управление памятью и др. Так как места в журнале мало, а я хочу помочь вам побыстрее приступить к работе, я намерен ограничить оставшуюся часть статьи использованием модели сервисов для создания клиента и сервера веб-сервиса. В будущей статье я рассмотрю подробнее и другие части WWS.
Приступаем к работе
Для начала возьмем минимальное определение веб-сервиса с рис. 4.Этот WSDL-документ определяет типы, сообщения, операции, конечные точки и привязки канала для сервиса. Первым делом его надо пропустить через утилиту Wsutil.exe:
Wsutil.exe Calculator.wsdl
В итоге вы получите заголовочный файл Calculator.wsdl.h и файл исходного кода на C — Calculator.wsdl.c.
Рис. 4. Определение веб-сервиса
<wsdl:definitions xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="ttp://www.w3.org/2001/XMLSchema"
xmlns:tns="http://calculator.example.org/"
targetNamespace="http://calculator.example.org/">
<wsdl:types>
<xsd:schema elementFormDefault="qualified"
targetNamespace="http://calculator.example.org/">
<xsd:element name="AddRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="first" type="xsd:double" />
<xsd:element name="second" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="AddResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="result" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="AddRequestMessage">
<wsdl:part name="parameters" element="tns:AddRequest" />
</wsdl:message>
<wsdl:message name="AddResponseMessage">
<wsdl:part name="parameters" element="tns:AddResponse" />
</wsdl:message>
<wsdl:portType name="CalculatorPort">
<wsdl:operation name="Add">
<wsdl:input message="tns:AddRequestMessage" />
<wsdl:output message="tns:AddResponseMessage" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CalculatorBinding" type="tns:CalculatorPort">
<soap:binding transport="https://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Add">
<soap:operation soapAction=
"http://calculator.example.org/Add" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CalculatorService">
<wsdl:port name="CalculatorPort" binding="tns:CalculatorBinding">
<soap:address location="http://localhost:81/calculator"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Прежде чем изучать, что же было сгенерировано, нужна кое-какая подготовительная работа независимо от того, что именно вы реализуете — клиент или сервер. Первым делом потребуется какой-то способ выражения информации об ошибках. WWS API предоставляет богатую информацию об ошибках, связанных как с вызовами его функций, так и с SOAP, через объект error. Так как это C API, данный объект представляется непрозрачным описателем и набором функций. В нашем случае WS_ERROR* представляет описатель объекта error, а WsCreateError — это функция, которая его создает. Этот объект освобождается вызовом функции WsFreeError. Объект error может хранить несколько строк с разными уровнями информации об ошибке. Чтобы получить информацию, нужно сначала определить, сколько строк в нем содержится. Для этого вызывается функция WsGetErrorProperty с передачей ей описателя объекта error и константы WS_ERROR_PROPERTY_STRING_COUNT. Получив требуемые сведения, вы вызываете функцию WsGetErrorString, передавая ей описатель объекта error, а также индекс извлекаемой строки (отсчет от нуля). Кроме того, вы можете использовать API-функции для заполнения собственных объектов ошибок. И здесь вам понадобится немного помощи от C++. На рис. 5 представлена простая оболочка объекта error. С ее помощью вы можете легко перечислять строки в объекте error, как показано на рис. 6.
Рис. 5. Оболочка объекта error
class WsError
{
WS_ERROR* m_h;
public:
WsError* m_h(0)
{}
~WsError()
{
if (0 != m_h)
}
HRESULT Create(const WS_ERROR_PROPERTY* properties,
ULONG propertyCount)
{
return WsCreateError(properties, propertyCount, &m_h);
}
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, void* buffer,
ULONG bufferSize)
{
return WsGetErrorProperty(m_h, id, buffer, bufferSize);
}
template <typename T>
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, out T* buffer)
{
return GetProperty(id, buffer, sizeof(T));
}
HRESULT GetString(ULONG index, WS_STRING* string)
{
return WsGetErrorString(m_h, index, string);
}
operator WS_ERROR*() const
{
return m_h;
}
};
Рис. 6. Перечисление строк в объекте error
WsError error;
HR(error.Create(0, 0));
// Something goes wrong . . .
ULONG stringCount = 0;
HR(error.GetProperty(WS_ERROR_PROPERTY_STRING_COUNT,&stringCount));
for (ULONG i = 0; i < stringCount; ++i)
{
WS_STRING string;
HR(error.GetString(i, &string));
wprintf(L"Error %d: %.*s\n", i, string.length, string.chars);
}
Следующее, что нам понадобится, — объект heap. Этот объект обеспечивает тонкое управление операциями выделения памяти при формировании или использовании сообщений, а также при создании различных структур. Кроме того, он упрощает модель программирования, так как вам не придется ломать голову над тем, сколько именно памяти потребуется для каждой функции для успешной работы. Многие более старые функции в Windows SDK, например, требуют от вас либо догадаться, сколько памяти нужно отвести под буферы, либо сначала вызвать специфическую функцию, определяющую нужный объем памяти. Применение WWS-объекта heap избавляет от всей дополнительной работы и позволяет удобно контролировать использование памяти этим API. Объект heap также удобен с точки зрения безопасности, когда нужно задавать ожидаемые лимиты буферов, которые могут создавать API-функции. WS_HEAP* представляет описатель объекта heap, а WsCreateHeap — это функция, которая создает его. Объект освобождается вызовом WsFreeHeap. После создания объекта вы можете выделять память из кучи (heap) с помощью функции WsAlloc, но в этой статье мы обойдемся простой передачей описателя объекта heap в API-функции.
Простая оболочка объекта heap приведена на рис. 7. Используя ее, вы можете создать объект heap, ограниченный 250 байтами:
WsHeap heap;
HR(heap.Create(250, // max size
0, // trim size
0, // properties
0, // property count
error));
Обратите внимание на то, как я передаю объект error в метод Create объекта heap. Если что-то пойдет не так при создании объекта heap, я смогу проанализировать объект error и выяснить причину ошибки.
Рис. 7. Оболочка объекта heap
class WsError
{
WS_ERROR* m_h;
public:
WsError* m_h(0)
{}
~WsError()
{
if (0 != m_h)
}
HRESULT Create(const WS_ERROR_PROPERTY* properties,
ULONG propertyCount)
{
return WsCreateError(properties, propertyCount, &m_h);
}
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, void* buffer,
ULONG bufferSize)
{
return WsGetErrorProperty(m_h, id, buffer, bufferSize);
}
template <typename T>
HRESULT GetProperty(WS_ERROR_PROPERTY_ID id, out T* buffer)
{
return GetProperty(id, buffer, sizeof(T));
}
HRESULT GetString(ULONG index, WS_STRING* string)
{
return WsGetErrorString(m_h, index, string);
}
operator WS_ERROR*() const
{
return m_h;
}
};
class WsHeap
{
WS_HEAP* m_h;
public:
WsHeap() : m_h(0)
{}
~WsHeap()
{
if (0 != m_h) WsFreeHeap(m_h);
}
HRESULT Create(SIZE_T maxSize, SIZE_T trimSize,
const WS_HEAP_PROPERTY* properties,
ULONG propertyCount,
in_opt WS_ERROR* error)
{
return WsCreateHeap(maxSize, trimSize, properties, propertyCount,
&m_h, error);
}
operator WS_HEAP*() const
{
return m_h;
}
};
Клиент
Клиентская модель сервисов ориентирована на объект прокси сервиса. Генерируемый исходный код включает функцию CalculatorBinding_CreateServiceProxy. Это имя формируется из имени конечной точки или привязки, определенного в WSDL-документе. Функция создает объект прокси сервиса и возвращает WS_SERVICE_PROXY*, представляющий непрозрачный описатель этого объекта. Объект освобождается вызовом функции WsFreeServiceProxy. После создания объекта прокси ваше приложение может открыть конечную точку сервиса через функцию WsOpenServiceProxy, а затем обращаться к веб-сервису через прокси сервиса. Что именно делает WsOpenServiceProxy, зависит от задействованного транспорта. Вы также должны позаботиться о закрытии прокси сервиса до его освобождения вызовом функции WsCloseServiceProxy. Естественно, вся эта работа может быть инкапсулирована в простой класс (рис. 8).В этом случае вы можете создавать и открывать прокси сервиса, как показано на рис. 9..
Рис. 8. Оболочка прокси сервиса
class WsServiceProxy
{
WS_SERVICE_PROXY* m_h;
public:
WsServiceProxy() : m_h(0)
{}
~WsServiceProxy()
{
if (0 != m_h)
{
Close(0, 0); // error
WsFreeServiceProxy(m_h);
}
}
HRESULT Open(const WS_ENDPOINT_ADDRESS* address,
const WS_ASYNC_CONTEXT* asyncContext,
WS_ERROR* error)
{
return WsOpenServiceProxy(m_h, address, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext,
WS_ERROR* error)
{
return WsCloseServiceProxy(m_h, asyncContext, error);
}
WS_SERVICE_PROXY** operator&()
{
return &m_h;
}
operator WS_SERVICE_PROXY*() const
{
return m_h;
}
};
Рис. 9. Создание и открытие прокси сервиса
WsServiceProxy serviceProxy;
HR(CalculatorBinding_CreateServiceProxy(0, // template value
0, // properties
0, // property count
&serviceProxy,
error));
WS_ENDPOINT_ADDRESS address =
{
{
static_cast<ULONG>(wcslen(url)),
const_cast<PWSTR>(url)
}
};
HR(serviceProxy.Open(&address,
0, // async context
error));
Структура WS_ENDPOINT_ADDRESS указывает адрес передаваемого сообщения. Обычно этой структурой пользуются при программировании на уровне канала. В данном случае мы записываем только URL, а об остальном заботится прокси сервиса.
В этот момент мы можем задействовать другую сгенерированную функцию, а именно CalculatorBinding_Add, которая, что неудивительно, представляет операцию Add веб-сервиса:
const double first = 1.23;
const double second = 2.34;
double result = 0.0;
HR(CalculatorBinding_Add(serviceProxy,
first,
second,
&result,
heap,
0, // properties
0, // property count
0, // async context
error));
ASSERT(3.57 == result);
Закончив взаимодействие с веб-сервисом, вам нужно лишь закрыть коммуникационный канал прокси сервиса:
HR(serviceProxy.Close(0, // async context
error));
Сервер
Если клиентская модель программирования ориентирована на прокси сервиса, то сервер вместо этого создает и управляет хостом сервиса, который предоставляет необходимую исполняющую среду для прослушивания различных конечных точек на основе указанной информации о канале. И вновь, поскольку мы используем модель сервисов, большая часть деталей от нас абстрагируется, и нам надо лишь создать конечную точку сервиса и хост. Остальное сделает WWS.
Первый шаг — создание конечной точки сервиса. Модель сервисов заботится об этом, предоставляя еще одну генерируемую функцию — CalculatorBinding_CreateServiceEndpoint (рис. 10). При создании конечной точки нужно указать адрес, по которому конечная точка будет слушать. Для этого предназначена структура WS_STRING — Unicode-строка, длина которой задается в префиксе и которая не требует завершения нулем. Поскольку за выполнение запросов отвечает конечная точка, вы должны предоставить таблицу указателей функций, сопоставленных с операциями сервиса. С этой целью используется генерируемая структура CalculatorBindingFunctionTable. Наконец, сама конечная точка представлена структурой WS_SERVICE_ENDPOINT и создается в предоставляемой куче.
Рис. 10. CalculatorBinding_CreateServiceEndpoint
class WsServiceHost
{
WS_SERVICE_HOST* m_h;
public:
WsServiceHost() : m_h(0)
{}
~WsServiceHost()
{
if (0 != m_h)
{
Close(0, 0);
WsFreeServiceHost(m_h);
}
}
HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints,
const USHORT endpointCount,
const WS_SERVICE_PROPERTY* properties,
ULONG propertyCount, WS_ERROR* error)
{
return WsCreateServiceHost(endpoints, endpointCount, properties,
propertyCount, &m_h, error);
}
HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsOpenServiceHost(m_h, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsCloseServiceHost(m_h, asyncContext, error);
}
operator WS_SERVICE_HOST*() const
{
return m_h;
}
};
WsServiceProxy serviceProxy;
const WS_STRING address =
{
static_cast<ULONG>(wcslen(url)),
const_cast<PWSTR>(url)
};
CalculatorBindingFunctionTable functions =
{
AddCallback
};
WS_SERVICE_ENDPOINT* endpoint = 0;
HR(CalculatorBinding_CreateServiceEndpoint(0, // template value
&address,
&functions,
0, // authz callback
0, // properties
0, // property count
heap,
&endpoint,
error));
Следующий шаг — создание хоста сервиса. WS_SERVICE_HOST* представляет описатель объекта хоста сервиса, а создает его функция WsCreateServiceHost. Объект освобождается вызовом функции WsFreeServiceHost. Функция WsCreateServiceHost создает объект хоста сервиса, принимая список конечных точек. В этот момент вы можете запустить слушатели всех конечных точек, используя функцию WsOpenServiceHost. Для прекращения коммуникаций служит функция WsCloseServiceHost. Как и в случае прокси сервиса, обязательно закрывайте хост сервиса до его освобождения. И вновь вся эта работа может быть инкапсулирована в простой класс (рис. 11).В этом случае вы можете запускать веб-сервис следующим образом:
const WS_SERVICE_ENDPOINT* endpoints[] = { endpoint };
WsServiceHost serviceHost;
HR(serviceHost.Create(endpoints,
_countof(endpoints),
0, // properties
0, // property count
error));
HR(serviceHost.Open(0, // async context
error));
В данном случае у нас только одна конечная точка, но добавить дополнительные конечные точки к хосту сервиса очень легко. Когда вам понадобится остановить работу сервиса, просто закройте коммуникационные каналы хоста сервиса:
HR(serviceHost.Close(0, // async context
error));
Кстати, на деле реализация операции веб-сервиса — самая простая задача:
HRESULT CALLBACK AddCallback(__in const WS_OPERATION_CONTEXT*,
__in double first,
__in double second,
__out double* result,
__in_opt const WS_ASYNC_CONTEXT* /*asyncContext*/,
__in_opt WS_ERROR* /*error*/)
{
*result = first + second;
return S_OK;
}
Сигнатура для функции AddCallback также предоставляется в генерируемом исходном коде — на случай, если у вас возникнут сомнения насчет ее определения.
Что ж, на этом все — места больше нет, но вы теперь наверняка получили неплохое представление о возможностях и преимуществах Windows Web Services API. Как видите, разработчики на C++ наконец получили современный стек SOAP, причем в полностью готовом виде. Он обеспечивает максимально возможную производительность, минимальное использование памяти и требует лишь небольшой помощи со стороны C++.
Рис. 11. Оболочка хоста сервиса
class WsServiceHost
{
WS_SERVICE_HOST* m_h;
public:
WsServiceHost() : m_h(0)
{}
~WsServiceHost()
{
if (0 != m_h)
{
Close(0, 0);
WsFreeServiceHost(m_h);
}
}
HRESULT Create(const WS_SERVICE_ENDPOINT** endpoints,
const USHORT endpointCount,
const WS_SERVICE_PROPERTY* properties,
ULONG propertyCount, WS_ERROR* error)
{
return WsCreateServiceHost(endpoints, endpointCount, properties,
propertyCount, &m_h, error);
}
HRESULT Open(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsOpenServiceHost(m_h, asyncContext, error);
}
HRESULT Close(const WS_ASYNC_CONTEXT* asyncContext, WS_ERROR* error)
{
return WsCloseServiceHost(m_h, asyncContext, error);
}
operator WS_SERVICE_HOST*() const
{
return m_h;
}
};
Кто этим пользуется?
Различные группы в Microsoft уже начали внедрять Windows Web Services (WWS) API в свои продукты или технологии. Во многих случаях им заменяют нестандартные встроенные стеки SOAP, а некоторые даже предпочли WWS вместо коммерческих реализаций вроде Windows Communication Foundation (WCF). Вот лишь несколько примеров.
Microsoft Web Services on Devices (WSD) API позволяет разработчикам писать клиенты и серверы на основе Devices Profile for Web Services (DPWS). В Windows 7 в интерфейсе WSD API начали использовать WWS для создания XML и приведения его к каноническому виду в SOAP-сообщениях, передаваемых исполняющей средой WSD. В группе WSD планируют расширить применение WWS, когда они добавят новые функции и переработают существующий код.
Windows CardSpace — Microsoft-реализация системы идентификации на основе стандартов веб-сервисов. Изначально реализованная с применением WCF, сейчас она переписывается под неуправляемый код и WWS, чтобы соответствовать очень жестким бизнес-требованиям к размеру скачиваемого установщика и рабочему набору исполняющей среды.
Microsoft Forefront Threat Management Gateway (TMG) — платформа безопасности, которая предоставляет брандмауэр (межсетевой экран) и средства кеширования для защиты и повышения производительности сетей. Ее функция фильтрации URL использует WWS для подключения к Microsoft Reputation Service с целью классификации URL.
Наконец, клиент Windows Public Key Infrastructure (PKI) обеспечивает автоматическое управление жизненным циклом сертификатов с автоматической регистрацией (enrollment), а также с регистрацией, инициируемой пользователем и приложением. В Windows 7 введен новый набор веб-сервисов, позволяющих выполнять эти операции над сертификатами без традиционных ограничений LDAP и DCOM. PKI-клиент использует WWS для всех операций, в том числе связанных с новым протоколом Certificate Enrollment Policy (MS-XCEP) и расширением WS-Trust для регистрации сертификатов (MS-WSTEP). Клиент WWS взаимодействует с новым набором Active Directory Certificate Services в Windows Server 2008 R2, реализованными с помощью WCF, а также с веб-сервисами, предоставляемыми издателями (поставщиками) открытых сертификатов (public certificate issuers).
Кенни Керр (Kenny Kerr) — высококвалифицированный специалист в области разработки ПО для Windows. С большим энтузиазмом пишет статьи по программированию, а также занимается обучением разработке и проектированию ПО. С ним можно связаться через блог weblogs.asp.net/kennykerr.