SOAP stands for a Simple Object Access Protocol. And, indeed, the protocol is very simple and extensible, since it only defines the container parts of the message being transferred, such as, body, header, and fault. But it does not strictly define format or validation rules for the content of the message apart from requesting the message to be XML-compatible.
Also, SOAP has several low-level protocol bindings such as, SOAP-over-HTTP, SOAP-over-SMTP, and SOAP-over-JMS. HTTP binding is the most popular choice for SOAP-based web services.
Usually, a SOAP web service is associated with a Web Service Definition Language (WSDL) and probably a set of XML Schema Definition (XSD) that more precisely define the contents of the service operation requests and responses. But strictly speaking those are not required by the SOAP standard, even though they definitely help to understand the service input and output formats.
There is also a family of WS-* (WS-I, WS-Policy, WS-Addressing, WS-Security, and so on) standards that define different aspects of web service authentication, authorization, interoperability, notification, validation, and so on, which can enrich a SOAP message with additional headers, structures, and faults.
There are plenty of libraries available out there to support different WS-* and X-* flavors, but there is no single answer (at least in the open source world) to all of them.
In essence, any SOAP request is an HTTP POST
request containing a SOAP Envelope with message data and headers.
In this recipe, we will focus on constructing simple SOAP requests at the HTTP protocol level using the HTTPBuilder
library that we just encountered in the previous recipe, Issuing a REST request and parsing a response.
For testing purposes, we will use SOAP web services hosted at http://www.holidaywebservice.com. We will try to call the GetMothersDay
operation on the USHolidayDates
service. The following XML snippet is the SOAP request we will send to the service:
<?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'> <soap-env:Header/> <soap-env:Body> <GetMothersDay xmlns='http://www.27seconds.com/Holidays/US/Dates/'> <year>2013</year> </GetMothersDay> </soap-env:Body> </soap-env:Envelope>
As you can guess, it tries to retrieve a date of the Mother's Day for 2013.
Let's perform the following steps to construct our SOAP client for the holiday web service:
@Grab
and import the required classes as shown in the following code snippet:@Grab( group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.6' ) import static groovyx.net.http.Method.* import static groovyx.net.http.ContentType.* import groovyx.net.http.HTTPBuilder
HTTPBuilder
and point to our web service:def baseUrl = 'http://www.holidaywebservice.com/' + 'Holidays/US/Dates/USHolidayDates.asmx' def client = new HTTPBuilder(baseUrl)
def holidayBase = 'http://www.27seconds.com/Holidays' def holidayNS = "$holidayBase/US/Dates/" def soapAction = "$holidayBase/US/Dates/GetMothersDay" def soapNS = 'http://schemas.xmlsoap.org/soap/envelope/'
POST
a SOAP request to the service:def response = client.request( POST, XML ) { headers = [ 'Content-Type': 'text/xml; charset=UTF-8', 'SOAPAction': soapAction ] body = { mkp.pi(xml:'version="1.0" encoding="UTF-8"') 'soap-env:Envelope'('xmlns:soap-env': soapNS) { 'soap-env:Header'() 'soap-env:Body' { GetMothersDay('xmlns': holidayNS) { year(2013) } } } } }
println response
As you can guess, HTTPBuilder
just makes a POST
request. We also give a hint to HTTPBuilder
that we are going to send XML data to the service. When that hint is given, the body parameter is processed with the help of StreamingMarkupBuilder
(see the Constructing XML content recipe in Chapter 5, Working with XML in Groovy) and encoded.
Since the service, which we are trying to connect to, is based on SOAP 1.1, we need to add a special SOAPAction
header (in SOAP 1.2 that header is no longer needed). Also, the Content-Type
header is mandatory, because otherwise the web service will not recognize the type of content we are trying to submit.
The response object is of the GPathResult
type, which can be navigated, searched, and printed in the same way as any other XML structure in Groovy with the help of the GPath
expressions. For more information on XML processing, refer to Chapter 5, Working with XML in Groovy.
On the Groovy's website, you can find references to two other SOAP supporting libraries: GroovySOAP
and GroovyWS
. Both of them are considered deprecated in favor of the groovy-wslite
library (https://github.com/jwagenleitner/groovy-wslite) developed by John Wagenleitner. The library has a terrific support for SOAP features and is a bit less verbose than plain HTTPBuilder
.
The following script makes use of groovy-wslite
and does exactly the same as the script described previously:
@Grab('com.github.groovy-wslite:groovy-wslite:0.8.0') import wslite.soap.* def baseUrl = 'http://www.holidaywebservice.com/' + 'Holidays/US/Dates/USHolidayDates.asmx' def client = new SOAPClient(baseUrl) def holidayBase = 'http://www.27seconds.com/Holidays' def holidayNS = "$holidayBase/US/Dates/" def soapAction = "$holidayBase/US/Dates/GetMothersDay" def response = client.send(SOAPAction: soapAction) { body { GetMothersDay('xmlns': holidayNS) { year(2013) } } } println response.GetMothersDayResponse
3.145.35.194