Normally I only use this blog for political topics but what I spent the last two days learning is so under-documented on the Internet that I just had to write about it and publish it somewhere.
Step 0: Introduction to SOAP
So you’ve been tasked with writing a server implementing some dinosaur communication protocol that uses SOAP instead of defining a REST API. Maybe something like MSV3, the German standard for the pharma industry that allows pharmacies to send digital orders to their suppliers. Let’s say there’s a working reference implementation, but it uses an ancient version of Java and you’re really not sure if trying to bend the reference implementation to your will is the best way forward. We’re talking about a standardized protocol, so how hard can it be to write your own compliant server? Besides, you really want to learn how to implement a SOAP application server from scratch…
If you’ve never touched SOAP before, the first thing you might notice is that the documentation of whatever protocol you want to implement (like MSV3) might not say anything about how the data is transmitted. Maybe a few URLs the clients are supposed to call but absolutely nothing about how the data will look while going over the wire… What the heck? Well, turns out SOAP-based protocols are defined in an XML-based language called WSDL, which might seem weird when you’re used to reading REST API documentation, but it comes out to be a huge blessing, as there are tools that will generate a ton of your server’s code for you by reading those WSDL files.
In fact, you don’t need to understand anything at all about how the data will look while being transmitted. The auto-generated code will take care of absolutely everything up to the point where a method in your code will be called by the framework that corresponds to a command sent by a client. (Think remote procedure calls.) This is the one big advantage of SOAP/WSDL over REST APIs.
Oh, when I say auto-generated code I’m thinking of Java. It’s also possible with C# and seems like it might be possible with PHP, but I haven’t used those. Let me tell you though, if whatever language you wish to write your server in doesn’t have tools for generating code from WSDL (or doing something equivalent at run-time), it’s probably best to give up. SOAP itself isn’t necessarily that complicated of a protocol, and if you really want to do all that XML parsing yourself, have fun, but using e.g. Java and having so much of the code auto-generated for you is obviously much more convenient. Imagine you want to implement a REST API, but the programming language you want to use doesn’t even have an HTTP server library. You’d probably use a different language. This in turn is the big disadvantage of SOAP/WSDL: they lock you into using complex tools that aren’t available for every language.
Step 1: WSDL to Java
Anyhow, so how do we get all that code generated? Simple: install JDK 1.8 and use the wsimport tool found in the “bin” directory of the installation. (It’s not found in later JDK versions so do get 1.8.) The program takes a couple arguments; here’s how I used it to generate my MSV3 code:
for wsdl in wsdl/msv3/*.wsdl
do
name=${wsdl#wsdl/msv3/}
/c/Program\ Files/Java/jdk1.8.0_241/bin/wsimport.exe \
-encoding UTF-8 -extension -Xnocompile \
-s src/main/java \
-p org.mydomain.msv.msv3.v2 \
-wsdllocation "http://localhost:8089/msv3/v2.0/$name?wsdl" \
"$wsdl"
done
(That’s in Git Bash on Windows.)
The result is a bunch of Java files in the directory src/main/java/org/mydomain/msv/msv3/v2
which define the basis of the MSV3 (v2) protocol. E.g. they define data models used for communication between client and server, and define interfaces declaring methods that correspond to the the commands which clients can send. (Again, a bit like RPC.)
Actually processing the commands and responding with appropriate responses is up to the “implementation” code you will add. For instance, the MSV3 protocol supports an “order” command to order goods from the pharmacy supplier, and it’s up to your implementation whether you’d like to store the incoming orders in some SQL database, or put them in flat files, or pass them on to some other program. We’ll see in a later step how to add such implementation code.
The URL with “localhost” passed via the -wsdllocation
switch might have to be changed before the program is deployed; I’m not sure if it needs to be the correct URL which clients will be calling in the end.
Among the WSDL files of MSV3 (v2) there’s two XSD files. One can pass them to wsimport using the -b
switch, but doing so didn’t make any difference in the output in my case. I don’t know in what cases it’s necessary to use that wsimport switch.
Step 2: Importing a SOAP library
OK so we have a bunch of Java source files now that define the protocol, but they need more than Java’s standard library to be able to be compiled and to work at runtime. If you’ve searched the web a bit for SOAP frameworks you might have come across Spring and some other fancy things but all you really need is the “JAX WS” library. It’s officially part of the Java EE platform, but you can actually just import it into a standard Java project by using Gradle or another build tool that can read Maven repositories.
Following is the line you need to add to the dependencies
section of your build.gradle
file:
implementation group: 'com.sun.xml.ws', name: 'jaxws-rt', version: '2.3.2-1'
That’s all. It took me some time figuring out because everyone on the web says something different as there are so many different libraries implementing SOAP web services in Java. Even JAX WS is technically a specification and not one implementation. I first thought I need to import 4 different libraries into my project for the auto-generated Java code to work. Omitting some of them would make the code not compile, omitting others would make the program refuse to start… I discovered more or less by chance that the above library contains everything I need.
Step 3: Implementing commands
The auto-generated Java code cannot, obviously, know what your server actually intends to do when a certain request defined by the protocol is invoked. Refer again to the example with the “order” command above. So how do we provide our actual implementation of a command? We simply create a class implementing a certain interface (which is found in the auto-generated sources) and annotate it to tell the framework what it is.
Most trivially, MSV3 supports a “test connection” command, which I’ll use for an example:
@WebService(
endpointInterface = "org.mydomain.msv.msv3.v2.Msv3VerbindungTesten",
targetNamespace = "urn:msv3:v2"
)
@BindingType(value = "http://java.sun.com/xml/ns/jaxws/2003/05/soap/bindings/HTTP/")
public class VerbindungTesten implements Msv3VerbindungTesten {
@Override
public void verbindungTesten(String clientSoftwareKennung)
throws DenialOfServiceException, ServerException, ValidationException
{
}
}
(Sorry about the German; it’s how the MSV3 protocol is defined. VerbindungTesten means TestConnection.)
As you see there’s a bunch of annotations explaining what this class represents, and that the service it provides should be served over HTTP. The Msv3VerbindungTesten
interface that it implements tells us what methods it must support. We can do whatever we want in the bodies of those methods. In this case it’s just one method, which can be empty, as it’s really just for testing whether a connection can be established to the server via the SOAP protocol. But if I wanted to I could, for instance, log to some file that the client was able to establish a connection.
Step 4: Starting the HTTP server
Our project now imports a SOAP library, contains the Java files that define the protocol we want to implement (MSV3), and contains the actual implementations of some or all commands. But how do we actually invoke an HTTP server in the main()
method of our program, which will act as the SOAP server? Simple:
public static void main(String[] args) {
Endpoint.publish(
"http://localhost:8089/msv3/v2.0/verbindungTesten",
new VerbindungTesten()
);
}
Call Endpoint.publish()
once for every protocol command you’ve implemented, and the SOAP framework will take care of starting a worker thread that listens for connections and dispatches to your implementations. (You don’t need to do anything at the end of your main method to prevent the application from quitting.)
Step 5: Rejoice!
We’re done! You’ve successfully created the “template” of a server application that implements a SOAP-based protocol defined in WSDL.
It was pretty frustrating to learn all this, and the complexity of SOAP is daunting when compared to simple REST APIs which just define a bunch of GET and POST requests and maybe use JSON or a self-baked XML-based format to transmit commands and/or data. But if you know how to get the code auto-generation to work, SOAP actually allows you to write less code in the end.
Happy hacking!