Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Пишем SOAP-коннектор






Стандартный коннектор не поддерживает отмену SOAP-вызова. Поэтому мы реализуем свой коннектор, который, помимо возможности отмены вызова, будет поддерживать стандартный UI для запроса имени пользователя и пароля (см. раздел “Настройка безопасности”) при доступе к защищенным ресурсам.

Чтобы стать “настоящим” коннектором, наш компонент должен реализовать интерфейс ISoapConnector, его объявление можно найти в заголовочных файлах, входящих в Platform SDK и SOAP Toolkit.

Название Описание
BeginMessage Подготовка SOAP-сообщения, к этому моменту свойства коннектора должны быть проинициализированы.
BeginMessageWSDL Подготовка SOAP-сообщения, свойства коннектора могут быть получены через параметр, имеющий тип IWSDLOperation*.
Connect Инициализация коннектора, все свойства должны быть установлены
ConnectWSDL Инициализация коннектора, свойства могут быть получены через параметр ISoapPort.
EndMessage Завершение формирования SOAP-сообщения, в этот момент должна начаться физическая отправка сообщения.
Reset Прекращает текущее соединение и выполняет повторную инициализацию коннектора.

Методы ISoapConnector

Название Описание
InputStream Входной поток. Используется для записи исходящего сообщения.
OutputStream Результат запроса.
Property Свойства коннектора.

Свойства ISoapConnector

Со времен SOAP Toolkit 2.0 интерфейс ISoapConnector несколько изменился. Теперь метод “reset” называется “Reset”, и интерфейс имеет другой IID, хотя имя осталось прежним. Будьте осторожны – при использовании заголовочных файлов от старых версий могут использоваться неправильные IID.

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

ПРИМЕЧАНИЕ Можно использовать и серверный вариант ServerXMLHTTP, но в этом случае придется задавать информацию для доступа к защищенным ресурсам через “свойства” объекта, в то время как XMLHTTP использует стандартный UI для запроса имени пользователя/пароля и/или выбора сертификата.
ПРЕДУПРЕЖДЕНИЕ При создании статьи использовалсять IXMLHTTPRequest, входящий в MS XML 2.6. Использование компонента, входящего в состав MS XML 3.0, не рекомендовано Microsoft по причине наличия ошибок в этой версии. Эти ошибки были исправлены в MS XML 3.0 SP1.

Еще один вопрос, который необходимо решить перед реализацией коннектора – выбор потоковой модели. Так как стандартный коннектор имеет потоковую модель ‘Both’, мы также будем использовать потоковую модель ‘Both’.

Если верить документации, вызовы к коннектору приходят в следующем порядке:

  • ConnectWSDL;
  • BeginMessageWSDL;
  • get_InputStream;
  • запись сообщения в InputStream;
  • EndMessage – здесь происходит физическая отправка сообщения;
  • get_OutputStream;
  • чтение отклика сервера;
  • Reset.

Именно таким образом и работал коннектор в SOAP Toolkit 2.0. Однако в версии 3.0 это не так. Изменения в логике работы коннектора связаны с поддержкой бинарных вложений. Начиная с версии 3.0, появилась возможность передавать бинарные данные в теле запроса по HTTP в формате DIME, избегая накладных расходов на перекодировку в текстовый формат (base64). Поддержка вложений (attachment) осуществляется и на уровне коннектора. Реально от указателя на IStream, возвращаемого свойствами InputStream и OutputStream, можно запросить дополнительные интерфейсы, позволяющие передавать и принимать вложения:

  • IGetComposerDestination – поддерживается объектом, возвращаемым свойством InputStream. Предназначен для получения указателя на интерфейс IComposerDestination.
  • IComposerDestination – предоставляет отдельный поток для сохранения вложений в теле запроса.
  • IGetParserSource – поддерживается объектом, возвращаемым свойством OutputStream. Предназначен для получения указателя на интерфейс IParserSource.
  • IParserSource – предоставляет отдельный поток для чтения вложений из тела запроса.

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

Метод Описание
HRESULT _stdcall BeginSending([out] ISequentialStream** par_stream, [out] ComposerDestinationFlags* par_flags); Получение потока для записи бинарных вложений.
HRESULT _stdcall EndSending(); Физическая отправка данных на сервер.
get_property, put_Property Позволяет задать заголовок запроса, например “content-type” = “applicationdime”.

Интерфейс IComposeDestination

Метод Описание
HRESULT _stdcall BeginReceiving([out, retval] ISequentialStream** par_stream); Получение потока для чтения бинарных вложений
HRESULT _stdcall EndReceiving(); Чтение данных закончено.
get_property, put_Property Позволяет получить заголовок запроса, например “content-type”.

Интерфейс IParserSource

