The WSDL is not the SOAP web service

Image by theresaharris10 on pixabay

I’m one of the users on Stack Overflow that follows the [soap] tag and, from time to time, someone inevitably asks if a SOAP web service can function without a WSDL, or why is the SOAP service still working even after the WSDL was moved some place else or can’t be accessed anymore, or something along those lines. The questions are asked by people that are not familiar with SOAP and WSDL and don’t fully grasp the two concepts yet.

I’ve answered such questions a few times before and thought that it’s about time to write a more detailed explanation around these issues. If I see the same questions asked again, I’ll just copy-paste the link to this article.

But first, what is a web service anyway?

The World Wide Web Consortium defines a web service as “a software system designed to support interoperable machine-to-machine interaction over a network”. Basically, it’s one device (usually called a server) that offers a service to other devices (usually called clients), by transferring data over the network in all sorts of formats, like for example as XML or JSON.

When researching web services online, you will find mainly two flavors of services:

SOAP web services come packaged with this thing called a WSDL, and it seems people find it harder to understand how this couple works, as opposed to how RESTful web services work.

Because of that, I’m going to start my explanations about SOAP+WSDL by using a simple RESTful web service as a guinea pig. Well… maybe not exactly a RESTful web service, but that thing that everyone is familiar with and calls REST: a Web API, or otherwise put, a bunch of URL addresses where you can send JSON data and get back a JSON response, the kind of “web service” mainly driven by documentation that tells you how to interact with it. So if you want to tell me that my guinea pig experiment isn’t a truly RESTful web service, because it stinks of RPC, isn’t hypermedia driven, and whatnot… don’t bother! I already agree with you. And because of that, I’m going to refer to it as a WebAPI in the rest of this article.

A simple WebAPI

People are confused when it comes to SOAP+WSDL, but somehow a WebAPI doesn’t pose a problem (being more familiar, I think).

So…

Consider a calculator service that allows you to do some math operations, like addition, subtraction, multiplication, etc. Let’s call it the “CalculatorService”. How do you interact with it? Simple. Read its documentation, right? Web pages of documentation or some PDF file that tells you how to use this WebAPI. Maybe something like this:

General:
This is the documentation for the CalculatorService, a service for doing simple math operations.

Root address:
http://calculator-service.com

Methods:
GET /operations/ — returns the list of mathematical operations supported by this WebAPI as a list of STRINGs
POST /operations/{op} — invoke an operation identified by {op}

Operation {op} in methods:
addition
subtraction
multiplication
division

Parameters:
firstNumber — first number for the math operation you are invoking. Type INTEGER. Mandatory.
secondNumber — second number for the math operation you are invoking. Type INTEGER. Mandatory.

Parameters format: XML

<params xmlns="http://calculator-service.com/ns/">
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>

Result:
result — the result of the math operation you invoked. Type INTEGER.

Result format: XML

<result xmlns="http://calculator-service.com/ns/">300</result>

And so on.

Sound familiar right?

Maybe the format of the data might be JSON (even more familiar) not XML as I’m using here to make a point later on, but this is something everyone has seen at some point or another. As a programmer, based on this documentation, you now have what you need to invoke the WebAPI.

A request on the wire might look like this:

POST /operations/addition
Host: http://calculator-service.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<params xmlns="http://calculator-service.com/ns/">
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>

And a response would look like this:

HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<result xmlns="http://calculator-service.com/ns/">300</result>

SOAP is a protocol

As I’ve already said above, this isn’t a proper RESTful web service, but this sort of WebAPI falls into one of the two popular approaches for building a web service: the REST style. REST itself is an architectural style for building applications. I can build my service however I want, as long as I respect REST’s architectural constraints (which I haven’t really done here, but, as I said, that’s not the point of this article :)). Many other WebAPIs will look like above and I have to read the documentation to know the details for how to invoke it. When it comes to SOAP web services though, there are some things that I need to write in a certain way and not another.

Unlike REST, which as I said is an architectural style, SOAP is a messaging protocol. A protocol is a set of rules that you have to follow. For example, SOAP says that the messages that go back and forth over the network need to have a specific format. They need to be wrapped in a SOAP envelope, like this one:

<SOAP-ENV:Envelope 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
...
</SOAP-ENV:Header>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The header is optional while the actual parameters for the call need to be placed inside the body section of the envelope. I can’t just send my XML like this to a SOAP service:

<params xmlns="http://calculator-service.com/ns/">
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>

I need to wrap this in the SOAP envelope, otherwise I’m not respecting the SOAP protocol, and the server will just hit me in the face with a SOAP Fault. I need to do something like this instead:

<SOAP-ENV:Envelope 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<params xmlns="http://calculator-service.com/ns/">
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

But how exactly do I know how to send the parameters when calling this SOAP service? And where do I send them? To what address?

Changing the guinea pig WebAPI to make it more… SOAPy

The SOAP specification says that the service accepts requests only on POST, at one URL address. If you look at the above WebAPI, you see that it exposes multiple URLs, one for each operation:

POST /operations/{op} — invoke an operation identified by {op}

