hooklift / gowsdl

WSDL2Go code generation as well as its SOAP proxy
Mozilla Public License 2.0
1.14k stars 390 forks source link

Incorrect namespace handling #232

Open w65536 opened 2 years ago

w65536 commented 2 years ago

I am afraid the change introduced with #218 breaks a few things. I have not had the time to properly analyze this. But this is what I see so far.

Before, this is what my request and response looked like:

<operationX xmlns="http://www.example.com/service/v1">
  <request xmlns="http://www.example.com/service/v1" id="1234">
  </request>
</operationX> 
<operationXResponse xmlns="http://www.example.com/service/v1">
  <response xmlns="http://www.example.com/service/v1" id="1234">
    <success>true</success>
  </response>
</operationXResponse> 

Now this is what the request looks like:

<operationX xmlns="http://www.example.com/service/v1">
  <request xmlns="http://www.example.com/service/v1" xmlns:v1="http://www.example.com/service/v1" v1:id="1234">
  </request>
</operationX> 

Trying to marshal the response for printing panics. I speculate this is because the service does not respond in the format that is now expected by the generated Go code: panic: reflect: call of reflect.Value.CanInterface on zero Value

ieure commented 2 years ago

Hi @w65536. Do you have a capture of the raw response the server sends back, and the service's WSDL?

If the service you're taking to expects an un-namespaced id attribute, #218 will definitely cause problems. This is uncommon, in my experience, but certainly not impossible.

Unfortunately, Go's XML support is so broken that I don't know if the right thing can be done in both cases.

w65536 commented 2 years ago

So I was able to capture the raw SOAP request/response before and after the change. It is all simplified and sanitized to just expose the problem.

Before #218:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <operationX xmlns="http://www.example.com/service/v1">
      <request xmlns="http://www.example.com/service/v1" id="1234">
        <userId>8888</userId>
        <orderNr>9876</orderNr>
      </request>
    </operationX>
  </soap:Body>
</soap:Envelope>

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
    <v1:operationXResponse xmlns:v1="http://www.example.com/service/v1">
      <v1:response id="1234">
        <v1:success>true</v1:success>
      </v1:response>
    </v1:operationXResponse>
  </soapenv:Body>
</soapenv:Envelope>

After #218:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <operationX xmlns="http://www.example.com/service/v1">
      <request xmlns="http://www.example.com/service/v1" xmlns:v1="http://www.example.com/service/v1" v1:id="1234">
        <userId>8888</userId>
        <orderNr>9876</orderNr>
      </request>
    </operationX>
  </soap:Body>
</soap:Envelope>

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
    <v1:operationXResponse xmlns:v1="http://www.example.com/service/v1">
      <v1:response>
        <v1:success>true</v1:success>
      </v1:response>
    </v1:operationXResponse>
  </soapenv:Body>
</soapenv:Envelope>

This is the diff of above traces:

--- soap-simplified-before-218-sanitized.log    2021-12-08 17:10:39.000000000 +0100
+++ soap-simplified-after-218-sanitized.log 2021-12-08 17:10:39.000000000 +0100
@@ -1,7 +1,7 @@
 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
     <operationX xmlns="http://www.example.com/service/v1">
-      <request xmlns="http://www.example.com/service/v1" id="1234">
+      <request xmlns="http://www.example.com/service/v1" xmlns:v1="http://www.example.com/service/v1" v1:id="1234">
         <userId>8888</userId>
         <orderNr>9876</orderNr>
       </request>
@@ -14,7 +14,7 @@
   <soapenv:Header/>
   <soapenv:Body>
     <v1:operationXResponse xmlns:v1="http://www.example.com/service/v1">
-      <v1:response id="1234">
+      <v1:response>
         <v1:success>true</v1:success>
       </v1:response>
     </v1:operationXResponse>

You can see that the server no longer responds with the id attribute in the response, presumably because it did not understand the attribute in the request.

