且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在 Python 中,如何使用字典为 Zeep 设置 _soapheaders?

更新时间:2022-10-20 13:03:34

我发现如果我们有一个有效且完整的 WSDL,使用 Zeep 会更容易.

需要一个没有命名空间的元素的简单 API 服务 WSDL 将导入一个没有命名空间的模式,如下所示:

</s:元素><s:序列><s:element minOccurs="1" maxOccurs="1" name="ApiResult" type="s:int"/></s:sequence></s:complexType></s:元素><s:序列><s:element name="api_key" type="s:string"></s:element></s:sequence></s:complexType></s:元素></s:schema></wsdl:types><wsdl:message name="ApiSoapIn"><wsdl:part name="parameters" element="tns:Api"/></wsdl:message><wsdl:message name="ApiSoapOut"><wsdl:part name="parameters" element="tns:ApiResponse"/></wsdl:message><wsdl:message name="ApiKeyHeader"><wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/></wsdl:message><wsdl:message name="PagerHeader"><wsdl:part name="PagerHeaderParam" ref="pager"/></wsdl:message><wsdl:portType name="ApiSoap"><wsdl:operation name="Api"><wsdl:documentation>这是一个测试 WebService.返回一个数字</wsdl:documentation><wsdl:input message="tns:ApiSoapIn"/><wsdl:output message="tns:ApiSoapOut"/></wsdl:操作></wsdl:portType><wsdl:binding name="ApiSoap" type="tns:ApiSoap"><soap:binding transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="Api"><soap:operation soapAction="http://tempuri.org/Api" style="document"/><wsdl:输入><soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/><soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/><soap:body use="literal"/></wsdl:input><wsdl:输出><soap:body use="literal"/></wsdl:输出></wsdl:操作></wsdl:binding><wsdl:service name="ApiTest"><wsdl:port name="ApiSoap" binding="tns:ApiSoap"><soap:address location="http://superpc:8082/"/></wsdl:port></wsdl:service></wsdl:定义>

使用 namespaceLessElement.xsd:

<s:element name="pager"><s:序列><s:element name="page" type="s:int"></s:element><s:element name="per_page" type="s:int"></s:element></s:sequence></s:complexType></s:元素></s:schema>

注意期望标头值的操作定义如何指向正确的消息:

<soap:operation soapAction="http://tempuri.org/Api" style="document"/><wsdl:输入><soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/><soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/><soap:body use="literal"/></wsdl:input><wsdl:输出><soap:body use="literal"/></wsdl:输出></wsdl:操作>

这些依次引用正确的元素:

<wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/></wsdl:message><wsdl:message name="PagerHeader"><wsdl:part name="PagerHeaderParam" ref="pager"/></wsdl:message>

您应该在 Web 服务的 WSDL 中检查该操作是否描述了两个标头,并且它包括两个元素的架构定义.在示例 WSDL 中,服务名称空间是 targetNamespace="http://tempuri.org/" 但这应该指向您的 Web 服务 URL.

因此假设您的 WSDL 有效且完整,我们需要定义指向 WSDL 的客户端,然后使用 _soapheaders 参数设置标头值,类似于我使用的方法 此处 但构建内容参考.Zeep 可以处理不同的命名空间,但我发现空命名空间有问题:

transport = Transport(cache=SqliteCache())self.Test = Client(wsdl='http://my-endpoint.com/production.svc?wsdl', transport=transport)# 标题对象apiKey_header = xsd.Element('{http://tempuri.org/}apiKey',xsd.ComplexType([xsd.Element('api_key', xsd.String())]))pager_header = xsd.Element('寻呼机',xsd.ComplexType([xsd.Element('页面', xsd.Integer()),xsd.Element('per_page', xsd.Integer())]))apiKey_header_value = apiKey_header( api_key=key)pager_header_value = pager_header( page=page, per_page=perpage)# 要求response = self.Test.service.Api( _soapheaders=[apiKey_header_value, pager_header_value] )logger.debug("Result={1}".format(response))# 打印:Result=2(或测试 API 发送的任何值)