В SOAP Toolkit 3.0 коннектор работает в следующем режиме:

  • ConnectWSDL.
  • get_InputStream, get_OutputStream.
  • BeginMessageWSDL.
  • Запись сообщения во входной поток. Если есть бинарные вложения – запрашивается интерфейс IGetComposerDestination, получается интерфейс IComposerDestination, в поток записываются бинарные вложения. В этом случае метод Write интерфейса IStream уже не вызывается. И, наоборот, если нет бинарных вложений, используется Write. Вызов EndSending интерфейса IParserSource или вызов Write должны означать физическую передачу запроса на сервер.
  • Чтение результата из выходного потока. Если есть бинарные вложения, запрашивается интерфейс IParserSource, и чтение данных производится через него. Если вложений нет, используется обычный IStream:: Read.
  • Вызов get_Property(“ResponseHTTPHeaders”) у коннектора для получения заголовка HTTP-отклика сервера.

Графически работа коннектора без бинарных вложений выглядит так:


Рисунок 3. Последовательность вызовов в случае без бинарных вложений

С бинарными вложениями:


Рисунок 4. Последовательность вызовов при наличии бинарных вложений

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

Этот компонент пригодится нам, чтобы избежать промежуточного копирования данных.

Реализация этого компонента тривиальна:

void CMemStream:: init(void* pb, ULONG cb) { m_pb = reinterpret_cast< BYTE*> (pb); m_cb = cb; m_seekptr = 0; } STDMETHODIMP CMemStream:: Read(void * pv, ULONG cb, ULONG * pcbRead) { ULONG toread = min(cb, m_cb - m_seekptr); memcpy(pv, m_pb + m_seekptr, toread); m_seekptr += toread; if(pcbRead) *pcbRead = toread; return S_OK; } STDMETHODIMP CMemStream:: Write(const void * pv, ULONG cb, ULONG * pcbWritten) { ULONG towrite = min(cb, m_cb - m_seekptr); memcpy(m_pb + m_seekptr, pv, towrite); m_seekptr += towrite; if(pcbWritten) *pcbWritten = towrite; return S_OK; }

Для реализации свойств InputStream и OutputStream коннектора мы используем два вспомогательных компонента CInputStream и COutputStream. COutputStream поддерживает интерфейсы IStream, IGetParserSource и IParserSource, а всю реальную работу перенаправляет коннектору.

STDMETHODIMP COutputStream:: Read(void * pv, ULONG cb, ULONG * pcbRead) { return m_pConn-> receive(pv, cb, pcbRead); } // IGetParserSource STDMETHODIMP COutputStream:: get_ParserSource(IParserSource * * par_value) { *par_value = IParserSourcePtr(GetUnknown()).Detach(); return S_OK; } // IParserSource STDMETHODIMP COutputStream:: get_Property(BSTR par_name, VARIANT * par_value) { _bstr_t val; HRESULT hr = m_pConn-> get_property(par_name, val); if(SUCCEEDED(hr)) *par_value = _variant_t(val).Detach(); return hr; } STDMETHODIMP COutputStream:: BeginReceiving(ISequentialStream * * par_stream) { *par_stream = IStreamPtr(GetUnknown()).Detach(); return S_OK; }

Реализация CInputStream немного сложнее, так как вызов IStream:: Write должен приводить к немедленной отправке сообщения на сервер, а вызовы через IComposerDestination должны буферизоваться, отправка на сервер должна быть начата только при вызове IComposerDestination:: EndSending. Для хранения промежуточных данных между вызовами BeginSending и EndSending используется CMemStream.

void CInputStream:: init(CConnector* pConn) { m_pConn = pConn; } STDMETHODIMP CInputStream:: Write(const void * pv, ULONG cb, ULONG * pcbWritten) { HRESULT hr = m_pConn-> send(pv, cb); if((SUCCEEDED(hr)) & & pcbWritten) *pcbWritten = cb; return hr; } STDMETHODIMP CInputStream:: put_TotalSize(LONG sz) { m_cb = sz; return S_OK; } STDMETHODIMP CInputStream:: put_Property(BSTR par_name, VARIANT par_value) { CComVariant v = par_value; HRESULT hr = v.ChangeType(VT_BSTR); if(SUCCEEDED(hr)) hr = m_pConn-> set_property(par_name, v.bstrVal); return hr; } STDMETHODIMP CInputStream:: BeginSending(ISequentialStream ** par_stream, ComposerDestinationFlags * /*par_flags*/) { if(m_pb) { delete[] m_pb; m_pb = 0; } m_pb = new BYTE[m_cb]; m_pMemStream-> init(m_pb, m_cb); *par_stream = IStreamPtr(m_pMemStream).Detach(); return S_OK; } STDMETHODIMP CInputStream:: EndSending() { HRESULT hr = m_pConn-> send(m_pb, m_cb); if(m_pb) { delete[] m_pb; m_pb = 0; } return hr; } // IGetComposerDestination STDMETHODIMP CInputStream:: get_ComposerDestination(IComposerDestination ** par_value) { *par_value = IComposerDestinationPtr(GetUnknown()).Detach(); return S_OK; }