After seeing this, I realize that already before the change from #218 do I get a panic if I do not send the attribute in the request at all. I am seeing the following panic in all cases where the server responds without the id attribute when trying to call xml.MarshalIndent on the received response:

panic: reflect: call of reflect.Value.CanInterface on zero Value

goroutine 1 [running]:
reflect.Value.CanInterface(...)
    /usr/local/go/src/reflect/value.go:1005
encoding/xml.(*printer).marshalAttr(0xc00018a120, 0xc0000d3950, 0x0, 0x0, 0x767700, 0xd, 0x0, 0x0, 0x0, 0x7d1040, ...)
    /usr/local/go/src/encoding/xml/marshal.go:554 +0x1465
encoding/xml.(*printer).marshalValue(0xc00018a120, 0x73ffe0, 0xc0001783b0, 0x196, 0xc0000648a0, 0x0, 0xc0001783b0, 0x196)
    /usr/local/go/src/encoding/xml/marshal.go:520 +0x60f
encoding/xml.(*printer).marshalStruct(0xc00018a120, 0xc0000b6120, 0x78cb80, 0xc000178390, 0x199, 0x16, 0x200000003)
    /usr/local/go/src/encoding/xml/marshal.go:952 +0x2a6
encoding/xml.(*printer).marshalValue(0xc00018a120, 0x7400a0, 0xc000178390, 0x16, 0x0, 0x0, 0x88, 0x780180)
    /usr/local/go/src/encoding/xml/marshal.go:530 +0x75d
encoding/xml.(*Encoder).Encode(0xc00018a120, 0x7400a0, 0xc000178390, 0xc0001c7000, 0x31)
    /usr/local/go/src/encoding/xml/marshal.go:162 +0xbb
encoding/xml.MarshalIndent(0x7400a0, 0xc000178390, 0x0, 0x0, 0x7dcec3, 0x2, 0x0, 0x0, 0xed, 0x0, ...)
    /usr/local/go/src/encoding/xml/marshal.go:129 +0x159

So the problem here is twofold:

  1. The id attribute is somehow broken in the request. As a consequence the server responds without the id attribute.
  2. xml.MarshalIndent panics when the id attribute is not present in the response. It may not be present due to problem 1 or because it was deliberately left out in the request.

Bottom line:

