Making a SOAP request by using XML in Rails - ruby-on-rails

I want to make a request to a SOAP Web Service but I don't want to install any gems.
Is there any way to just make the request using plain XML?
I think it's trivial but there might be something I've missed, because all implementations/tutorials were using a gem.
I think that the SOAP response, can be handled also as a XML response right?
The request is this:
POST /services/tickets/issuer.asmx HTTP/1.1
Host: demo.demo.com
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<Tick xmlns="http://demo.com/test/test">
<Request>
<Username>string</Username>
<Password>string</Password>
<AcquirerId>int</AcquirerId>
<RequestType>string</RequestType>
<ExpirePreauth>unsignedByte</ExpirePreauth>
<BitPerSec>int</BitPerSec>
<Office>string</Office>
</Request>
</Tick>
</soap12:Body>
</soap12:Envelope>

You could do this:
def post_xml(path, xml)
host = "http://demo.demo.com"
http = Net::HTTP.new(host)
resp = http.post(path, xml, { 'Content-Type' => 'application/soap+xml; charset=utf-8' })
return resp.body
end
The XML response would be returned by this method.

Related

Issues Formatting a SOAP API with Savon gem

I was terrified this day would come.. dealing with SOAP API's...
This is a whole new realm for me, Ive done some digging with the SAVON gem but i cant seem to structure my call..
Essentially what I am trying to do is the following:
Step 1: Call the API to retrieve the LatestCallerVersion (API Version)
Step 2: Take the LatestCallerVersion and send a second request to the validation API to see if my customers are in a valid service area.
This is what I've come up with (but it crashes and burns hard)
caller_client = Savon.client(
wsdl: 'https://alarmadmin.alarm.com/webservices/CustomerManagement.asmx?WSDL '\
)
caller_response = caller_client.call(
:authentication,
message: {
user: 'xxxxxx',
password: 'xxxxxx',
two_factor_device_id: 'xxxxxx'
},
:body,
message: {
:get_latest_caller_version
}
)
Here is the SOAP request & response Doc for the GetLatestCaller call
POST /webservices/CustomerManagement.asmx HTTP/1.1
Host: alarmadmin.alarm.com
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Header>
<Authentication xmlns="http://www.alarm.com/WebServices">
<User>string</User>
<Password>string</Password>
<TwoFactorDeviceId>string</TwoFactorDeviceId>
</Authentication>
</soap12:Header>
<soap12:Body>
<GetLatestCallerVersion xmlns="http://www.alarm.com/WebServices" />
</soap12:Body>
</soap12:Envelope>
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<GetLatestCallerVersionResponse xmlns="http://www.alarm.com/WebServices">
<GetLatestCallerVersionResult>int</GetLatestCallerVersionResult>
</GetLatestCallerVersionResponse>
</soap12:Body>
</soap12:Envelope>
Here is the CheckCaller_V2 request & Response
POST /webservices/Validate.asmx HTTP/1.1
Host: alarmadmin.alarm.com
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Header>
<Authentication xmlns="http://www.alarm.com/WebServices">
<User>string</User>
<Password>string</Password>
<TwoFactorDeviceId>string</TwoFactorDeviceId>
</Authentication>
</soap12:Header>
<soap12:Body>
<CheckCoverage_v2 xmlns="http://www.alarm.com/WebServices">
<input>
<Address>
<Street1>string</Street1>
<Street2>string</Street2>
<SubCity>string</SubCity>
<City>string</City>
<SubState>string</SubState>
<State>string</State>
<Zip>string</Zip>
<CountryId>Canada</CountryId>
</Address>
<Network>Gsm</Network>
<Generation>FourG</Generation>
<CallerVersion>returned from GetLatestCaller call</CallerVersion>
</input>
</CheckCoverage_v2>
</soap12:Body>
</soap12:Envelope>
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<CheckCoverage_v2Response xmlns="http://www.alarm.com/WebServices">
<CheckCoverage_v2Result>NotChecked or FullCoverage or MostlyCovered or NoCoverage or Error or BadZip or PartialCoverage or NotSupported or NotOffered</CheckCoverage_v2Result>
</CheckCoverage_v2Response>
</soap12:Body>
</soap12:Envelope>
I am brand new to SOAP Any assistance here would be greatly appreciated. Please let me know if anything further is needed.
kindly use SOAP UI to check for the Request definition. On loading the the WSDL file in SOAP UI, I am able to get the following.
After that it becomes relatively easy to follow along. It seems all the operations has the same header, so I will go and create a client that looks like
caller_client = Savon.client(
wsdl: 'https://alarmadmin.alarm.com/webservices/CustomerManagement.asmx?WSDL',
env_namespace: :soapenv,
namespace_identifier: :web,
soap_header: {
'web:Authentication': {
'web:User': 'xxxx',
'web:Password': 'xxxxx',
'web:TwoFactorDeviceId': 'xxx'
}
})
And after that calling the individual operation becomes
caller_client.call(:get_latest_caller_version)
If you want to list all the possible operations, you can use
caller_client.operations
If you want to pass message to individual operation, use the following format
caller_client.call(:get_modem_serial_from_iccid, message: { iccid: 'xxxx' })