Чтобы поддерживать асинхронные вызовы, наш коннектор будет поддерживать три свойства (доступные через SOAPClient.ConnectorProperty), с помощью которых клиент будет контролировать асинхронные вызовы:

Свойство Описание
Async True, false – обычный или асинхронный вызов.
CallbackInterval Интервал в миллисекундах для получения клиентом уведомления о состоянии вызова.
Callback Указатель на интерфейс IAsyncSOAPCall, который должен реализовать клиент. С помощью этого интерфейса можно отменить текущий вызов.
Метод Описание
OnProgress([out] VARIANT_BOOL* bCancel) Коннектор будет передавать управлению этому методу через заданный интервал времени в процессе SOAP-вызова. Чтобы прервать SOAP-вызов, нужно вернуть bCancel = true.

Интерфейс IAsyncSOAPCall

Вот реализация основных методов коннектора:

STDMETHODIMP CConnector:: put_Property(BSTR pPropertyName, VARIANT pPropertyValue) { ObjectLock lock_(this); try { _variant_t v = pPropertyValue; if(! wcsicmp(pPropertyName, L" Async")) { // сохраняем новое значение в переменной m_bAsync // и при необходимости снова вызываем XMLHTTP-> open с новым значением v.ChangeType(VT_BOOL); m_bAsync = (v.boolVal == VARIANT_TRUE); // open m_spRequest-> open(L" POST", (WCHAR*)m_bstrURL, _variant_t(m_bAsync? VARIANT_TRUE: VARIANT_FALSE), vtMissing, vtMissing); } else if(! wcsicmp(pPropertyName, L" CallbackInterval")) { v.ChangeType(VT_I4); m_nCallInterval = v.lVal; } else if(! wcsicmp(pPropertyName, L" Callback")) { // сохраняем указатель на интерфейс в переменной m_spClient if((v.vt! = VT_UNKNOWN) & & (v.vt! = VT_DISPATCH)) _com_issue_error(E_INVALIDARG); CComPtr< IAsyncSOAPCall> spClient; HRESULT hr = v.punkVal-> QueryInterface(IID_IAsyncSOAPCall, (void**)& spClient); if(SUCCEEDED(hr)) { m_spClient.Release(); m_spClient = spClient; } else _com_issue_error(hr); } } catch(_com_error& e) { // обрабатываем ошибки return e.Error(); } return S_OK; } HRESULT CConnector:: send(const void* pb, ULONG cb) { ObjectLock lock_(this); try { // устанавливаем заголовок запроса content-type = text/xml WCHAR buf[20]; _ultow(cb, buf, 10); if(! m_bContentTypeSet) m_spRequest-> setRequestHeader(L" content-type", L" text/xml"); m_spRequest-> setRequestHeader(L" content-length", buf); m_pMemStream-> init(const_cast< void*> (pb), cb); IUnknownPtr spStm = m_pMemStream; m_spRequest-> send(_variant_t((IUnknown*)spStm)); while(m_spRequest-> readyState! = 4) { VARIANT_BOOL bCancel = VARIANT_FALSE; if(m_spClient) { // вызываем событие у клиента, если bCancel = true - выходим m_spClient-> OnProgress(& bCancel); if(bCancel == VARIANT_TRUE) { m_spRequest-> abort(); _com_issue_error(E_ABORT); } } // обрабатываем очередь сообщений MSG msg; const DWORD dwSleepTime = 100; DWORD dwTotal = 0; while(dwTotal < m_nCallInterval) { while(:: PeekMessage(& msg, 0, 0, 0, PM_REMOVE)) {:: TranslateMessage(& msg);:: DispatchMessage(& msg); } Sleep(dwSleepTime); dwTotal += dwSleepTime; } } // сохраняем в m_spStm значение responseStream _variant_t v = m_spRequest-> responseStream; if((v.vt! = VT_UNKNOWN) & & (v.vt! = VT_DISPATCH)) _com_issue_error(E_UNEXPECTED); m_spStm = v.punkVal; } catch(_com_error& e) { return e.Error(); } return S_OK; }

Основная особенность метода send заключается в том, что во время ожидания завершения вызова обрабатывается очередь сообщений. Чтобы клиент мог осуществлять асинхронный вызов, ему достаточно реализовать интерфейс IAsyncSOAPCall (в VB, например, это можно сделать с помощью ключевого слова implements).

Итак, наша реализация коннектора обладает следующими достоинствами:

  • Поддерживает стандартный UI для запроса информации о пользователе (имя и пароль) при доступе к защищенным ресурсам.
  • Позволяет контролировать SOAP-вызов и отменять его с помощью интерфейса IAsyncSOAPCall.
  • Может использоваться как в C++, так и в VB-приложениях.
  • Корректно работает с вложениями.





© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.