生成的 XML 请求示例:

<soap-env:Header><ns0:apiKey xmlns:ns0="http://tempuri.org/"><api_key>1230011</api_key></ns0:apiKey><寻呼机><page>2</page><per_page>10</per_page></寻呼机></soap-env:Header><soap-env:Body><ns0:Api xmlns:ns0="http://tempuri.org/"/></soap-env:Body></soap-env:Envelope>

确保使用正确的 URL 定义具有命名空间的标头.

如果您仍然有问题,这可能意味着您的 WSDL 没有定义所有元素,或者它没有正确链接到外部 XSD.在这些情况下,一种选择是保存 WSDL 和链接的 XSD 的本地副本,然后编辑文件以修复引用,然后将 Zeep 指向该本地文件.

In working with a SOAP api, the wsdl spec describes the api key passed in the header in a complex namespaced structure as well as additional non-namespaced XML that relates to a paging mechanism for accessing bulk results successively:

Specification:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="https://webservice_address_here">
  <soapenv:Header>
    <ns:apiKey>
      <api_key>***</api_key>
    </ns:apiKey>
    <pager>
      <page>1</page>
      <per_page>100</per_page>
    </pager>
  </soapenv:Header>
</soapenv:Envelope>

The answer, How to set soap headers in zeep when header has multiple elements, describes a similar scenario, without the namespace "ns" but with "acm." I have not been successful in using this method.

This works, allowing access to the api but without the pager making it mostly useless for any methods that return more than 100 results:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    '{wsdl}AuthenticateRequest',
    xsd.ComplexType([xsd.Element("{wsdl}api_key", xsd.String())])
)

# Insert values into header placeholders
self._header_value = header(api_key=self.api_key)

This does not work:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    'Header',
    xsd.ComplexType([
        xsd.Element(
            '{wsdl}AuthenticateRequest',
            xsd.ComplexType([
                xsd.Element('{wsdl}api_key', xsd.String()),
            ])
        ),
        xsd.Element(
            'pager',
            xsd.ComplexType([
                xsd.Element('page', xsd.String()),
                xsd.Element('per_page', xsd.String()),
            ])
        ),
    ])
)

# ERROR HERE: Insert values into header placeholders
self._header_value = header(api_key=self.api_key, pager={'page':1,'per_page':100})

Error: TypeError: ComplexType() got an unexpected keyword argument 'api_key'. Signature: AuthenticateRequest: {api_key: xsd:string}, pager: {page: xsd:string, per_page: xsd:string}

This also does not work:

header = xsd.Element(
    '{wsdl}AuthenticateRequest',
    xsd.ComplexType([xsd.Element("{wsdl}api_key", xsd.String())]),
    xsd.Element(
        'pager',
        xsd.ComplexType([
            xsd.Element('page', xsd.String()),
            xsd.Element('per_page', xsd.String()),
        ])
    )
)

# ERROR HERE: Insert values into header placeholders
self._header_value = header(api_key=self.api_key, pager={"page":1,"per_page":100})

'pager' is not defined in the wsdl but the server expects that it could be there.

TypeError: ComplexType() got an unexpected keyword argument 'pager'. Signature: api_key: xsd:string

What is the simplest way using Zeep to set the namespace api_key and non-namespaced complex pager element?

I find it's easier to work with Zeep if we have a valid and complete WSDL.

