Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Отправка Soap Headers


Servy ©   (2011-09-05 13:10:00) 

Возникла необходимость написать клиент к одному SOAP-сервису. От авторов сервиса пришел пример, как должен выглядеть запрос, он приведен ниже. Я заменил url сервиса заказчика на " http://serviceurl/", ибо он длинный и не должен быть доступен широкой публике:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV=" http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1=" http://serviceurl/" xmlns:ns2="redeemVoucher">
 <SOAP-ENV:Header>
  <ns2:Authorization>Basic b2N0b2JlcjoxMjM0</ns2:Authorization>
 </SOAP-ENV:Header>
 <SOAP-ENV:Body>
  <ns1:redeemVoucher>
  <partner_id>26538</partner_id>
  <voucher_id>9910000000980</voucher_id>
  <timeout>20</timeout>
  <transaction_data>Some data here
  </transaction_data>
  </ns1:redeemVoucher>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

В Делфи я экспортнул wsdl'ку. Header в wsdl'ке не описан. Прочитав:

http://docwiki.embarcadero.com/RADStudio/en/Defining_and_Using_SOAP_Headers

Я написал следующий код:

TAuthHeader = class(TSOAPHeader)
private
 FAuthorization: string;
published
 property Authorization: string read FAuthorization write FAuthorization;
end;

// ...

initialization
 { VoucherService }
 InvRegistry.RegisterInterface(TypeInfo(VoucherService), ' http://serviceurl/', 'UTF-8'); // сгенерировано делфи
 
 InvRegistry.RegisterHeaderClass(TypeInfo(VoucherService), TAuthHeader, 'Authentification', '');
 RemClassRegistry.RegisterSerializeOptions(TAuthHeader, [xoSimpleTypeWrapper]);
 
При вызове передаю заголовок следующим образом:

function SendRedeemVoucherW(PartnerId: PWideChar; VoucherId: PWideChar;
                           Timeout: Integer;
                           TransactionData: PWideChar): HRESULT;
var
 Headers: ISOAPHeaders;
 H: TAuthHeader;
begin
 LogWrite('redeemVoucher has been called', Standart);

 Headers := PartnerVoucherService as ISOAPHeaders;

 H := TAuthHeader.Create;
 H.Authorization := 'Basic b2N0b2JlcjoxMjM0';
 Headers.OwnsSentHeaders := True;
 Headers.Send(H);

 Result := PartnerVoucherService.redeemVoucher(PartnerId, VoucherId,
                                               Timeout, TransactionData);
                                               

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

POST / HTTP/1.1
SOAPAction: ""
Content-Type: text/xml; charset="utf-8"
User-Agent: CodeGear SOAP 1.3
Host: localhost:8333
Content-Length: 652
Connection: Keep-Alive
Cache-Control: no-cache

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV=" http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd=" http://www.w3.org/2001/XMLSchema" xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance">
 <SOAP-ENV:Header>
  <NS1:TAuthHeader xmlns:NS1="urn:uVoucher">
   <NS1:Authorization>Basic b2N0b2JlcjoxMjM0</NS1:Authorization>
  </NS1:TAuthHeader>
 </SOAP-ENV:Header>
 <SOAP-ENV:Body>
  <redeemVoucher xmlns=" http://services.partnergw.servop.ru/">
   <partner_id xmlns="">26538</partner_id>
   <voucher_id xmlns="">9910000000980</voucher_id>
   <timeout xmlns="">10</timeout>
   <transaction_data xmlns="">Some data</transaction_data>
  </redeemVoucher>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Сервис возвращает ошибку авторизации. Собственно вопрос - как передать soap-header так, как этого ждет сервис заказчика:

 <SOAP-ENV:Header>
  <ns2:Authorization>Basic b2N0b2JlcjoxMjM0</ns2:Authorization>
 </SOAP-ENV:Header>

А не так, как это происходит при использовании вышеприведенного кода, написанного (ну, я старался ^_^) согласно справке:

 <SOAP-ENV:Header>
  <NS1:TAuthHeader xmlns:NS1="urn:uVoucher">
   <NS1:Authorization>Basic b2N0b2JlcjoxMjM0</NS1:Authorization>
  </NS1:TAuthHeader>
 </SOAP-ENV:Header>


Есть злобный вариант - просто открыть сокет и писать туда все руками так, как этого ждет сервис. Но формирование SOAP запроса руками видится мне костылем, и убивает всю "идею" соапа.

P.S. Мои знания технологии SOAP, и тем более особенностей ее реализации в Делфи далеки до идеала.


Плохиш   (2011-09-07 11:32:00) [1]

Стоит разместить это на http://edn.embarcadero.com/


Servy   (2011-09-07 01:07:00) [2]

На случай, если кто-то найдет этот тред поиском, поделюсь воплощенным решением:

После копания генофонда, выяснилось, что добавлять чужие xmlns в SOAP:Envelope делфи не умеет в принципе, кроме того, нормального способа дописать их туда, не меняя пресловутый генофонд, или не используя ужас вроде StringReplace'а получившегося xml'я, не нашлось. Судя по всему, по спецификации SOAP'а, которую я полностью естесственно не осилил, эти самые xmlns можно писать и по месту, так, как это делает Делфи, то есть например так:


<SOAP-ENV:Envelope ...>
<SOAP-ENV:Body>
 <redeemVoucher xmlns=" http://serviceurl">


вмето


<SOAP-ENV:Envelope xmlns:ns1=" http://serviceurl/" ...>
<SOAP-ENV:Body>
 <ns1:redeemVoucher>


Соответственно, проблема была преобразована в другую: получить от Делфи заголовок вида:


<SOAP-ENV:Header>
 <Authorization xmlns="redeemVoucher">Basic b2N0b2JlcjoxMjM0</Authorization>
</SOAP-ENV:Header>


Так как с помощью опций и другого мелкого вмешательства этого добиться не удалось, пришлось вмешаться в процесс преобразования объект->xml, и написать свой класс заголовка такого вида:


 TAuthHeader = class(TSOAPHeader)
 private
   FAuthorization: string;
 published
   property Authorization: string read FAuthorization write FAuthorization;

 public
   function   ObjectToSOAP(RootNode, ParentNode: IXMLNode;
                           const ObjConverter: IObjConverter;
                           const NodeName, NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions;
                           out RefID: InvString): IXMLNode; override;

 end;

type
 TSOAPDomConvAccess = class(TSOAPDomConv);

function TAuthHeader.ObjectToSOAP(RootNode, ParentNode: IXMLNode;
 const ObjConverter: IObjConverter; const NodeName, NodeNamespace,
 ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions;
 out RefID: InvString): IXMLNode;
var
 Conv: TSOAPDomConv;
begin
 Result := ParentNode.AddChild('Authorization', 'redeemVoucher', False);

 Conv := (ObjConverter as TSOAPDomConv);
 RefId := TSOAPDomConvAccess(Conv).GetNewID;

 Result.Text := FAuthorization;
end;


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


Наверх

Титульная страница Поиск, карта сайта Написать письмо