Can I use HTTP Post Requests for SOAP? - SOAP and Django - python

I am wondering if I could use simply use HTTP POST Requests in order to implement a SOAP API.
If so how should I format and treat the requests?

Yep, I have done this in certain cases where SOAPpy did not work with a given schema. This should get you started.
import httplib
from xml.dom import minidom
http.request("POST", "/path/to/my/webservice", body=xml, headers = {
"Host": "myservername",
"Content-Type": "text/xml; charset=UTF-8",
"Content-Length": len(xml)
})
print minidom.parseString(http.getresponse().read())
For the content string, I would use SoapUI to make the requests manually, and then mimic the XML.

Yes, see http://www.w3schools.com/SOAP/soap_httpbinding.asp
A SOAP method is an HTTP
request/response that complies with
the SOAP encoding rules.
HTTP + XML = SOAP
A SOAP request could be an HTTP POST
or an HTTP GET request.
You'll sometimes find other transport mechanisms used for quality of service reasons, for instance using a messaging framework.

I found a great example of this in the simple-salesforce package. The code is in login.py and pasted below
soap_url = 'https://{domain}.salesforce.com/services/Soap/u/{sf_version}'
domain = 'test' if sandbox else 'login'
soap_url = soap_url.format(domain=domain, sf_version=sf_version)
username = escape(username)
password = escape(password)
# Check if token authentication is used
if 'security_token' in kwargs:
security_token = kwargs['security_token']
# Security Token Soap request body
login_soap_request_body = """<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:login xmlns:n1="urn:partner.soap.sforce.com">
<n1:username>{username}</n1:username>
<n1:password>{password}{token}</n1:password>
</n1:login>
</env:Body>
</env:Envelope>""".format(username=username, password=password, token=security_token)
# Check if IP Filtering is used in cojuction with organizationId
elif 'organizationId' in kwargs:
organizationId = kwargs['organizationId']
# IP Filtering Login Soap request body
login_soap_request_body = """<?xml version="1.0" encoding="utf-8" ?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:partner.soap.sforce.com">
<soapenv:Header>
<urn:CallOptions>
<urn:client>RestForce</urn:client>
<urn:defaultNamespace>sf</urn:defaultNamespace>
</urn:CallOptions>
<urn:LoginScopeHeader>
<urn:organizationId>{organizationId}</urn:organizationId>
</urn:LoginScopeHeader>
</soapenv:Header>
<soapenv:Body>
<urn:login>
<urn:username>{username}</urn:username>
<urn:password>{password}</urn:password>
</urn:login>
</soapenv:Body>
</soapenv:Envelope>""".format(
username=username, password=password, organizationId=organizationId)
else:
except_code = 'INVALID AUTH'
except_msg = 'You must submit either a security token or organizationId for authentication'
raise SalesforceAuthenticationFailed(except_code, except_msg)
login_soap_request_headers = {
'content-type': 'text/xml',
'charset': 'UTF-8',
'SOAPAction': 'login'
}
response = requests.post(soap_url,
login_soap_request_body,
headers=login_soap_request_headers,
proxies=kwargs.get('proxies', None))

Related

Python soap requests (VIES)

