2015-01-22 3 views
3

Я пишу клиент SOAP в Delphi 2007, чтобы выполнить обычную проверку таможенной версии. Я отправляю SOAP-серверу некоторую информацию, и я должен либо получить информацию о выпуске таможни, либо ошибке SOAP, если сервер не смог найти информацию, которую я ему отправил. Первая часть работает нормально, но обработка ошибки не выполняется. WSDL определяет исключение пользовательских SOAP (это входит в основной WSDL - весь WSDL не показан):Delphi 2007 Обработка ошибок SOAP

<?xml version="1.0" encoding="ISO-8859-1"?> 
<xsd:schema targetNamespace="http://trips.crownagents.com/wsexception/message" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns="http://trips.crownagents.com/wsexception/message"> 
    <xsd:element name="WSException" type="WSException" nillable="true"/> 
    <xsd:complexType name="WSException"> 
    <xsd:sequence> 
     <xsd:element name="ErrorCode" type="xsd:string" minOccurs="0" maxOccurs="1"/> 
     <xsd:element name="ErrorDescription" type="xsd:string" minOccurs="0" maxOccurs="1"/> 
     <xsd:element name="Stack" type="xsd:string" minOccurs="0" maxOccurs="1"/> 
    </xsd:sequence> 
    </xsd:complexType> 
</xsd:schema> 

И ответ SOAP я вернусь, кажется, ссылается на исключение:

<?xml version="1.0" encoding="UTF-8"?> 
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" 
       xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:ns0="http://trips.crownagents.com/wsexception/message" 
       xmlns:ns1="http://trips.crownagents.com/external/customs/release/message" 
       xmlns:ns2="http://trips.crownagents.com/external/common/message"> 
    <env:Body> 
    <env:Fault xsi:type="env:Fault"> 
     <faultcode>env:Server</faultcode> 
     <faultstring xsi:nil="1"/> 
     <detail> 
     <ans1:WSExceptionResponse xmlns:ans1="http://msgsvr.trips.crownagents.com/"> 
      <ErrorCode>0002</ErrorCode> 
      <ErrorDescription>Invalid Declaration</ErrorDescription> 
      <Stack>getSingleResult() did not retrieve any entities.</Stack> 
     </ans1:WSExceptionResponse> 
     </detail> 
    </env:Fault> 
    </env:Body> 
</env:Envelope> 

Но мой код никогда не видит WSExceptionResponse. Вместо этого, я получаю общую ERemotableException:

Try 
    Res := Rel.releaseStatus(RelInfo); 
Except 
    On E: WSExceptionResponse Do // This never fires 
    Status('Release check error (' + E.ErrorCode + ' - ' + 
      E.ErrorDescription + ').', True); 
    Else 
    Status('Release check error (' + Exception(ExceptObject).Message + 
      ').', True); 
End; 