That’s another characteristic of REST; it can create a large address space for a whole bunch of resources. In my example above I wasn’t really using resources (another reason it’s a WebAPI and not a RESTful service :)) but just sending parameters to methods instead, like RPC. SOAP wants just one endpoint address, so somehow we need to differentiate between the operations. Let me butcher the WebAPI a bit further and make it look more SOAP friendly (or is it unfriendly ?!).

Let’s say I only expose one endpoint and send everything there:

POST /operation

I now need to find another way to specify the operation and a simple way to do it is to include it in the body of the request, like this:

<add xmlns="http://calculator-service.com/ns/">
<params>
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>
</add>

So now, my changed call looks like this:

POST /operation
Host: http://calculator-service.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<add xmlns="http://calculator-service.com/ns/">
<params>
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>
</add>

And I will still return the same response as before:

HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<result xmlns="http://calculator-service.com/ns/">300</result>

Great. Now, this looks almost like a SOAP call. If instead of just sending plain XML I send a SOAP envelope, then it really looks like a SOAP call:

POST /operation
Host: http://calculator-service.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<add xmlns="http://calculator-service.com/ns/">
<params>
<firstNumber>100</firstNumber>
<secondNumber>200</secondNumber>
</params>
</add>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Wrapping the response in a SOAP envelope might look like this now:

HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<result xmlns="http://calculator-service.com/ns/">300</result>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Now, if you paid attention, you saw that I changed the operation name from “addition” to “add”. The question is, how did I know how to call this SOAP(ish) WebAPI? Obviously, I need documentation for this service just like for the initial WebAPI. Some web pages, a document or a PDF file that tells me how to call the web service, what parameters and types it expects, how to wrap it in the envelope, and so on.

So a SOAP web service needs documentation just like the initial WebAPI does.

But as I said, SOAP is a protocol. That means strict rules. Break the rules and the thing doesn’t work anymore. If you want it to work, you must follow the rules. In other words, follow what the documentation says PRECISELY. And since these rules are known by everyone and must be followed precisely, wouldn’t it be nice if we could find a way to do it automatically, without bothering too much with them?

If you look at the examples above, you will see that they are HTTP requests and responses that carry some XML data around. “The meat” of the service is that it does some mathematical operations. “The meat” of the client is to select the numbers for the operation and do something with the result afterward. Everything else is boilerplate code that you must write just to move things around. And because SOAP is a protocol with strict rules, you can generate this boilerplate code based on those rules.

Enter WSDL now

The WSDL is like documentation that can be executed by a machine to generate code that knows how the SOAP service can be called, what parameters it expects and of what types, and what it returns, and of what type. Obviously, because this is documentation that a machine needs to read, you can’t have it as a PDF. You need a more strict format and structure, and a WSDL is an XML file.

This XML can be parsed and transformed into code; any kind of code. Want to call the SOAP web service from Java? Use a Java tool and feed it the WSDL to generate the Java boilerplate code for interacting with the service. Want to call it from C#? Use a .NET tool, feed it the WSDL and have it generate C# boilerplate code to interact with the web service. Use other tools, like SoapUI for example, to feed it the WSDL and have it generate sample requests and responses for you to test your service (as you will see later). And so on.

Let’s write a WSDL

At this point, I must mention this is not a WSDL tutorial, so I’m not going to go deep into the theory. There are SOAP frameworks that can generate the WSDL from the code you write for the web service, so you often don’t even need to bother with it. For this article, it suffices to say that the WSDL contains a part for defining things related to the SOAP calls, and one part where you define the types you send inside the SOAP body as a payload.

Here is the beast (only for the “add” operation just to keep things nice and simple — I leave the rest of the mathematical operation as an exercise for the reader):

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://calculator-service.com/ns/"
targetNamespace="http://calculator-service.com/ns/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<s:schema elementFormDefault="qualified"
targetNamespace=”http://calculator-service.com/ns/">
<s:complexType name="parameters">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="firstNumber" type="s:int" />
<s:element minOccurs="1" maxOccurs="1"
name="secondNumber" type="s:int" />
</s:sequence>
</s:complexType>

<s:element name="add">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="params" type="tns:parameters" />
</s:sequence>
</s:complexType>
</s:element>

<s:element name="result">
<s:simpleType>
<s:restriction base="s:int" />
</s:simpleType>
</s:element>

<s:element name="addResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1"
name="result" type="s:int" />
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</wsdl:types>

<wsdl:message name=”addSoapIn”>
<wsdl:part name="parameters" element="tns:add" />
</wsdl:message>
<wsdl:message name="addSoapOut">
<wsdl:part name="parameters" element="tns:result" />
</wsdl:message>

<wsdl:portType name="calculatorService">
<wsdl:operation name="add">
<wsdl:input message="tns:addSoapIn"/>
<wsdl:output message="tns:addSoapOut"/>
</wsdl:operation>
</wsdl:portType>

<wsdl:binding name="calculatorService"
type="tns:calculatorService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="add">
<soap:operation soapAction="add" style="document" />
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<wsdl:service name="calculator">
<wsdl:port name="calculatorService"
binding="tns:calculatorService">
<soap:address
location="http://calculator-service.com/operation" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

Now, if I feed this to a tool like SoapUI, it will know how the messages look like and can also generate a bunch of code for different technologies:

Wrapping up:

In ending, I want to write a few statements, just to drive the point further home:

There you have it. Now I have a link to use for all of these questions.