I'm trying to send soap request to check if clients NIP is valid via Python
import requests
url="http://ec.europa.eu/taxation_customs/vies/checkVatTestService.wsdl"
headers = {"content-type" : "application/soap+xml"}
body = """
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
<soapenv:Header/>
<soapenv:Body>
<urn:checkVat>
<urn:countryCode>SK</urn:countryCode>
<urn:vatNumber>2020016449</urn:vatNumber>
</urn:checkVat>
</soapenv:Body>
</soapenv:Envelope>
"""
response = requests.post(url, data = body, headers = headers)
print(response)
I always get code 200 which means it's invalid, but their webpage says it is valid.
I've seen other methods, but I really need to use this one.
Your code will produce the following result:
<Response [200]>
which is correct, but it's not what you expect, because I believe there are a few misunderstandings at play here.
First of all, <Response [200]> is the response of the HTTP request, not the response of the web service. Don't confuse the two. You are making a SOAP call over HTTP. That HTTP response will have a HTTP response code, which in this case is 200, and a SOAP payload with the response of the web service inside, and that's what's you are mostly interested in.
Second, you are not calling the web service endpoint. You are making a request to the WSDL of the web service. The two are different. You could have noticed that by replacing the last line of code with print(response.text) and you would have seen the WSDL content there.
The endpoint of the service is found inside the WSDL and it is http://ec.europa.eu/taxation_customs/vies/services/checkVatTestService. This is where you have to POST the request.
Third, based on the documentation in the WSDL, this seems a test service that gives you back all the possible responses of the service depending on which VAT number you use:
<xsd:documentation>
Specific disclaimer for this service -----------------------------------------
Here is the list of VAT Number to use to receive each kind of answer :
100 = Valid request with Valid VAT Number
200 = Valid request with an Invalid VAT Number
201 = Error : INVALID_INPUT
202 = Error : INVALID_REQUESTER_INFO
300 = Error : SERVICE_UNAVAILABLE
301 = Error : MS_UNAVAILABLE
302 = Error : TIMEOUT
400 = Error : VAT_BLOCKED
401 = Error : IP_BLOCKED
500 = Error : GLOBAL_MAX_CONCURRENT_REQ
501 = Error : GLOBAL_MAX_CONCURRENT_REQ_TIME
600 = Error : MS_MAX_CONCURRENT_REQ
601 = Error : MS_MAX_CONCURRENT_REQ_TIME
For all the other cases, The web service will responds with a "SERVICE_UNAVAILABLE" error.
</xsd:documentation>
Use this code instead:
import requests
url="http://ec.europa.eu/taxation_customs/vies/services/checkVatTestService"
headers = {"content-type" : "application/soap+xml"}
body = """
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
<soapenv:Header/>
<soapenv:Body>
<urn:checkVat>
<urn:countryCode>SK</urn:countryCode>
<urn:vatNumber>2020016449</urn:vatNumber>
</urn:checkVat>
</soapenv:Body>
</soapenv:Envelope>
"""
response = requests.post(url, data = body, headers = headers)
print(response.text)
When you call it you will get (formatting not included):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>SERVICE_UNAVAILABLE</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
which is exactly what I expect because the documentation says it: For all the other cases, The web service will responds with a "SERVICE_UNAVAILABLE" error.
If I want a a valid response I only need to change the VAT number to this within the SOAP request message: <urn:vatNumber>100</urn:vatNumber> and now the service responds with (formatting not included):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
<countryCode>SK</countryCode>
<vatNumber>100</vatNumber>
<requestDate>2022-03-27+02:00</requestDate>
<valid>true</valid>
<name>John Doe</name>
<address>123 Main St, Anytown, UK</address>
</checkVatResponse>
</soap:Body>
</soap:Envelope>

Accessing Federal Data via SOAP Webservice using SUDS in Python

