Python Zeep - HTTP status 415 (no content available) - python

Hi I am using zeep to consume a soap based web service,
and I keep on getting HTTP status 415 error. I dig down a bit and use
Pycharm Debuggger and found that the reason was:
'Cannot process the message because the content type \'text/xml;
charset=utf-8 XaSOfalw: rtt; ___utmvmBfuwVEwB=yEnqIuCmRhw\' was
not the expected type \'text/xml; charset=utf-8\'.'
What is wrong with the content type? and how do I change it in Zeep?
I just created a simple test code which looks like this:
from zeep import Client
pretend_wsdl = 'https://pretendwsdl'
client = Client(wsdl=pretend_wsdl)
res = client.service.NameOfService()
print(res)
and get this error:
zeep.exceptions.TransportError: Server returned HTTP status 415 (no
content available)

I have solved the problem by using plugins in zeep client.
My code looks like this:
from zeep import Client
from zeep import Plugin
class MyLoggingPlugin(Plugin):
def ingress(self, envelope, http_headers, operation):
return envelope, http_headers
def egress(self, envelope, http_headers, operation, binding_options):
http_headers['Content-Type'] = 'text/xml; charset=utf-8;'
return envelope, http_headers
pretend_wsdl = 'https://pretendwsdl.com'
client = Client(wsdl=pretend_wsdl, plugins=[MyLoggingPlugin()])
res = client.service.NameOfService()
print(res)
I find it weird because the default content type of zeep is text/xml; charset=utf-8;
and the wsdl I'm using doesn't think that the content type from zeep is text/xml; charset=utf-8;
So I used zeep plugins to explicitly set the content type to text/xml; charset=utf-8; and it surprisingly works.

Related

requests.exceptions.HTTPError: 415 Client Error Unsupported Media Type when using python zeep

I am using python zeep for consuming a webservice. I had used SOAP UI and I am able to consume the webservice. when I am using the below code it generates HTTP Error. How can I see the SOAP request content to actually check what i am sending in the request.
class MyLoggingPlugin(Plugin):
def ingress(self, envelope, http_headers, operation):
return envelope, http_headers
def egress(self, envelope, http_headers, operation, binding_options):
http_headers['Content-Type'] = 'text/xml; charset=utf-8;'
return envelope, http_headers
requests.packages.urllib3.disable_warnings()
session = Session()
session.verify = False
session.auth = HTTPBasicAuth('xxxxxxx', 'xxxxx')
client =
Client('https://xxxx.com:44383/sap/bc/srt/rfc/sap/zws_send_emailid/101/zws_send_emailid/binding_1',
transport=Transport(session=session),plugins=[MyLoggingPlugin()])
Not sure if you still need the answer.
You can actually just modify your plugin to see the actual xml like this:
from lxml import etree
class MyLoggingPlugin(Plugin):
def ingress(self, envelope, http_headers, operation):
# to see whats coming in
print(etree.tostring(envelope, pretty_print=True))
return envelope, http_headers
def egress(self, envelope, http_headers, operation, binding_options):
http_headers['Content-Type'] = 'text/xml; charset=utf-8;'
# to see whats going out
print(etree.tostring(envelope, pretty_print=True))
return envelope, http_headers
hope this helps.

How to send a request using suds client with multiple elements in the request

I am having an issue sending a suds request.
I sent a request to a different method just using the following:
from suds.client import Client
client = Client(wsdlurl)
client.service.Login(name, employid)
This came back with the correct response as name and employid are direct children elements of Login.
But how can I send a request using the below:
<soapenv:Body>
<v12:getStuff>
<v12:stuffSelect>
<!--Optional:-->
<v12:stuffIDs>
<!--Zero or more repetitions:-->
<v12:num></v12:num>
</v12:stuffIDs>
</v12:stuffSelect>
</v12:getStuff>
</soapenv:Body>
</soapenv:Envelope>
The reason for this is so I can add a dynamic value into num
I've tried it like this:
return self.client.service.getStuff.stuffSelect.stuffIDs(**{'stuffID': stuff_id, })
But get this error
AttributeError: 'Method' object has no attribute 'stuffSelector'
I assume you are using https://bitbucket.org/jurko/suds. It is essential you know your wsdl interface; suds can provide it partly at run-time:
# ... 'client' via wsdl url, login
# get example
http_status, payload = client.service.your_wsdl_get_stuff_method()
stuffIDs = []
if http_status == 200:
for stuff_id in payload: # depending on your wsdl
stuffIDs.append(stuff_id)
# upload example
stuffSelect = client.factory.create('stuffSelect') # structure generated by suds from wsdl
stuffSelect.your_wdsl_stuff_ids_name = stuffIDs # (maybe debug to see your name)
params = foo, bar, stuffSelect
svr_response = client.service.your_wsdl_upload_method(*params)

requests library fails to POST properly but urllib succeeds [python]