Here is the simplified and sanitized WSDL for reference:

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:tns="http://www.example.com/service/ws/V001"
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:wsg="http://www.example.com/service/v1"
                  xmlns:ns="http://schemas.xmlsoap.org/soap/encoding/"
                  name="ServiceV001"
                  targetNamespace="http://www.example.com/service/ws/V001">
    <wsdl:types>
        <xsd:schema xmlns="http://www.example.com/service/v1"
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                    xmlns:wsg="http://www.example.com/service/v1"
                    targetNamespace="http://www.example.com/service/v1"
                    elementFormDefault="qualified"
                    attributeFormDefault="unqualified">
            <xsd:element name="operationX">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="request" type="wsg:operationXRequestType"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <xsd:element name="operationXResponse">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="response" type="wsg:operationXAckType"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <xsd:complexType name="operationXRequestType">
                <xsd:complexContent>
                    <xsd:extension base="requestType">
                        <xsd:sequence>
                            <xsd:choice>
                                <xsd:element ref="orderNr"/>
                            </xsd:choice>
                        </xsd:sequence>
                    </xsd:extension>
                </xsd:complexContent>
            </xsd:complexType>
            <xsd:complexType name="operationXAckType">
                <xsd:complexContent>
                    <xsd:extension base="ackType">
                    </xsd:extension>
                </xsd:complexContent>
            </xsd:complexType>
            <xsd:complexType name="requestType" abstract="true">
                <xsd:complexContent>
                    <xsd:extension base="messageType">
                        <xsd:sequence>
                            <xsd:element ref="userId"/>
                        </xsd:sequence>
                    </xsd:extension>
                </xsd:complexContent>
            </xsd:complexType>
            <xsd:complexType name="ackType" abstract="true">
                <xsd:complexContent>
                    <xsd:extension base="messageType">
                        <xsd:sequence>
                            <xsd:element ref="success"/>
                        </xsd:sequence>
                    </xsd:extension>
                </xsd:complexContent>
            </xsd:complexType>
            <xsd:complexType name="messageType" abstract="true">
                <xsd:attribute name="id" type="idType" use="optional"/>   <!-- HERE IS THE ATTRIBUTE -->
            </xsd:complexType>
            <xsd:element name="success" type="xsd:boolean">
            </xsd:element>
            <xsd:element name="userId">
                <xsd:simpleType>
                    <xsd:restriction base="xsd:int">
                        <xsd:totalDigits value="6"/>
                    </xsd:restriction>
                </xsd:simpleType>
            </xsd:element>
            <xsd:element name="orderNr" type="orderNrType">
            </xsd:element>
            <xsd:simpleType name="orderNrType">
                <xsd:restriction base="xsd:string">
                    <xsd:pattern value="[1-9]\d{25}"/>
                </xsd:restriction>
            </xsd:simpleType>
            <xsd:simpleType name="idType">
                <xsd:restriction base="xsd:string"/>
            </xsd:simpleType>
        </xsd:schema>
    </wsdl:types>

    <wsdl:message name="operationXRequest">
        <wsdl:part name="parameters" element="wsg:operationX"/>
    </wsdl:message>
    <wsdl:message name="operationXResponse">
        <wsdl:part name="parameters" element="wsg:operationXResponse"/>
    </wsdl:message>

    <wsdl:portType name="Wsg">
        <wsdl:operation name="operationX">
            <wsdl:input message="tns:operationXRequest"/>
            <wsdl:output message="tns:operationXResponse"/>
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="WsgSoapBinding" type="tns:Wsg">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

        <wsdl:operation name="operationX">
            <soap:operation soapAction="https://www.example.com/wsg-outbound/services/ServiceV001/operationX"/>
            <wsdl:input>
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>

    </wsdl:binding>
    <wsdl:service name="WsgService">
        <wsdl:port name="ServiceV001" binding="tns:WsgSoapBinding">
            <soap:address location="https://www.example.com/wsg-outbound/services/ServiceV001"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>
ieure commented 2 years ago

Thanks for the response. The issue here is this part of the XSD:

        <xsd:schema … attributeFormDefault="unqualified">

Which indicates that attributes shouldn't be qualified with the targetNamespace prefix. The previous version of gowsdl only sent unqualified attributes (which broke my usecase), and the current one only sends qualified ones (which broke yours).

Since these are defaults, I assume they can also change per xsd:attrubute, which is another thing that ought to get handled.

The fix is to add a conditional around these two lines in types_tmpl.go, based on the attributeFormDefault in the schema definition, and whatever specifies that at the xsd:attribute level.

This is a thing I could look at implementing, but don't have much bandwidth to take on at the moment. I see you've opened some PRs already, so maybe this is sufficient guidance for you to fix it.

w65536 commented 2 years ago

Thanks for your insights. I get what you are saying and it is helpful indeed. I guess I might be able to fix this, but I also need to check my time management. I will try to look into it, if I can find some time.

Do you have any insights whatsoever on problem 2: xml.MarshalIndent panics when the id attribute is not present in the response. It may not be present due to problem 1 or because it was deliberately left out in the request. What I am wondering: is this a problem of the code generated by gowsdl or is this due to a broken implementation of xml.MarshalIndent?

ieure commented 2 years ago

Do you have any insights whatsoever on problem 2: xml.MarshalIndent panics when the id attribute is not present in the response. It may not be present due to problem 1 or because it was deliberately left out in the request. What I am wondering: is this a problem of the code generated by gowsdl or is this due to a broken implementation of xml.MarshalIndent?

I'm not sure. The stacktrace you shared is entirely inside of encoding/xml, which suggests to me that it's a bug in Go itself. You might try filing a bug with them and seeing what they have to say.