pythonweb-servicessoapsoap-clientsoappy

How to make a SOAP request by using SOAPpy?


I'm trying to call a method using a SOAP request by using SOAPpy on Python 2.7. The method is called GetCursOnDate and returns exchange rates. It takes a date parameter.

I'm using the following code:

from SOAPpy import SOAPProxy
import datetime

date=datetime.datetime.now()
namespace ="http://web.cbr.ru/"
url = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
server = SOAPProxy(url,namespace)
print (date)
server.GetCursOnDate(date)

But I get back an error:

Fault soap:Client: Server did not recognize the value of HTTP Header SOAPAction: GetCursOnDate.

Why do I get this error?


Solution

  • By default, SOAPpy uses the method name as the value of the HTTP SOAPAction header. If you run the following code you will see the value in the debug output:

    from SOAPpy import SOAPProxy
    from datetime import datetime
    
    input = datetime.now()
    namespace = "http://web.cbr.ru/"
    url = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
    
    proxy = SOAPProxy(url, namespace)
    proxy.config.debug = 1
    proxy.GetCursOnDate(input)
    

    The debug shows this:

    *** Outgoing HTTP headers ***************************
    POST /DailyInfoWebServ/DailyInfo.asmx HTTP/1.0
    Host: www.cbr.ru
    User-agent: SOAPpy 0.12.5 (http://pywebsvcs.sf.net)
    Content-type: text/xml; charset=UTF-8
    Content-length: 406
    SOAPAction: "GetCursOnDate"
    *****************************************************
    

    But the service expects another value (http://web.cbr.ru/GetCursOnDate) that you can set on the proxy with an additional parameter. The following code clears the error:

    from SOAPpy import SOAPProxy
    from datetime import datetime
    
    input = datetime.now()
    namespace = "http://web.cbr.ru/"
    url = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
    soapaction = "http://web.cbr.ru/GetCursOnDate"
    
    proxy = SOAPProxy(url, namespace = namespace, soapaction = soapaction)
    proxy.config.debug = 1
    proxy.GetCursOnDate(input)
    

    The debug will now show this:

    *** Outgoing HTTP headers ***************************
    POST /DailyInfoWebServ/DailyInfo.asmx HTTP/1.0
    Host: www.cbr.ru
    User-agent: SOAPpy 0.12.5 (http://pywebsvcs.sf.net)
    Content-type: text/xml; charset=UTF-8
    Content-length: 406
    SOAPAction: "http://web.cbr.ru/GetCursOnDate"
    *****************************************************
    

    But although that specific fault is gone, the call won't work. Because you will return with questions I thought I'll spare us some message exchanges and write directly the sequel. I mentioned my disappointment with Python's SOAP support on another occasion. For this post I'm adding all details here as a reference to myself and hopefully as help for other users. So here it goes...

    The call won't work because by default SOAPpy uses ordered parameters for the call. They are called v1, v2, v3 etc (see the MethodParameterNaming.txt file inside the SOAPpy download for more details). Your SOAP message will look like this:

    <SOAP-ENV:Body>
        <ns1:GetCursOnDate xmlns:ns1="http://web.cbr.ru/" SOAP-ENC:root="1">
            <v1>
            </v1>
        </ns1:GetCursOnDate>
    </SOAP-ENV:Body>
    

    This particular web service is expecting a parameter named On_date, not v1. You can try to fix it by using named parameters:

    from SOAPpy import SOAPProxy
    from datetime import datetime
    
    input = datetime.now()
    namespace = "http://web.cbr.ru/"
    url = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
    soapaction = "http://web.cbr.ru/GetCursOnDate"
    
    proxy = SOAPProxy(url, namespace = namespace, soapaction = soapaction)
    proxy.config.debug = 1
    proxy.GetCursOnDate(On_date = input)
    

    Your message now looks like this:

    <SOAP-ENV:Body>
        <ns1:GetCursOnDate xmlns:ns1="http://web.cbr.ru/" SOAP-ENC:root="1">
           <On_date>
           </On_date>
        </ns1:GetCursOnDate>
    </SOAP-ENV:Body>
    

    I think the value of the date is missing because the proxy has an issue with datetime objects. I didn't actually check to see what the issue is with that because there is another problem with this message: the web service expects <ns1:On_date> not <On_date>.

    This is where SOAPpy has some issues with namespaces. Using the original SOAPpy source code you can't change the namespaces. It seems that with most of Python's SOAP libraries you can only get your desired behavior by tweaking the code, which is what I did. I changed the SOAPBuilder.py file in some places where namespaces and tag prefixes were handled. See the original file here and the changed one here.

    Those changes allow me to use a SOAPpy Type for a finer control over the message:

    from SOAPpy import SOAPProxy
    from SOAPpy import Types
    
    namespace = "http://web.cbr.ru/"
    url = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
    soapaction = "http://web.cbr.ru/GetCursOnDate"
    input = Types.dateType(name = (namespace, "On_date"))
    
    proxy = SOAPProxy(url, namespace = namespace, soapaction = soapaction)
    proxy.config.debug = 1
    proxy.GetCursOnDate(input)
    

    Now I get the result I was looking for:

    <SOAP-ENV:Body>
        <ns1:GetCursOnDate xmlns:ns1="http://web.cbr.ru/" SOAP-ENC:root="1">
          <ns1:On_date xsi:type="xsd:date">2013-11-02Z</ns1:On_date>
        </ns1:GetCursOnDate>
    </SOAP-ENV:Body>
    

    The server returns the data on the above request.

    But even the above code can be improved. Notice that I'm setting the SOAPAction on the proxy for one particular operation: GetCursOnDate. If I want to use it with another operation I need another proxy or I need to modify this one. By using a WSDL.Proxy you get this automatically from the WSDL (it provides a SOAPProxy wrapper that parses method names, namespaces and SOAPActions from the WSDL of the web service).

    But even if this handles the SOAPAction automatically it doesn't pick up the namespace for the method. So I tweaked the WSDL.py file. Original version is here, changed file is here. The new client code now looks like this:

    from SOAPpy import WSDL
    from SOAPpy import Types
    
    # you can download this and use it locally for better performance
    wsdl = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?wsdl"
    namespace = "http://web.cbr.ru/"
    input = Types.dateType(name = (namespace, "On_date"))
    
    proxy = WSDL.Proxy(wsdl, namespace = namespace)
    proxy.soapproxy.config.debug = 1
    
    proxy.GetCursOnDate(input)
    

    For the examples above I used Python 2.6.6, SOAPpy 0.12.5, fpconst 0.7.2 and wstools 0.4.3. For others I think YMMV depending on the versions or the particular web service that you are calling. In conclusion I also want to mention that if you do a search on Google you'll notice that most people recommend SUDS instead of SOAPpy as a SOAP client so maybe have a look at that too. Good luck!