A simple API service WSDL that expects an element without namespace would import a schema with no namespace like this:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://tempuri.org/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" >
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
      <s:import schemaLocation="namespaceLessElement.xsd"/>
      <s:element name="Api" minOccurs="0" maxOccurs="1">
      </s:element>
      <s:element name="ApiResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="ApiResult" type="s:int"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="apiKey">
        <s:complexType>
          <s:sequence>
            <s:element name="api_key" type="s:string"></s:element>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </wsdl:types>
  <wsdl:message name="ApiSoapIn">
    <wsdl:part name="parameters" element="tns:Api"/>
  </wsdl:message>
  <wsdl:message name="ApiSoapOut">
    <wsdl:part name="parameters" element="tns:ApiResponse"/>
  </wsdl:message>
  <wsdl:message name="ApiKeyHeader">
    <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
  </wsdl:message>
  <wsdl:message name="PagerHeader">
    <wsdl:part name="PagerHeaderParam" ref="pager"/>
  </wsdl:message>
  <wsdl:portType name="ApiSoap">
    <wsdl:operation name="Api">
      <wsdl:documentation>This is a test WebService. Returns a number</wsdl:documentation>
      <wsdl:input message="tns:ApiSoapIn"/>
      <wsdl:output message="tns:ApiSoapOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="ApiSoap" type="tns:ApiSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Api">
      <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
      <wsdl:input>
        <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
        <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="ApiTest">
    <wsdl:port name="ApiSoap" binding="tns:ApiSoap">
      <soap:address location="http://superpc:8082/"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

With namespaceLessElement.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:schema xmlns:s="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
  <s:element name="pager">
    <s:complexType>
      <s:sequence>
        <s:element name="page" type="s:int"></s:element>
        <s:element name="per_page" type="s:int"></s:element>
      </s:sequence>
    </s:complexType>
  </s:element>
</s:schema>

Note how the operation definition that expects header values points to correct messages:

<wsdl:operation name="Api">
  <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
  <wsdl:input>
    <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
    <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
    <soap:body use="literal"/>
  </wsdl:input>
  <wsdl:output>
    <soap:body use="literal"/>
  </wsdl:output>
</wsdl:operation>

and these in turn reference correct elements:

<wsdl:message name="ApiKeyHeader">
  <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
</wsdl:message>
<wsdl:message name="PagerHeader">
  <wsdl:part name="PagerHeaderParam" ref="pager"/>
</wsdl:message>

You should check in the WSDL of your web service that the operation describes both headers and that it includes a schema definition for both elements. In the example WSDL the service namespace is targetNamespace="http://tempuri.org/" but this should point to your web service URL.

So assuming your WSDL is valid and complete, we need to define the Client pointing to the WSDL and then set the header values using the _soapheaders parameter, similar to the method I used here but building the content reference. Zeep can take care of the different namespaces but I found issues with empty ones:

transport = Transport(cache=SqliteCache())
self.Test = Client(wsdl='http://my-endpoint.com/production.svc?wsdl', transport=transport)

# Header objects
apiKey_header = xsd.Element(
    '{http://tempuri.org/}apiKey',
    xsd.ComplexType([
        xsd.Element(
            'api_key', xsd.String()
        )
    ])
)

pager_header = xsd.Element(
    'pager',
    xsd.ComplexType([
        xsd.Element(
            'page', xsd.Integer()
        ),
        xsd.Element(
            'per_page', xsd.Integer()
        )
    ])
)

apiKey_header_value = apiKey_header( api_key=key)
pager_header_value = pager_header( page=page, per_page=perpage)

# Request
response = self.Test.service.Api( _soapheaders=[apiKey_header_value, pager_header_value] )

logger.debug("Result={1}".format(response))

# Prints: Result=2 (or whatever value the test API sends)

EDIT: Example of generated XML request:

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
   <soap-env:Header>
      <ns0:apiKey xmlns:ns0="http://tempuri.org/">
         <api_key>1230011</api_key>
      </ns0:apiKey>
      <pager>
         <page>2</page>
         <per_page>10</per_page>
      </pager>
   </soap-env:Header>
   <soap-env:Body>
      <ns0:Api xmlns:ns0="http://tempuri.org/"/>
   </soap-env:Body>
</soap-env:Envelope>

Make sure that the header that has a namespace is defined with the correct URL.

If you still have problems it may mean your WSDL does not define all elements or that it's not linking correctly to external XSDs. In those cases one option is to save a local copy os the WSDL and linked XSDs, then edit the files to fix references and then point Zeep to that local file instead.