EWS managed-api gets 500 Internal Server Error when creating streaming subscription with affinity using OAuth

I have an application that uses EWS streaming subscriptions via the managed API (built from latest source on GitHub as NuGet version is out of date), and have been enhancing it to group subscriptions for mailboxes with the same GroupingInformation and ExternalEwsUrl user settings, to reduce the number of connections, as described in Maintain affinity between a group of subscriptions and the Mailbox server in Exchange.
I am also introducing modern authentication for Exchange Online: Authenticate an EWS application by using OAuth
I have only been testing the changes on a relatively small Azure tenant. When I try to create a subsequent subscription on a group, it works perfectly with basic authentication, but with OAuth authentication, it always fails with HTTP error 500. The EWS error message is "Request failed because EWS could not contact the appropriate CAS server for this request".
I include an excerpt from the XML trace, when using OAuth, for the request and response for the first subscription on the anchor mailbox, then the request and failed response for the second subscription. The GroupingInformation value for the two mailboxes was "VE1PR03" when these requests were made.
It is not obvious how the use of OAuth should affect the routing of the requests.
POST /EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: MyApp/2.2.0.20149 (ExchangeServicesClient/2.2.1.0)
Accept-Encoding: gzip,deflate
X-AnchorMailbox: user1#xyz.onmicrosoft.com
X-PreferServerAffinity: true
Authorization: Bearer ey..
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2016" />
<t:ExchangeImpersonation>
<t:ConnectingSID>
<t:SmtpAddress>user1#xyz.onmicrosoft.com</t:SmtpAddress>
</t:ConnectingSID>
</t:ExchangeImpersonation>
</soap:Header>
<soap:Body>
<m:Subscribe>
<m:StreamingSubscriptionRequest>
<t:FolderIds>
<t:DistinguishedFolderId Id="inbox">
<t:Mailbox>
<t:EmailAddress>user1#xyz.onmicrosoft.com</t:EmailAddress>
</t:Mailbox>
</t:DistinguishedFolderId>
</t:FolderIds>
<t:EventTypes>
<t:EventType>NewMailEvent</t:EventType>
</t:EventTypes>
</m:StreamingSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Encoding: gzip
Vary: Accept-Encoding
X-CalculatedFETarget: VI1P194CU002.internal.outlook.com
X-BackEndHttpStatus: 200,200
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Set-Cookie: exchangecookie=5bf5f04d41dd4205b2fd96f211c5b2b4; expires=Thu, 17-Jun-2021 14:06:18 GMT; path=/; secure; HttpOnly
Set-Cookie: X-BackEndOverrideCookie=VE1PR03MB5854.eurprd03.prod.outlook.com~1943309328; path=/; secure; HttpOnly
Server: Microsoft-IIS/10.0
X-FEProxyInfo: VI1P194CA0032.EURP194.PROD.OUTLOOK.COM
X-CalculatedBETarget: VE1PR03MB5854.eurprd03.prod.outlook.com
X-RUM-Validated: 1
x-ms-appId: f456225c-aef6-41fc-bbd5-8a5c9c9287d6
X-FromBackend-ServerAffinity: True
x-EwsHandler: Subscribe
X-AspNet-Version: 4.0.30319
X-BeSku: WCS5
X-DiagInfo: VE1PR03MB5854
X-BEServer: VE1PR03MB5854
X-Proxy-RoutingCorrectness: 1
X-Proxy-BackendServerStatus: 200
X-FEServer: VI1P194CA0032,LO2P265CA0158
X-Powered-By: ASP.NET
Date: Wed, 17 Jun 2020 14:06:18 GMT
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo MajorVersion="15" MinorVersion="20" MajorBuildNumber="3088" MinorBuildNumber="29" Version="V2018_01_08" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</s:Header>
<s:Body>
<m:SubscribeResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:SubscribeResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:SubscriptionId>JwB2ZTFwcjAzbWI1ODU0LmV1cnByZDAzLnByb2Qub3V0bG9vay5jb20QAAAADJevxSPm2ESq94+CIcSMEp28L5vHEtgIEAAAAONzlukW2B5KmN/hjFV/so0=</m:SubscriptionId>
</m:SubscribeResponseMessage>
</m:ResponseMessages>
</m:SubscribeResponse>
</s:Body>
</s:Envelope>
POST /EWS/Exchange.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept: text/xml
User-Agent: MyApp/2.2.0.20149 (ExchangeServicesClient/2.2.1.0)
Accept-Encoding: gzip,deflate
X-AnchorMailbox: user1#xyz.onmicrosoft.com
X-PreferServerAffinity: true
Cookie: X-BackEndOverrideCookie=VE1PR03MB5854.eurprd03.prod.outlook.com~1943309328
Authorization: Bearer ey..
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2016" />
<t:ExchangeImpersonation>
<t:ConnectingSID>
<t:SmtpAddress>user2#xyz.onmicrosoft.com</t:SmtpAddress>
</t:ConnectingSID>
</t:ExchangeImpersonation>
</soap:Header>
<soap:Body>
<m:Subscribe>
<m:StreamingSubscriptionRequest>
<t:FolderIds>
<t:DistinguishedFolderId Id="inbox">
<t:Mailbox>
<t:EmailAddress>user2#xyz.onmicrosoft.com</t:EmailAddress>
</t:Mailbox>
</t:DistinguishedFolderId>
</t:FolderIds>
<t:EventTypes>
<t:EventType>NewMailEvent</t:EventType>
</t:EventTypes>
</m:StreamingSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>
HTTP/1.1 500 Internal Server Error
X-CalculatedFETarget: VI1P194CU002.internal.outlook.com
X-BackEndHttpStatus: 500,500
X-FEProxyInfo: VI1P194CA0034.EURP194.PROD.OUTLOOK.COM
X-CalculatedBETarget: VE1PR03MB5854.eurprd03.prod.outlook.com
X-RUM-Validated: 1
x-ms-appId: f456225c-aef6-41fc-bbd5-8a5c9c9287d6
X-BeSku: WCS5
X-DiagInfo: VE1PR03MB5854
X-BEServer: VE1PR03MB5854
X-Proxy-RoutingCorrectness: 1
X-Proxy-BackendServerStatus: 500
X-FEServer: VI1P194CA0034,LO2P265CA0158
Content-Length: 839
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Date: Wed, 17 Jun 2020 14:06:18 GMT
Set-Cookie: exchangecookie=39b2d19d8e9740128573cb1af6358c33; expires=Thu, 17-Jun-2021 14:06:18 GMT; path=/; secure; HttpOnly
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">*</Action>
</s:Header>
<s:Body>
<s:Fault>
<faultcode xmlns:a="http://schemas.microsoft.com/exchange/services/2006/types">a:ErrorInvalidRequest</faultcode>
<faultstring xml:lang="en-US">Request failed because EWS could not contact the appropriate CAS server for this request.</faultstring>
<detail>
<e:ResponseCode xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ErrorInvalidRequest</e:ResponseCode>
<e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">Request failed because EWS could not contact the appropriate CAS server for this request.</e:Message>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
After further investigation, I have resolved this by simply using the same ExchangeService object for each subscription in the group (I had been creating a new ExchangeService for each subscription). The grouping now works with both OAuth and basic authentication. The article on maintaining affinity does say "Create and use one ExchangeService object for the rest of the procedure", and I should have taken that more literally!
Before creating each subscription, including the first, one does of course need to set ExchangeService.ImpersonatedUserId to the SMTP address of the relevant mailbox user, and after creating the first subscription, add an assignment of the X-BackendOverrideCookie cookie value from the first subscription response to the HttpHeaders.
I hope this is useful for anyone else who is working with streaming subscriptions.
To pass X-BackEndOverrideCookie to subsequent requests, either:
Use the same ExchangeService for each subscription in the same grouping. This handles X-BackEndOverrideCookie automatically.
Use a fresh ExchangeService, but manually copy X-BackEndOverrideCookie via ExchangeService's CookieContainer property.
I recommend the second approach for thread-safety. If your application is a long-running service, you will likely need a retry loop to deal with failed subscriptions.
To transfer X-BackEndOverrideCookie manually:
string backEndOverrideCookie =
service1.CookieContainer.GetCookies(service1.Url)["X-BackEndOverrideCookie"]?.Value;
...
if (!string.IsNullOrWhiteSpace(backEndOverrideCookie))
service2.CookieContainer.SetCookies(service2.Url, "X-BackEndOverrideCookie=" + backEndOverrideCookie);
Note: Assigning to Credentials resets the ExchangeService's CookieContainer, and for OAuth you will need to do this regularly. Fortunately there's a simple workaround:
var cookieContainer = service.CookieContainer;
service.Credentials = new OAuthCredentials(authenticationResult.AccessToken);
service.CookieContainer = cookieContainer;