Я прочитал, что есть несколько проблем с обработкой SOAP в Delphi 2007 (https://groups.google.com/forum/#!msg/borland.public.delphi.webservices.soap/71t3P-vPMbk/qw9JVTEVS3YJ), и я изменил файл OPToSOAPDomConv.pas, чтобы вернуть его в соответствии с предложением но это не помогает. У кого-нибудь есть идеи относительно того, что я могу делать неправильно?

+0

Используйте SoapUI, чтобы убедиться, что служба работает должным образом, а затем проверьте, существуют ли различия между SOAP-запросами SoapUI и Delphi. – mjn

+0

Способ, которым SoapUI отправляет сообщение, сильно отличается от того, как делает Delphi (хотя в основном это касается пространств имен), но возвращаемая ошибка msg одинакова в любом случае. – Caynadian

ответ

3

Для тех, кто все еще использует Delphi 2007, который сталкивается с этим вопросом, я как-то исправил эту проблему.

Сначала скопируйте OPToSOAPDomConv.pas и InvokeRegistry.pas из исходного каталога Delphi (\ Program Files < (x86)> \ CodeGear \ RAD Studio \ 5.0 \ source \ Win32 \ soap) в каталог вашего проекта. Добавьте эти два файла в свой проект, поскольку вы будете настраивать исходный код, и вам понадобятся эти данные для перекомпиляции с вашим проектом вместо использования предварительно скомпилированных DCU, которые поставляются с Delphi.

В файле OPToSOAPDomConv.pas, найти процедуру ProcessFault и заменить его следующим:

procedure TOPToSoapDomConvert.ProcessFault(FaultNode: IXMLNode); 
var 
    FA, FC, FD, FS, CustNode: IXMLNode; 
    I, J: Integer; 
    AMessage: WideString; 
    AClass: TClass; 
    URI, TypeName: WideString; 
    Count: Integer; 
    PropList: PPropList; 
    Ex: ERemotableException; 

    function GetNodeURIAndName(const Node: IXMLNode; var TypeURI, 
    ElemName: InvString): boolean; 
    var 
    Pre: InvString; 
    begin 
    ElemName := Node.NodeName; 
    if IsPrefixed(ElemName) then 
    begin 
     Pre := ExtractPrefix(ElemName); 
     ElemName := ExtractLocalName(ElemName); 
     TypeURI := Node.FindNamespaceURI(Pre); 
    end 
    else 
     TypeURI := Node.NamespaceURI; 
    Result := True; 
    end; 

begin 
    FA := nil; 
    FC := nil; 
    FD := nil; 
    FS := nil; 
    Ex := nil; 
    for I := 0 to FaultNode.ChildNodes.Count - 1 do 
    begin 
    if  SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultCode) then 
     FC := FaultNode.ChildNodes[I] 
    else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultString) then 
     FS := FaultNode.ChildNodes[I] 
    else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultDetails) then 
     FD := FaultNode.ChildNodes[I] 
    else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultActor) then 
     FA := FaultNode.ChildNodes[I]; 
    end; 

    { Retrieve message from FaultString node } 
    if FS <> nil then 
    AMessage := FS.Text; 

    { If there's a <detail> node, try to map it to a registered type } 
    if FD <> nil then 
    begin 
    { Some SOAP stacks, including Delphi6 and others (see 
     http://softwaredev.earthweb.com/script/article/0,,12063_641361_2,00.html) 
     use the approach of putting custom fault info right at the <detail> node: 

     Listing 4 - Application Fault Details 
     <SOAP-ENV:Fault> 
     <faultcode>300</faultcode> 
     <faultstring>Invalid Request</faultstring> 
     <runcode>1</runcode> 
     <detail xmlns:e="GetTemperatureErr-URI" 
       xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" 
       xsi:type="e:GetTemperatureFault"> 
      <number>5575910</number> 
      <description>Sensor Failure</description> 
      <file>GetTemperatureClass.cpp</file> 
      <line>481</line> 
     </detail> 
     </SOAP-ENV:Fault> 

     However, much more common is the approach where the type and namespace 
     are on the childnode of the <detail> node. Apache, MS and the SOAP spec. 
     seem to lean towards that approach: 

     Example 10 from the SOAP 1.1 Spec: 

     <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> 
     <SOAP-ENV:Body> 
      <SOAP-ENV:Fault> 
       <faultcode>SOAP-ENV:Server</faultcode> 
       <faultstring>Server Error</faultstring> 
       <detail> 
        <e:myfaultdetails xmlns:e="Some-URI"> 
         <message>My application didn't work</message> 
         <errorcode>1001</errorcode> 
        </e:myfaultdetails> 
       </detail> 
      </SOAP-ENV:Fault> 
     </SOAP-ENV:Body> 
     </SOAP-ENV:Envelope> 

     For interop reasons we favor the later approach but we'll support both here!! 
    } 
    CustNode := nil; 
    if GetElementType(FD, URI, TypeName) then 
     CustNode := FD 
    else 
    begin 
     if ntElementChildCount(FD) > 0 then 
     begin 
     CustNode := ntElementChild(FD, 0); 
     if not GetElementType(CustNode, URI, TypeName) and 
      not GetNodeURIAndName(CustNode, URI, TypeName) then 
      CustNode := nil; 
     end; 
    end; 

    if (CustNode <> nil) then 
    begin 
     AClass := RemClassRegistry.URIToClass(URI, TypeName); 
     if AClass <> nil then 
     begin 
     if AClass.InheritsFrom(ERemotableException) then 
     begin 
      Ex := ERemotableExceptionClass(AClass).Create(AMessage); 
      LoadObject(Ex, FaultNode, CustNode); 
     end; 
     end; 
    end; 
    end; 

    { Create default SOAP invocation exception if no suitable class was found } 
    if Ex = nil then 
    Ex := ERemotableException.Create(AMessage); 
    if FA <> nil then 
    Ex.FaultActor := FA.Text; 
    if FC <> nil then 
    Ex.FaultCode := FC.Text; 
    if FD <> nil then 
    Ex.FaultDetail := FD.XML; 
    raise Ex; 
end; 

Далее, найти функцию GetElementType и заменить его следующим:

function TSOAPDomConv.GetElementType(Node: IXMLNode; var TypeURI, TypeName: InvString): Boolean; 
var 
    Idx: Integer; 
    S : InvString; 
    V: Variant; 
    Pre: InvString; 
begin 
    TypeURI := ''; 
    TypeName := ''; 
    Result := False; 
    if (Node.NamespaceURI = SSoap11EncodingS5) and 
    (Node.LocalName = SSoapEncodingArray) then 
    begin 
    TypeURI := SSoap11EncodingS5; 
    TypeName := SSoapEncodingArray; 
    Result := True; 
    end 
    else 
    begin 
    V := GetTypeBySchemaNS(Node, XMLSchemaInstNameSpace); 
    if VarIsNull(V) then 
     V := Node.GetAttribute(SSoapType); 
    if not VarIsNull(V) then 
    begin 
     S := V; 
     Idx := Pos(':', S); { do not localize } 
     if Idx <> 0 then 
     begin 
     TypeName := Copy(S, Idx + 1, High(Integer)); 
     Pre := Copy(S, 1, Idx - 1); 
     TypeURI := Node.FindNamespaceURI(Pre); 
     end 
     else 
     begin 
     TypeName := S; 
     TypeURI := ''; 
     end; 
     Result := True; 
    end; 
    end 
end; 

Наконец , откройте файл InvokeRegistry.pas и найдите функцию GetExternalPropName. Измените строку, которая говорит:

if Info.Kind = tkClass then 

к этому:

if (Info.Kind = tkClass) and Assigned(GetTypeData(info).ParentInfo) then 

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

Все кредитные рейтинги для этого пользователя http://www.codenewsfast.com/cnf/article/859054074/permalink.art-ng1920q2368 и этот http://www.delphigroups.info/2/7/342954.html.

Смежные вопросы