I am trying to access the Webservice of the Business Register of the Swiss Federal Government (https://www.bfs.admin.ch/bfs/de/home/register/unternehmensregister/unternehmens-identifikationsnummer/uid-register/uid-schnittstellen.assetdetail.1760903.html)
However their documentation is so complex and weird that I have no clue on how to build the request that it works.
I am rather familiar with REST APIs but SOAP is new to me.
Using SoapUI I managed to build a working request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:uid="http://www.uid.admin.ch/xmlns/uid-wse" xmlns:ns="http://www.ech.ch/xmlns/eCH-0108-f/3" xmlns:ns1="http://www.ech.ch/xmlns/eCH-0098-f/3" xmlns:ns2="http://www.ech.ch/xmlns/eCH-0097-f/2" xmlns:ns3="http://www.ech.ch/xmlns/eCH-0046-f/3" xmlns:ns4="http://www.ech.ch/xmlns/eCH-0044-f/4" xmlns:ns5="http://www.ech.ch/xmlns/eCH-0010-f/6" xmlns:ns6="http://www.ech.ch/xmlns/eCH-0007-f/6">
<soapenv:Header/>
<soapenv:Body>
<uid:Search>
<uid:searchParameters>
<ns:organisation>
<ns1:organisationIdentification>
<ns2:organisationName>Beekeeper</ns2:organisationName>
</ns1:organisationIdentification>
</ns:organisation>
</uid:searchParameters>
</uid:Search>
</soapenv:Body>
</soapenv:Envelope>
But I am failing to implement this request in Python. Whatever tutorials I look up i find things such as
request_data = client.factory.create('s1:CityWeatherRequest')
request_data.City = "Stockholm"
What would the methods be to build the request as above?
How do I create nested requests to SOAP?
You should have a look at pyxb
When you have built up Python bindings based on wsdl schema or xsd files, you can execute the code below to make the request. The wsld shows what your actions will be.
from pyxb.utils.six.moves.urllib import request as urllib_request
import pyxb.bundles.wssplat.soap11 as soapenv
# Create a SOAP envelope from XML
myenv = soapenv.Envelope(soapenv.Body(myxmlreq))
# Create URL, headers and body for the HTTP request
url="http://www.test.com"
headers = {'Content-Type': 'text/xml;charset=UTF-8',
'Accept-Encoding': 'gzip,deflate',
'SOAPAction': "http://test.com/API/Action", # See documentation to learn about the actions
'Connection':'close'} # set what your server accepts
body = myenv.toxml("utf-8")
# Create the request, send it and receive the response as XML
uri = urllib_request.Request(url,data=body,headers=headers)
rxml = urllib_request.urlopen(uri).read() # This is the response XML

Python + SOAP : The message with Action \'\' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher

Trying to access this soap webservice using python requests : http://bsestarmfdemo.bseindia.com/MFUploadService/MFUploadService.svc?WSDL
Here is my request :
import requests
url="http://bsestarmfdemo.bseindia.com/MFUploadService/MFUploadService.svc?WSDL"
#headers = {'content-type': 'application/soap+xml'}
headers = {'content-type': 'application/soap+xml'}
body = """<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
<soap:Header/>
<soap:Body>
<tem:getPassword>
<!--Optional:-->
<tem:UserId>1003201</tem:UserId>
<!--Optional:-->
<tem:MemberId>10032</tem:MemberId>
<!--Optional:-->
<tem:Password>xxxxxxxx</tem:Password>
<!--Optional:-->
<tem:PassKey>xxxxxx123</tem:PassKey>
</tem:getPassword>
</soap:Body>
</soap:Envelope>"""
response = requests.post(url,data=body,headers=headers)
print (response.content)
This the response :
b'Sendera:ActionNotSupportedThe message with Action \'\' cannot be processed at
the receiver, due to a ContractFilter mismatch at the
EndpointDispatcher. This may be because of either a contract mismatch
(mismatched Actions between sender and receiver) or a binding/security
mismatch between the sender and the receiver. Check that sender and
receiver have the same contract and the same binding (including
security requirements, e.g. Message, Transport,
None).'
If I understand correctly, I probably need to set the action in the request, but can't figure out how to do that. I'm entirely new to soap in python, please suggest how to fix this.
Thanks!
Just add the SOAPAction onto the header, for example:
headers = {'Content-type': 'text/xml;charset=UTF-8',
'SOAPAction': 'http://google.com/ISearchService/Find'
}

How do I add a factory created type as a header in suds?

I can't seem to get suds working with my setup. I have to pass a context, with a remote user set before I can use any of the functions in the API. What I tried to do was this:
client = Client(url, username=userid, password=password)
apiContext = client.factory.create("apiCallContext") # This is listed in the types
apiContext.remoteUser = "serviceAccount" # when I print the client
client.set_options(soapheaders=apiContext)
client.service.getActiveIPs()
Throughout the process, everything seems to be getting created correctly (if I print the client, I see all the functions and types, if I print apiContext, I see everything set correctly), but the headers don't actually seem to be getting set:
...
DEBUG:suds.client:sending to ($URL) message:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0=$NS
xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns1:Body>
<ns0:getActiveIPs/>
</ns1:Body>
</SOAP-ENV:Envelope>
DEBUG:suds.client:headers = {'SOAPAction': u'""',
'Content-Type': 'text/xml; charset=utf-8'}
DEBUG:suds.transport.http:sending:
URL:$URL
HEADERS: {'SOAPAction': u'""', 'Content-Type': 'text/xml; charset=utf-8',
'Content-type': 'text/xml; charset=utf-8', 'Soapaction': u'""'}
...
I'm not seeing the context anywhere in the headers, and the server is erroring out that there's no remote user set.
What am I doing wrong?
Without knowing the exact spec of the webservice you are working with, I can only hazard a guess at this answer, but the header looks similar to a web service I have used in the past. Have you tried creating the elements directly and passing them into the header thus?:
from suds.sax.element import Element
...
NS = ('h', SOME_NAMESPACE)
apiContext = Element('apiContext')
authcred = Element('authenticationCredential', ns=NS)
username = Element(userid, ns=NS).setText('USERNAME')
passw = Element(password, ns=NS).setText('PASSWORD')
authcred.append(username)
authcred.append(passw)
apiContext.append(authcred)
client.set_options(soapheaders=apiContext)
i.e. is the authentication part of the context object?

Add Header section to SOAP request using SOAPpy

I need to construct this SOAP query using python SOAPpy module:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<LicenseHeader xmlns="http://schemas.acme.eu/">
<LicenseKey>88888-88888-8888-8888-888888888888</LicenseKey>
</LicenseHeader>
</soap:Header>
<soap:Body>
<GetProductClassification xmlns="http://schemas.acme.eu/">
<GetProductClassificationRequest />
</GetProductClassification>
</soap:Body>
</soap:Envelope>
So I use this code:
from SOAPpy import WSDL
wsdlFile = 'https://example.comt/1.0/service.asmx?wsdl'
server = WSDL.Proxy(wsdlFile)
result = server.GetProductClassification();
The request generated is:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
>
<SOAP-ENV:Body>
<GetProductClassification SOAP-ENC:root="1">
</GetProductClassification>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
When I send request I get Object reference not set to an instance of an object. I think this might be because I don't have a header section with license key in my request.
How do I modify my code to add header section with LicenseHeader parameter?
I am not sure how to do this in SOAPpy but I do know how to do it in suds. SUDS does the same thing as SOAPpy but it is newer and is still supported. I don't think SOAPpy is supported anymore. Below show's the code to connect to a WSDL and send a soap request:
class MySudsClass():
def sudsFunction(self):
url = "http://10.10.10.10/mywsdl.wsdl"
# connects to WSDL file and stores location in variable 'client'
client = Client(url)
#I have no address set in the wsdl to the camera I connect to so I set it's location here
client.options.location = 'http:/10.10.10.11'
# Create 'xml_value' object to pass as an argument using the 'factory' namespace
xml_value = client.factory.create('some_value_in_your_xml_body')
#This send the SOAP request.
client.service.WSDLFunction(xml_value)
Put this in the script before you send the soap request and it will add any headers you want.
# Namespaces to be added to XML sent
wsa_ns = ('wsa', 'http://schemas.xmlsoap.org/ws/2004/08/addressing')
wsdp_ns = ('http://schemas.xmlsoap.orf/ws/2006/02/devprof')
# Field information for extra XML headers
message = 'mymessage'
address_txt = 'myheader_information'
# Soapheaders to be added to the XML code sent
# addPrefix allow's you to addc a extra namespace. If not needed remove it.
message_header = Element('MessageID', ns=wsa_ns).setText(message)
address_header = Element('Address', ns=wsa_ns).setText(address_txt).addPrefix(p='wsdp', u=wsdp_ns)
header_list = [message_header, address_header]
# Soapheaders being added to suds command
client.set_options(soapheaders=header_list)
This will allow you to add in wsa encding that makes the XML understand:
# Attribute to be added to the headers to make sure camera verifies information as correct
mustAttribute = Attribute('SOAP-ENV:mustUnderstand', 'true')
for x in header_list:
x.append(mustAttribute)
If you use something like this you will be able to add any headers, namespaces etc. I have used this and it worked perfectly.
To add the license header in SUDS add:
license_key = Element('LicenseKey', ns=some_namespace).setText('88888-88888-8888-8888-888888888888')
license_header = Element('LicenseHeader', ns=some_namespace).insert(license_key)
license_attribute = Attribute(xmlns, "http://schemas.acme.eu/")
license_header.append(license_attribute)

Categories