wash_out | XML in payload has < > instead of < >

wash_out(0.9.0) has been very helpful for me in implementing a SOAP service in my Rails(3.1) app. One little problem I am facing is that for XML payload in the SOAP body, < > are getting replaced by
< >
Here's my code snippet
render :soap => "<person><firstname>larry</firstname></person>"
Output is
<?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" xmlns:tns="http://www.w3.org/2001/12/soap-envelope">
<soap:Body>
<tns:index_response>
<value xsi:type="xsd:string"><person><firstname>larry</firstname></person></value>
</tns:index_response>
</soap:Body>
</soap:Envelope>
Is this a bug or can I fix this by some configuration or additional code.
Kindly help.
Try this (didn't test it):
render :soap => "<person><firstname>larry</firstname></person>".html_safe
I was able to resolve this. There was 1 thing I was doing wrong.
I am using savon client to call my SOAP service. I was doing
client = Savon::Client.new(wsdl: "")
result = client.call(...)
puts result
This was displaying < and >
The right way to access the response content in my case is
result.body[:index_response][:value]
result.body returns a hash
With Nokogiri I could do doc = Nokogiri::XML(result.body.to_xml)
This works well with XML in payload.

Wrap TXMLData in SOAP Body and modify Content-Type in HTTP header

I have almost the same question as this, but I would need to send just [MyXML] in SOAP body.
That's like this:
POST /soap HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/octet-stream
SOAPAction: "getData"
Content-Length: 1664
Host: abc.com
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://xy.com/ns1">
<soapenv:Header>
<ns1:metadata>
<ns1:processGroup>PG</ns1:processGroup>
<ns1:processName>PN</ns1:processName>
<ns1:applReqID>445526687456</ns1:applReqID>
<ns1:receiver>XY</ns1:receiver>
<ns1:documentVersion>2<ns1:documentVersion>
</ns1:metadata>
</soapenv:Header>
<soapenv:Body>
<sh:StandardBusinessDocument>
<sh:StandardBusinessDocumentHeader>
<sh:HeaderVersion>1.0</sh:HeaderVersion>
...
</sh:StandardBusinessDocument>
</soapenv:Body>
</soapenv:Envelope>
Is it possible in delphi 2010 and how to do that?
Another question is how would I change Content-Type, as you can see the webservice requests to be application/octet-stream?
Use a Rio.OnBeforeExecute handler and you can substitute the XML with anything you'd like.

Making a SOAP / XML request from Rails

I have an xml object xml:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<GetLocations xmlns="http://clients.mindbodyonline.com/api/0_5_1">
<Request>
<SourceCredentials>
<SourceName>{SourceName}</SourceName>
<Password>{Password}</Password>
<SiteIDs>
<int>{SiteID}</int>
</SiteIDs>
</SourceCredentials>
<XMLDetail>Bare</XMLDetail>
<PageSize>10</PageSize>
<CurrentPageIndex>0</CurrentPageIndex>
<Fields>
<string>Locations.Name</string>
<string>Locations.City</string>
</Fields>
</Request>
</GetLocations>
</soapenv:Body>
</soapenv:Envelope>
and am trying to copy this request I found in an example:
POST https://clients.mindbodyonline.com/api/0_5_1/SiteService.asmx HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://clients.mindbodyonline.com/api/0_5_1/GetLocations"
Host: clients.mindbodyonline.com
Content-Length: 795
But I do not know how to include things like SOAPAction in my request using something like RestClient.
How do you include such parameters, or is there a different way to make this type of request?
Check out the example code they have written in ruby here.
You can use savon gem to do the request, you can have a look at this code sample that shows how to do that.

Resources