I am trying to POST a query to the RSCB PDB web services, as outlined here.
I set up the url, and query as XML:
import urllib.request as urllib
import requests
url = "http://www.rcsb.org/pdb/rest/search"
queryText = """
<?xml version="1.0" encoding="UTF-8"?>
<orgPdbQuery>
<version>B0907</version>
<queryType>org.pdb.query.simple.ExpTypeQuery</queryType>
<description>Experimental Method Search: Experimental Method=SOLID-STATE NMR</description>
<mvStructure.expMethod.value>SOLID-STATE NMR</mvStructure.expMethod.value>
</orgPdbQuery>
"""
I then define two possible ways of POSTing this data:
def query_old_fashioned(url, query_xml):
req = urllib.Request(url, data=query_xml.encode())
f = urllib.urlopen(req)
result = f.read()
return result.decode()
def query_with_requests(url, query_xml):
response = requests.post(url, data=query_xml.encode())
return response.text
# result = query_old_fashioned(url, queryText)
# result = query_with_requests(url, queryText)
With the first function, using good old fashioned urllib.request, I get the correct result - a list of 4 character strings.
With the second function, which as far as I can tell is doing exactly the same thing, I get a JSP error message HTML returned. This is that error message when displayed in a browser:
type Exception report
message
description The server encountered an internal error that prevented it from fulfilling this request.
exception
java.lang.NullPointerException
java.util.StringTokenizer.<init>(StringTokenizer.java:199)
java.util.StringTokenizer.<init>(StringTokenizer.java:221)
org.rcsb.servlet.RestfulServiceServlet.doPost(RestfulServiceServlet.java:1371)
javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:86)
org.pdb.util.web.OutOfServiceFilter.doFilter(OutOfServiceFilter.java:91)
org.pdb.util.web.DOSFilter.doFilter(DOSFilter.java:158)
org.pdb.util.web.AntiRobotFilter.doFilter(AntiRobotFilter.java:29)
org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:176)
org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)
org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)
org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:394)
note The full stack trace of the root cause is available in the Apache Tomcat/7.0.61 logs.
I know a little JSP, but I haven not been able to glean a reason for the POST's failure from this error message, nor is it clear to me why requests is failing but the standard library's urllib is succeeding. I've even tried going through the source code of the requests library on github trying to find exactly how requests creates its requests, but I was not successful in this endeavour.
This is using Python 3. I first encountered this problem using Ubuntu, and have since reproduced it on Windows 10.
Any help would be really appreciated.
I managed to fix this.
I inspected the HTTP requests being sent, and saw that requests was sending this:
POST /pdb/rest/search HTTP/1.1
Host: www.rcsb.org
User-Agent: python-requests/2.8.1
Connection: keep-alive
Accept: */*
Content-Length: 316
Accept-Encoding: gzip, deflate
<?xml version="1.0" encoding="UTF-8"?>
<orgPdbQuery>
<version>B0907</version>
<queryType>org.pdb.query.simple.ExpTypeQuery</queryType>
<description>Experimental Method Search: Experimental Method=SOLID-STATE NMR</de
scription>
<mvStructure.expMethod.value>SOLID-STATE NMR</mvStructure.expMethod.value>
</orgPdbQuery>
...and urllib was sending this...
POST /pdb/rest/search HTTP/1.1
Accept-Encoding: identity
Content-Type: application/x-www-form-urlencoded
Content-Length: 316
User-Agent: Python-urllib/3.4
Connection: close
Host: www.rcsb.org
<?xml version="1.0" encoding="UTF-8"?>
<orgPdbQuery>
<version>B0907</version>
<queryType>org.pdb.query.simple.ExpTypeQuery</queryType>
<description>Experimental Method Search: Experimental Method=SOLID-STATE NMR</de
scription>
<mvStructure.expMethod.value>SOLID-STATE NMR</mvStructure.expMethod.value>
</orgPdbQuery>
There are a few headers different, and by playing around with them, I found that it was the Content-Type header which is needed in requests' request.
The following now works:
response = requests.post(
url,
data=query_xml.encode(),
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
Thanks to Philipp for running my original code and verifying that this was technically possible. I suspect he has a different version of requests than me.
On my Ubuntu machine it works fine.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import urllib.request as urllib
def query_old_fashioned(url, query_xml):
req = urllib.Request(url, data=query_xml.encode())
f = urllib.urlopen(req)
result = f.read()
return result.decode()
def query_with_requests(url, query_xml):
response = requests.post(url, data=query_xml.encode())
return response.text
def test():
url = "http://www.rcsb.org/pdb/rest/search"
query = """
<?xml version="1.0" encoding="UTF-8"?>
<orgPdbQuery>
<version>B0907</version>
<queryType>org.pdb.query.simple.ExpTypeQuery</queryType>
<description>Experimental Method Search: Experimental Method=SOLID-STATE NMR</description>
<mvStructure.expMethod.value>SOLID-STATE NMR</mvStructure.expMethod.value>
</orgPdbQuery>"""
print(query_old_fashioned(url, query))
print(query_with_requests(url, query))
if __name__ == '__main__':
test()
print("done")
Both print out the same. What exact version of python are you using? I use Python 3.4.3 on a Ubuntu 14.03

Get status code for request sent using pysimplesoap

I'm using Pysimplesoap for sending a data to the web-service. I want to know the status of the request code. I'm able to print a traceback using trace=True. Over there it does prints the status code and other response variables but how do I get the to store all the traceback into a variable and then check the status code for the same?
Here's my code :_
client = SoapClient(
location = url,
action = 'http://tempuri.org/IService_1_0/',
namespace = "http://tempuri.org/",
soap_ns='soap', ns = False,trace = True
)
data = {'AWB_Number' : '406438762211111', 'Weight':'0.023' ,'Length':'16.4','Height':'4.5','Width':'9.9'}
response= client.UpdateShipment(
ShipmentNumber = data['AWB_Number'],
Weight = Decimal(data['Weight']),
Length = Decimal(data['Length']),
Height = Decimal(data['Height']),
Width = Decimal(data['Width']) ,
InboundLane = "2",
SequenceNumber = "1",
)
I do get a traceback :-
Content-length: 526
Content-type: text/xml; charset="UTF-8"
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header/>
<soap:Body>
<UpdateShipment xmlns="http://tempuri.org/">
<SequenceNumber>1</SequenceNumber><Weight>0.023</Weight><Height>4.5</Height><Width>9.9</Width><Length>16.4</Length><ShipmentNumber>406438762211111</ShipmentNumber><InboundLane>2</InboundLane></UpdateShipment>
</soap:Body>
</soap:Envelope>
status: 200
content-length: 293
x-powered-by: ASP.NET
server: Microsoft-IIS/7.5
date: Sat, 23 Aug 2014 07:27:38 GMT
content-type: text/xml; charset=utf-8
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><UpdateShipmentResponse xmlns="http://tempuri.org/"><UpdateShipmentResult/></UpdateShipmentResponse></s:Body></s:Envelope>
===============================================================================
There's a status code mention there as 200, but I dont get to store this traceback into a variable to get to know the status code of it. A human need to intervene to have a look at the status code. How do my program gets to know the status code?
The SoapClient instance retains a .response attribute, containing information about the response. What that information is depends on the transport picked.
If you have just PySimpleSoap installed, the urllib2 library is picked and the status code is not part of the client.response attribute; the information is not retained from the actual response from urllib2, only the HTTP headers are preserved.
The pycurl transport gives you even less info; client.response is always an empty dictionary then.
Only if you also installed httplib2 will you get anything useful out of this; client.response is then set to a dictionary that includes a status code:
>>> import pysimplesoap
>>> client = pysimplesoap.client.SoapClient(wsdl='http://www.webservicex.net/stockquote.asmx?WSDL')
>>> response = client.GetQuote('GOOG')
>>> client.response
{'status': '200', 'content-length': '991', 'x-aspnet-version': '4.0.30319', 'vary': 'Accept-Encoding', 'server': 'Microsoft-IIS/7.0', '-content-encoding': 'gzip', 'cache-control': 'private, max-age=0', 'date': 'Sat, 23 Aug 2014 08:05:19 GMT', 'x-powered-by': 'ASP.NET', 'content-type': 'text/xml; charset=utf-8'}
>>> client.response['status']
'200'
Note that the value is a string, not an integer.
The httplib2 transport is picked by default if available.
As for the trace option; that sets up a logging module log, calls logging.basicConfig() with a log level and gets on with it. You could add a custom handler to the pysimplesoap.client.log object, but I wouldn't bother, really. If you see the status logged to the trace, then in all likelihood you are already using httplib2 and can access the status code more directly. For urllib2, for example, no status is logged either. That's because it is the client.response.items() values that are being logged here.
From the pysimplesoap source code, at line 261 of client, the send method is written which is used to send the http request from client's end
Taking cue from it,
def send(self, method, xml):
...
response, content = self.http.request(
location, 'POST', body=xml, headers=headers)
self.response = response
self.content = content
...
you should be able to access client.response to get the raw response for the request. From there, try doing a client.response.status, or do a dir(client.response) to find out any supportive method to get status code.
EDIT
As mentioned here in transport module, you can specifiy a library (like urllib2 or httplib2). to make it the default one to be picked up.

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?
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!
it looks like the targetNamespace is ignored but you can set a namespace per operation what works fine with soappy.
<operation name="createCall">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal" namespace="http://create.service/"/>
</input>
<output>
<soap:body use="literal" namespace="http://create.service/"/>
</output>
</operation>
of course you should use your namespace instead of http://create.service/

Categories