I'm trying to work with the SOAP API of a certain vendor (ExamOne). They have a wsdl, and I am trying to use Savon (2.2.0) to interface with them, and although I am reading the Savon doc, I cannot see how to get the XML output to match the sample request that ExamOne sent me.
For instance, ExamOne prescribes the following for the root node tag:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:eos="http://QuestWebServices/EOService">
...but Savon gives me the following:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://QuestWebServices/EOService" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
I'm sorry to ask such a dumb question, but I find the Savon documentation altogether unhelpful, and I am lost. Can anyone tell me how to correct the namespaces ('soapenv' instead of 'env')? Can anyone tell me how to get the root node to have the correct attributes?
TMI: Ruby v 1.9.3, Rails 3.2.13
Start with this snippet:
#!ruby
#
gem 'savon', '~> 2.0'
require 'savon'
client = Savon.client(
wsdl: 'https://wssim.labone.com/services/eoservice.asmx?WSDL',
env_namespace: :soapenv,
log: true,
log_level: :debug,
pretty_print_xml: true
)
p client.operations
response = client.call(:loop_back)
p response.to_hash
Working Code:
wsdl = Savon.client(
wsdl:APP_CONFIG['exam_one']['wsdl'],
env_namespace:'soapenv',
namespace_identifier:'eos',
logger:Logger.new("#{Rails.root}/log/exam_one_or01.log", 1, (2097152*10))
)
message = {
:username => APP_CONFIG['exam_one']["username"],
:password => APP_CONFIG['exam_one']["password"],
:destinationID => "C1",
:payload => self.to_s
}
#response = wsdl.call :deliver_exam_one_content, message:message, raise_errors:false
The solution:
The problem, it turns out, was that Savon was raising an error because of the 400 response, and as a consequence of the error, it was not logging its own request. (Poor design choice, I think.)
The solution was to include the option raise_errors: false in the params for call on the clientclient.
To get the namespaces I needed, I had to include: env_namespace:'soapenv', namespace_identifier:'eos'. (I know, I didn't mention the latter issue in my original question, but that's how to namespace items in the payload.)
(+1 to Steven for a correct demonstration of env_namespace.)
Related
I'm an algo-trader and Perl fan.
I want to create a client which connects to Binance Future Testnet and i decided to exploit the Binance API module developed for Perl.
Once the Binance::API module was installed (no errors or warnings occurred there) i dived into my script first lines of code as follows:
#!/bin/perl
use Binance::API;
#API di Binance-Testnet
my $api = Binance::API->new(
apiKey => 'my api',
secretKey => 'my secret key',
);
$api->account();
$api->exchange_info();
API KEY and SECRET KEY are taken from my Binance Future Testnet Account (freely available for all users), succesfully used via tradingview and its Pine script tool.
Unfortunatly i got the following error:
[Binance::API::Request::_exec] Unsuccessful request.
Status => 401,
Content => {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."} at C:/Strawberry/perl/site/lib/Binance/API/Request.pm line 107.
[Binance::API::Request::_exec] Unsuccessful request.
Status => 404,
Content => <html><body><h2>404 Not found</h2></body></html> at C:/Strawberry/perl/site/lib/Binance/API/Request.pm line 107.
Any idea on what went wrong with this? I don't want to use Python or C++ as I love Perl and its versatility.
If you look at Binance::API source code, you can see this module developed for Spot market. not Futures.
https://github.com/taskula/binance-perl-api/blob/master/lib/Binance/Constants.pm
BEGIN {
%constants = (
BASE_URL => $ENV{BINANCE_API_BASE_URL} || 'https://api.binance.com', #this endpoint is for spot
DEBUG => $ENV{BINANCE_API_DEBUG} || 0,
);
}
for Spot Testnet you can get API KEY from here:
https://binance-docs.github.io/apidocs/spot/en/#enabling-accounts
I think you may get confused with spot and futures. There are 4 different base URL for different markets:
Spot Production site: https://api.binance.com
Spot Testnet site: https://testnet.binance.vision
Futures Production site: https://fapi.binance.com
Futures Testnet site: https://testnet.binancefuture.com
I've used zeep against a SOAP-service, and it works perfectly. However, when this service is placed behind Gravitee API Manager, I'm unable to get it to work any longer, just get a 404 response.
Here's my code :
from zeep import Client, Settings
import base64,sys, logging, traceback
from requests import Session
from zeep.transports import Transport
import requests
from lxml import etree
wsdl= 'https://link_to_service_on_gravitee'
session = Session()
session.verify = False
session.headers['Api-Key']= 'xxxxx'
transport = Transport(session=session)
settings = Settings(raw_response=True, strict=False, xml_huge_tree=True)
client = Client(wsdl, transport=transport, settings=settings)
data = []
data.append(
{'ServerProcessId': 'GL07',
'OrderNumber': 1}
)
cdata = []
cdata.append(
{'Username': 'xxx',
'Client': 'yyy',
'Password': 'zzz'}
)
node = client.create_message(client.service, 'GetResult',input=data, credentials=cdata)
print('*** SOAP Message')
print(etree.tostring(node))
print('*** End SOAP Message')
response = requests.Response()
try:
response = client.service.GetResult(input=data, credentials=cdata)
print(response)
except Exception as e:
print(response.headers)
logging.error(traceback.format_exc())
When I test the message generated by Python (etree.tostring(node)) in SOAPUI, it works correctly. Also, if I alter the api-key, I get an error about authentication problems, so Gravitee seems to accept the key from my code.
But the response I get, with correct api-key, is always [404].
Got it to work when I downloaded the wsdl to a local file, but don't want to do this for every wsdl.
Any ideas ?
There are multiple reasons for getting a 404 from Gravitee:
* Did you create an API
* Did you create a simple plan for this API
* Did you deploy the API to the gateway.
Once all those steps are done, you should be able to consume your API.
Hope it helps,
Regards,
First off, let me apologize, because I don't know much about SOAP and most of what I'm saying is probably nonsense.
I upgraded some client-side code that was generated using gSoap 2.8.4 to gSoap 2.8.93
As far as I can tell, the program only sends one request to the server. Previously this request was wrapped in
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="bmf.gv.at:pkt/PKTSoap" xmlns:ns1="bmf.gv.at:pkt" xmlns:ns3="bmf.gv.at:pkt/PKTSoap12">
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
However with the code generated by the new version this envelope is missing. I understand that this is because the request is generated as a REST request instead of a SOAP one. I've found threads that talk about the //gsoap directive for the service setting the method-protocol to SOAP instead of POST, but I can only find these directives for requests to the sub-services /PKTSoap and /PKTSoap12, not to the root service bmf.gv.at:pkt, to which the request is sent. In fact that is not even called a service in the code, it's called a schema. And the requests to it are called top-level root elements of the schema. They are all automatically generated as REST requests.
My question is how can I instruct gSoap to generate all requests as SOAP1.1 requests? Any help whatsoever is greatly appreciated.
WSDL: https://pastebin.com/bmC8Hx6M
typemap.dat is the default one with the following appended:
ns1 = "bmf.gv.at:pkt"
ns2 = "bmf.gv.at:pkt/PKTSoap"
ns3 = "bmf.gv.at:pkt/PKTSoap12"
And I use the following commands to generate:
wsdl2h.exe -c -g -N ns %1.wsdl
soapcpp2.exe -c -C -I./import -1 %1.h
It is not clear from your question which request messages do not include the envelope.
Here is a quick way to test the generated source code, to verify that the SOAP1.1 envelope and body are included (I've used C++ here):
wsdl2h -L -g -N ns service.wsdl
soapcpp2 -1 -C -I import service.h
A small demo client, to test the request message:
#include "soapH.h"
#include "PKTSoap.nsmap"
int main()
{
struct soap *soap = soap_new1(SOAP_XML_INDENT);
_ns1__DatenpoolkontoErzeugen req;
_ns1__Verarbeitung res;
req.soap_default(soap);
soap_call___ns2__PKTDatenpoolkontoErzeugen(soap, "http://", NULL, &req, res);
}
Compiled with:
c++ -o service service.cpp soapC.cpp soapClient.cpp stdsoap2.cpp
Then run:
$ ./service
POST / HTTP/1.1
Host:
User-Agent: gSOAP/2.8
Content-Type: text/xml; charset=utf-8
Content-Length: 526
Connection: close
SOAPAction: "bmf.gv.at:pkt/PKTDatenpoolkontoErzeugen"
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="bmf.gv.at:pkt/PKTSoap" xmlns:ns1="bmf.gv.at:pkt" xmlns:ns3="bmf.gv.at:pkt/PKTSoap12">
<SOAP-ENV:Body>
<ns1:DatenpoolkontoErzeugen>
<ns1:Absender xsi:nil="true"/>
</ns1:DatenpoolkontoErzeugen>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This shows that bmf.gv.at:pkt requests are sent exactly as specified in the WSDL.
I have to make https calls to an api that appears to not have validated SSL certificate. I would still like to make calls to the api using the Http().singleRequest method of akka-http.
When I make a call, I however get the following error:
javax.net.ssl.SSLHandshakeException: General SSLEngine problem
When I make a call with curl, I get
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it.
The calls with curl however work if I had the flag --insecure.
In akka-http, I tried the following:
val badSslConfig = AkkaSSLConfig().mapSettings(
s => s.withLoose(
s.loose
.withAcceptAnyCertificate(true)
.withAllowLegacyHelloMessages(Some(true))
.withAllowUnsafeRenegotiation(Some(true))
.withAllowWeakCiphers(true)
.withAllowWeakProtocols(true)
.withDisableHostnameVerification(true)
.withDisableSNI(true)
)
)
val badCtx = Http().createClientHttpsContext(badSslConfig)
Http()
.singleRequest(
HttpRequest(
HttpMethods.POST,
uri = Uri("https://..."),
protocol = HttpProtocols.`HTTP/1.1`
),
connectionContext = badCtx
)
but I still get the same error.
What should I do to fix the issue?
PS: I understand (given the many warnings in akka-http docs) that it is something that I shouldn't do in production but I'd like this workaround to work for now...
I had similar problem some time ago and as far as I remember it had to do with this issue. Workaround for that problem is to have own implementation of SSLContext that will accept just anything. Implementation is pretty straightforward and the example can be found in the last comment of of issue linked above.
I am trying to test soap 1.2 services using RobotFramework. So far, we have only tested soap 1.1 services using suds library for RobotFramework, and suds is not compatible with soap 1.2.
Backwards compatibility is an option for the new services, but it would be better to have a more long-term solution. I am not an experienced programmer, though I can edit code if told what to edit and where.
What happens in the test we have for soap 1.2 services using suds is: suds is unable to interpret the response it gets from the webservice and gives this error: SAXParseException: :159:229: mismatched tag
The soap message is fine, there is no problem using it in SoapUI.
I have found some snippets online that suggest I could get suds library to work with soap 1.2 for my RobotFramework tests. But I have little programming experience and no idea how to incorporate those snippets in suds.
Someone commented on this snippet that this fixed his issue with RobotFramework and suds.
Is there someone out there willing to explain how I could make this work? I can't seem to figure it out on my own. Any suggestions would be greatly appreciated.
from suds.client import Client
from suds.bindings import binding
import logging
USERNAME = 'username'
PASSWORD = 'password'
# Just for debugging purposes.
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)
# Telnic's SOAP server expects a SOAP 1.2 envelope, not a SOAP 1.1 envelope
# and will complain if this hack isn't done.
binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope')
client = Client('client.wsdl',
username=USERNAME,
password=PASSWORD,
headers={'Content-Type': 'application/soap+xml'})
# This will now work just fine.
client.service.someRandomMethod()
snippet from: https://gist.github.com/kgaughan/858851
In short Suds does not support SOAP 1.2 bindings. Development has ceased quite some time ago. For this reason the SudsLibrary does not support it either.
Some of the differences I observed using an example service SOAP 1.1/1.2 are:
HTTP header Content-Type:
1.2 = "application/soap+xml"
1.1 = "text/xml".
HTTP header
1.2 = Action
1.1 = SOAPAction
Envelope Namespace
1.2 = "http://www.w3.org/2003/05/soap-envelope"
1.1 = "http://schemas.xmlsoap.org/soap/envelope/"
For each of these a seperate solution was implemented in the example below. The content type could be overwritten. The Action can be added but the SOAPAction can not be removed. The namespace can also be overwritten using the extension library. This should work for you if your service ignores the SOAPaction header attribute.
Test Case.robot
*** Settings ***
Library SudsLibrary
Library SudsLibraryExtension
Library Collections
*** Test Cases ***
TC
${BASE_URL} Set Variable http://www.holidaywebservice.com
${SERVICE} Create Dictionary
... name=HolidayService_v2
... wsdl=HolidayService2.asmx?WSDL
${PORT} Set variable HolidayService2Soap12
${METHOD} Set variable GetCountriesAvailable
Set Binding SOAP-ENV http://www.w3.org/2003/05/soap-envelope
Create Soap Client ${BASE_URL}/${SERVICE.name}/${SERVICE.wsdl}
Set Port ${PORT}
Set Headers Content-Type application/soap+xml
Set Headers Soapaction ${EMPTY}
Set Headers Action "${BASE_URL}/${SERVICE.name}/${METHOD}"
${result} Call Soap Method ${METHOD}
SudsLibraryExtension.py
import suds.bindings
from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError
class SudsLibraryExtension(object):
"""
Extension on the SudsLibrary
"""
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = 1.0
def __init__(self, LibraryName='SudsLibrary'):
"""SudsLibraryExtension can be imported with an optional argument.
- ``LibraryName``:
Default value for `LibraryName` is SudsLibrary if not given.
The name can by any Library Name that implements or extends the
SudsLibraryExtension.
"""
try:
self.SudsLibrary = BuiltIn().get_library_instance(LibraryName)
# This is useful for when you run Robot in Validation mode or load
# the library in an IDE that automatically retrieves the documen-
# tation from the library.
except RobotNotRunningError:
pass
def set_binding(self, binding, url):
"""Set Binding can be used to add a binding to the message.
Example Set Binding SOAP-ENV http://www.w3.org/2003/05/soap-envelope
"""
suds.bindings.binding.envns = (binding, url)