Visit Eneris Solutions for all your software development needs.
This work was sponsored by Eneris Solutions Oy.
Introduction to SOAP using Apache SOAP 2.1 for Java
Pablo Fraile Iglesias -
<pablo@eneris.com> <pescador@airtel.net>
March 26th, 2001
May 7th, 2001 - SSL additions
May 15th, 2001 - Minor corrections by Matthew Langham (thanks!)
This document tries to be a small introduction to the Soap protocol and interface programming using the toolkit provided by
the Apache project. This toolkit allows you to very easily program Soap communications using the Java language.
You should be familiar with Linux, java, client-server applications and public key cryptography.
Please, send me any corrections, comments or any other information about this document - Pablo. (email is at the beginning, after title)
SOAP, "Simple Object Access Protocol", is an XML based protocol that allows easy RPC over http connections making easier to reach hosts behind a firewall. On the other hand, as information arrives the server in text mode it is also easy to block Soap calls if needed by looking at http or Soap headers in the message.
It is possible to set up Soap to work with SSL to provide authentication to both client and server which makes the protocol very interesting to Business to Business (B2B) applications.
HTTP Server: Orion server for linux, version 1.4.7 (Orion/1.4.7)
SOAP Toolkit: provided by Apache Soap project, version 2.1. This is a toolkit that provides a Java API for the Soap protocol. At the moment of writing this document, API can be read from: http://www.geocities.com/monkiki5/doxygenDoc/html/. You can also build it yourself.
SOAP implements a standard client/server architecture. Process is initiated by the client and the server responds to the request.
If you think about programming a Soap procedure you will notice that it does not differ much to write a stand alone java procedure. In fact, the server function can be the same as if it was being used locally. You will only have to change the client (to specify the server used, function called and information encoding) and write a XML descriptor to the server so that it knows about the data types used in the transaction (deploy description).
Before starting to develop any Soap program we
must install the Soap toolkit to our http server. Please, read the
docs inside Soap package to do this as I will not describe it here.
I will use a modification of one of the
examples provided by the Apache implementation of Soap, the
addressbook. I will change it to add a new method, "delAddress"
and also to receive a more complex object, change it and send it
back. This second addition has nothing to do with an addressbook and
it is only used to illustrate how to send a more complex object than
just plain text or integer numbers.
We must tell the server about our server
processes, which type they are, which functions/methods we can call
and how to serialize the information. All this information goes in
the "DeploymentDescriptor.xml" file. Let's have a look at it:
<isd:service
xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:AddressFetcher">
<isd:provider type="java"
scope="Application"
methods="getAddressFromName addEntry getAllListings putListings
delAddress sendPabloObject">
<isd:java class="samples.addressbook.AddressBook"
static="false"/>
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
<isd:mappings>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-address-demo" qname="x:address"
javaType="samples.addressbook.Address"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
<isd:map
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-address-demo" qname="x:phone"
javaType="samples.addressbook.PhoneNumber"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
<isd:map
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-pablo_object-demo"
qname="x:pablo-object"
javaType="samples.addressbook.PabloObject"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
<isd:map
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-myObjectClass-demo"
qname="x:myObjectClass"
javaType="samples.addressbook.MyObjectClass"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
</isd:mappings>
</isd:service>
We must tell the server which is the name of our application. Here it is called AddressFetcher, but that is only a name:
<isd:service
xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:AddressFetcher">
We must also list the kind of application and methods the client can call and what class provides them. This is a Java Application and it has 6 methods. The application is contained in the java class samples.addressbook.AddressBook.
<isd:provider type="java"
scope="Application"
methods="getAddressFromName addEntry getAllListings putListings
delAddress sendPabloObject">
<isd:java class="samples.addressbook.AddressBook"
static="false"/>
</isd:provider>
Any data that is sent over the wire must be serialized and deserialized between
client and server. So we must next state the kind of data that is going to be
received by our server. Simple types such as integer, string, etc. must not be
stated because Apache SOAP already contains the classes and configuration to
automatically serialize and deserialize these. Complex data types such as java
objects NEED to be stated here along with a java class to serialize/deserialize
them. Usually you can use org.apache.soap.encoding.soapenc.BeanSerializer to
serialize/deserialize JavaBeans, which is an easy way to start.
The BeanSerializer is the easiest way to serialize Java objects that contain
other objects. Each object you wish to send in this way must be written as a
JavaBean (i.e. it must have have get and set methods for the contained
members).
This is the information for the class "Address":
<isd:map
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-address-demo" qname="x:address"
javaType="samples.addressbook.Address"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
We will have a detailed look at new class added called "PabloObject":
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="urn:xml-soap-pablo_object-demo" qname="x:pablo-object"
javaType="samples.addressbook.PabloObject"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
First line is standard:
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
Second line sets the name of the object as it must be in client, "urn:xml-soap-pablo_object-demo":
xmlns:x="urn:xml-soap-pablo_object-demo" qname="x:pablo-object"
Third line states the class which contains the object. This can be a JavaBean:
The next two lines are needed and must contain the classes used to serialize and deserialize the object. As our object is a JavaBean we can use the serializer provided by Apache Soap, "BeanSerializer":
Now the server has all the information about the methods a client can call. We just need to provide this information to the server. I assume that soap listener is installed in http://localhost:8080/soaptest
java org.apache.soap.server.ServiceManagerClient http://localhost:8080/soaptest/servlet/rpcrouter deploy samples/addressbook/DeploymentDescriptor.xml
We must redeploy this information to the server
whenever we add new method, change serialize information, etc. It is
also usually needed to restart the http server as it must be aware of
the changes and new classes it must load. So we must not forget to
restart our http server. It is done with "orionctl reinit"
with Orion server.
It takes a few seconds (5-10) until the http server is running and it can accept
Soap calls. If you happen to make a call during this time you will get a "Connection refused" error, try again!
Compared to the server implementation, where no actual SOAP specific
changes need to be made - compared to a "normal" class implementation - a
SOAP client needs to be SOAP aware - and changes to the client must be done
in order to enable remote calls and configure the serialization classes.
I will explain two new methods. First I added "delAddress" which only deletes
an entry in the addressbook. This is a very simple client (and server) but serves
to see the basic functionality.
Second I will comment on a method not related to addressbook although it is
written inside the provided sample. The method transmits a complex java
object, which the server then changes and returns the changed object back to the
client.
DelAddress.java: (client)
package samples.addressbook;
import java.io.*;
import java.util.*;
import java.net.*;
import org.w3c.dom.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.rpc.*;
/**
* delAddress will delete an entry in the addressbook
*
* 2001-03-13
*
* Author: Pablo Fraile Iglesias <pescador@airtel.net> <pablo@eneris.com>
*
*/
public class DelAddress
{
public static void main (String[] args)
{
System.out.println ("Running DelAddress...\n");
System.out.println("Number of arguments = " + args.length);
if (args.length != 2) {
System.err.println("Usage:");
System.err.println(" java " + DelAddress.class.getName() + " SOAP-router nameToDelete");
System.exit(1);
}
// Take the name from args
String nameToDelete = args[1];
// Take the URI from the encoding style. This is default URI
String encodingStyleURI = Constants.NS_URI_SOAP_ENC;
System.out.println("encodingStyleURI = " + encodingStyleURI);
// Take the URL for the SOAP listener from the args
URL soapURL = null;
try {
soapURL = new URL(args[0]); // First arg is number "0"
}
catch (MalformedURLException e) {
System.err.println("Caught an exception: " + e.getMessage());
return;
}
System.out.println("SoapURL = " + soapURL);
// don't know if I will need this
System.out.println("Constants.NS_URI_SOAP_ENC = " + Constants.NS_URI_SOAP_ENC);
All the code above has little to do with SOAP. Just takes the arguments and builds the url.
Now we create the Call object, which will have information about the server and parameters to give to our method on the server
// Let's create the call!
Call call = new Call();
// call.setSOAPMappingRegistry(smr);
call.setTargetObjectURI("urn:AddressFetcher");
call.setMethodName("delAddress");
call.setEncodingStyleURI(encodingStyleURI);
setTargetObjectURI is the name of the application as it is written in the deploy information. This must match. The information to the server is given via this deploy description and the information to the client is given via call.setTargetObjectURI.
setMethodName sets the name of the method we call in server. This method belongs to the application in setTargetObjectURI.
setEncondingStyleURI: just leave it as it is.
NOTE: We do not set any mapping information in this example. Because we are just using a basic type (the parameter send is just a String object) we do not need to set up any specific classes for the serialization. Apache SOAP handles this for us.
Vector params = new Vector();
params.addElement( new Parameter("nameToDelete", String.class, nameToDelete, null));
call.setParams(params);
It is necessary to create a Vector containing the parameters to send. In both examples I just use one parameter, but a Vector must be created. In this case the Vector will then only contain the 1 object.
We use params.addElement to add a parameter. We create the parameter in this call also.
First, we state the name of the parameter. I am not sure if first parameter needs to be of special name. I use the name which the server method uses to receive it but I think this is not needed.
Second is the class of the parameter, so we take the class of our java object.
Third, we give the name of our parameter. This is the name of the object we want to send. Our is the string containing the name to delete from the addressbook.
Last parameter is just null (don't ask why!)
We call.setParams to make them effective.
System.out.println("call object: " + call);
// Let's make the call
Response resp;
try {
System.out.println("Going to call.invoke(soapURL, \"\")");
resp = call.invoke(soapURL, "");
}
catch (SOAPException e) {
System.err.println("Caught an exception: " + e.getFaultCode() + ", " + e.getMessage());
return;
}
Time to call the remote method, call.invoke does this. We must use a try-catch in order not to loose any exception which could indicate that the remote method was not executed. Errors here should indicate some network problems. Errors later could indicate errors in Soap transaction.
// NEED TO CHECK THE RESPONSE AND LOOK for EXCEPTIONS!!!!
if (!resp.generatedFault()) {
Parameter ret = resp.getReturnValue();
System.out.println ("Deleted if existed");
First check whether the call succeeded. This means that the Soap transaction was understood by the server.
Next we just print a message indicating that the action was done as the server does not use any return value.
} else {
Fault fault = resp.getFault();
System.err.println("Generated fault: ");
System.out.println(" Fault Code = " + fault.getFaultCode());
System.out.println(" Fault String = " + fault.getFaultString());
If the transaction failed it is good to print the error messages.
getFaultCode indicates in which part the error was found but it is not very illustrative (at least for me).
getFaultString shows a more intuitive human readable error message.
}
System.out.println("\nFinishing...");
} // end of main ()
} // public class DelEntry
AddressBook.java (server)
This is the part of the server that is called remotely. It is just as if it was called locally. All the information about remote call is given to the server via the deploy descriptor.
public void delAddress(String nameToDelete) {
name2AddressTable.remove(nameToDelete);
}
This example servers to illustrate how to send a more complex object than before. The object is a JavaBean which includes some basic types inside, another JavaBean, and a Vector.
I include the source of the object to send as
well as the object inside. Note that both of them are JavaBeans so
that we can use the BeanSerializer to serialize/deserialize them.
PabloObject.java: (main object to be sent)
package samples.addressbook;
/**
* Complex object to be sent over Soap as an example - This must be a JavaBean
*
* Pablo Fraile Iglesias <pescador@airtel.net> <pablo@eneris.com>
*
*/
import java.util.*;
import java.lang.reflect.*;
import java.lang.System.*;
import java.lang.Object.*;
public class PabloObject {
private long id; // common types
private boolean used;
private String name;
private float value;
private MyObjectClass obj; // Object inside
private Vector oneVector; // Vector
public PabloObject() {
this.id = 0;
this.used = false;
this.name = "";
this.value = 0;
obj = new MyObjectClass();
obj.setSomefield("testobject " + name);
obj.setNumber((int)value);
}
public PabloObject(long id, boolean used,
String name, float value) {
this.id = id;
this.used = used;
this.name = name;
this.value = value;
obj = new MyObjectClass();
obj.somefield="testobject " + name;
obj.number = (int)value;
oneVector = new Vector();
oneVector.add(new Integer(111));
oneVector.add(new Integer(222));
oneVector.add(new Integer(333));
}
Two constructors are provided. Second one also populates the Vector.
public void print() {
System.out.println ("---------------------------");
System.out.println ("id : " + id);
System.out.println ("used : " + used);
System.out.println ("name : " + name);
System.out.println ("value : " + value);
System.out.println ("obj.somefield: " + obj.somefield);
System.out.println ("obj.number : " + obj.number);
System.out.println ("oneVector : " + oneVector);
System.out.println ("---------------------------");
}
public void setVector(Vector myVector) {
this.oneVector = myVector;
}
public Vector getVector() {
return oneVector;
}
We must provide get/set methods for each variable. This converts our object into a JavaBean.
public void setObj(MyObjectClass obj) {
this.obj = obj;
}
public MyObjectClass getObj() {
return obj;
}
public void setId (long id) {
this.id = id;
}
public long getId () {
return id;
}
public void setUsed (boolean used) {
this.used = used;
}
public boolean getUsed () {
return used;
}
public void setName (String name) {
this.name = name;
}
public String getName () {
return name;
}
public void setValue (float value) {
this.value = value;
}
public float getValue () {
return value;
}
} // end of the class PabloObject
MyObjectClass.java: (one object of this kind is inside the
main object to be sent. This is also a JavaBean)
package samples.addressbook;
/**
* Complex object to be sent over Soap as an example. This is an object to be inside a bigger object
*
* Pablo Fraile Iglesias <pescador@airtel.net> <pablo@eneris.com>
*
*/
import java.util.*;
public class MyObjectClass {
public MyObjectClass() {
}
public String somefield;
public int number;
public void setSomefield(String field) {
this.somefield = field;
}
public String getSomefield() {
return somefield;
}
public void setNumber(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
SendPabloObject.java: (client)
package samples.addressbook;
import java.io.*;
import java.util.*;
import java.net.*;
import org.w3c.dom.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;
/**
* sendPabloObject will send an object to the addressbook
*
* 2001-03-15
*
* Author: Pablo Fraile Iglesias <pescador@airtel.net> <pablo@eneris.com>
*
*/
public class SendPabloObject {
public static void main (String[] args) {
System.out.println("Running SendPabloObject...");
if (args.length != 1) {
System.err.println("Usage:");
System.err.println(" java " + SendPabloObject.class.getName() +" SOAP-router");
System.exit(1);
}
try {
System.out.println("Press ENTER to continue");
BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
is.readLine();
} catch (Exception e) {
}
It is useful to ask to strike a key before running anything in case you want to attach a debugger to the running process.
// let's create the object to send
PabloObject myObject = new PabloObject( 100, false, "ThisShouldBeTheName", (float)15.24);
Integer tmpInt[] = {new Integer(1), new Integer(2), new Integer(3), new Integer(4), new Integer(1)};
myObject.print();
This method is provides by the object PabloObject and it only prints the content of the object. This is also for debugging purposes.
// Take the URI from the encoding style. This is default URI
String encodingStyleURI = Constants.NS_URI_SOAP_ENC;
System.out.println("encodingStyleURI = " + encodingStyleURI);
// Serializer
SOAPMappingRegistry smr = new SOAPMappingRegistry();
BeanSerializer beanSer = new BeanSerializer();
// VectorSerializer vectorSer = new VectorSerializer();
Here begins the important part. We create two important objects.
smr will contain the information to serialize/deserialize our object.
beanSer is the method used to serialize/deserialize our object.
Note that VectorSerializer is commented as it is not needed due to Apache Soap already knows how to serialize vectors and arrays. Do not uncomment that if you want it to work!
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName("urn:xml-soap-pablo_object-demo", "pablo-object"),
PabloObject.class, beanSer, beanSer);
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName("urn:xml-soap-myObjectClass-demo", "myObjectClass"),
MyObjectClass.class, beanSer, beanSer);
We need to map the types with their serialize information. This is done using the mapTypes method. As we have two different objects we must give information about both of them.
urn:xml-soap-pablo_object-demo and pablo-object: this information must much the one provided in the deploy description to the server.
PabloObject.class: gets the information about what kind of java class is sent.
beanSer: indicates which instance of serializer must be used. This instance should have been created before. In this case we use the BeanSerializer for both serialize and deserialize. You can always use this serializer with JavaBeans.
// Take the URL for the SOAP listener from the args
URL soapURL = null;
try {
soapURL = new URL(args[0]); // First arg is number "0"
}
catch (MalformedURLException e) {
System.err.println("Caught an exception: " + e.getMessage());
return;
}
System.out.println("SoapURL = " + soapURL);
// Let's create the call
Call call = new Call();
call.setTargetObjectURI("urn:AddressFetcher");
call.setMethodName("sendPabloObject");
call.setEncodingStyleURI(encodingStyleURI);
call.setSOAPMappingRegistry(smr); // IMPORTANT NOT TO FORGET THIS
As in the previous example we create the call object and set its properties. Here it is very important to call method setSOAPMappingRegistry. If we do not do this all the information about serializing will be lost and will result in an exception.
Vector params = new Vector();
params.addElement( new Parameter("myPabloObject", PabloObject.class, myObject, null));
call.setParams(params);
We also set the parameter, which is the object we want to send. Same as before.
// Let's make the call
System.out.println("Call: " + call);
Response resp;
try {
System.out.println("Going to call.invoke(soapURL, \"\")");
resp = call.invoke(soapURL, "");
}
catch (SOAPException e) {
System.err.println("Caught an exception: " + e.getFaultCode() + ", " + e.getMessage());
return;
}
System.out.println("We got response!");
Make the call and check for errors in communication.
// NEED TO CHECK THE ANSWER
if (!resp.generatedFault()) {
Parameter ret = resp.getReturnValue();
Object myResponse = ret.getValue();
PabloObject other = new PabloObject();
other = (PabloObject)ret.getValue();
Now other contains the returned object. It is important to cast the returned value as it is a generic Object.
// I'll try to get a vector with params
Vector respParams = new Vector();
System.out.println("respParams(just after new ): " + respParams);
respParams = resp.getParams();
Never mind the upper source, it does not do anything useful.
other.print();
} else {
Fault fault = resp.getFault();
System.err.println("Generated fault: ");
System.out.println(" Fault Code = " + fault.getFaultCode());
System.out.println(" Fault String = " + fault.getFaultString());
If, for any reason, the call did not succeed we print the error message. Use both getFaultCode and getFaultString as they return different information about the failure.
}
}
} // end public class SendPabloObject
Next the server source is included. Not much to say about it...
AddressBook.java: (sendPabloObject method in the server)
public PabloObject sendPabloObject(PabloObject myObject) {
The method is declared the same way as if it was local. All the information needed to translate the Soap message into the parameters the method needs is available to the http server through the deploy description.
// I'll change a few things and give the object back
myObject.setId(666);
myObject.setUsed(true);
myObject.setName("Devil-1.5");
We use our methods get/set to change the values inside our object.
if (myObject.getValue() == (float)15.24) {
myObject.setName("It was 15.24");
myObject.setValue((float)315.24);
} else {
myObject.setName("It was not 15.24, " + "it was " + myObject.getValue());
myObject.setValue((float)3.9);
}
MyObjectClass tmpMyObject = new MyObjectClass();
tmpMyObject.setSomefield(myObject.getObj().getSomefield() + " : tmpMyObject this is somefield");
tmpMyObject.setNumber(555);
myObject.setObj(tmpMyObject);
This changes the information in the inside object.
// populate the vector
Vector myTmpVector = new Vector();
myTmpVector = myObject.getVector();
myTmpVector.remove(1);
myTmpVector.add(new Integer(777));
myObject.setVector(myTmpVector);
Here just removed one element from the list and added another one to check that the vector is correctly transmitted.
return myObject;
}
4.4
SOAP transactions. Information transmitted
It is very useful to debug the communication to use a graphical tool to watch Soap transactions over the network. We call it like this:
[pablo]$ java org.apache.soap.util.net.TcpTunnelGui 8081 localhost 8080 &
This creates a tunnel between port 8080 and
8081 so that all connections directed to port 8081 are logged and
forwarded to port 8080 in localhost. We can watch all the data going
in both directions.
We ask for the address of "John Doe" in our addressbook. Our client is samples.addressbook.GetAddress and we use the URL of our Soap listener which is http://localhost:8080/soaptest/servlet/rpcrouter. The way to invoke our program will be as follows. (I am using port 8081 instead 8080 to monitor the connection.)
[pablo]$ java samples.addressbook.GetAddress http://localhost:8081/soaptest/servlet/rpcrouter "John Doe"
[some debug information is returned here. Omitted]
123 Main Street
AnyTown, SS 12345
(800) 555-12
POST /soaptest/servlet/rpcrouter HTTP/1.0
Host: localhost:8081
Content-Type: text/xml; charset=utf-8
Content-Length: 480
SOAPAction: ""
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getAddressFromName xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<nameToLookup xsi:type="xsd:string">John Doe</nameToLookup>
</ns1:getAddressFromName>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
----------------------
HTTP/1.1 200 OK
Date: Mon, 26 Mar 2001 13:27:49 GMT
Server: Orion/1.4.7
Content-Length: 923
Set-Cookie: JSESSIONID=KEHMCAIJHLNG; Path=/
Cache-Control: private
Connection: Close
Content-Type: text/xml; charset=utf-8
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getAddressFromNameResponse xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xmlns:ns2="urn:xml-soap-address-demo" xsi:type="ns2:address">
<phoneNumber xsi:type="ns2:phone">
<areaCode xsi:type="xsd:int">800</areaCode>
<exchange xsi:type="xsd:string">555</exchange>
<number xsi:type="xsd:string">12</number>
</phoneNumber>
<streetNum xsi:type="xsd:int">123</streetNum>
<zip xsi:type="xsd:int">12345</zip>
<streetName xsi:type="xsd:string">Main Street</streetName>
<state xsi:type="xsd:string">SS</state>
<city xsi:type="xsd:string">AnyTown</city>
</return>
</ns1:getAddressFromNameResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
To see this it is better to copy&paste to emacs and set highlight and indent mode. Some data is nested.
We remove one address from the addressbook. Please note that the response does not contain any information about the transaction so we do not know if the address was actually removed. We can just know if the Soap message was correctly processed.
[pablo]$ java samples.addressbook.DelAddress http://localhost:8081/soaptest/servlet/rpcrouter "John Doe"
Running DelAddress...
[Debuging information omitted here]
Deleted if existed
Finishing...
POST /soaptest/servlet/rpcrouter HTTP/1.0
Host: localhost:8081
Content-Type: text/xml; charset=utf-8
Content-Length: 464
SOAPAction: ""
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:delAddress xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<nameToDelete xsi:type="xsd:string">John Doe</nameToDelete>
</ns1:delAddress>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
------------------------
HTTP/1.1 200 OK
Date: Mon, 26 Mar 2001 13:41:19 GMT
Server: Orion/1.4.7
Content-Length: 420
Set-Cookie: JSESSIONID=DKKDGBIJHLNG; Path=/
Cache-Control: private
Connection: Close
Content-Type: text/xml; charset=utf-8
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:delAddressResponse xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
</ns1:delAddressResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
We now send the complex object we had created and wait for an object of the same kind to be returned. I leave the debugging information although it is not very illustrative. The object is printed before being sent. The received object is printed as well.
[pablo]$ java samples.addressbook.SendPabloObject http://localhost:8081/soaptest/servlet/rpcrouter
Running SendPabloObject...
Press ENTER to continue
---------------------------
id : 100
used : false
name : ThisShouldBeTheName
value : 15.24
obj.somefield: testobject ThisShouldBeTheName
obj.number : 15
oneVector : [111, 222, 333]
---------------------------
encodingStyleURI = http://schemas.xmlsoap.org/soap/encoding/
SoapURL = http://localhost:8080/soaptest/servlet/rpcrouter
Call: [Header=null] [methodName=sendPabloObject] [targetObjectURI=urn:AddressFetcher] [encodingStyleURI=http://schemas.xmlsoap.org/soap/encoding/] [SOAPContext=[Parts={}]] [Params={[[name=myPabloObject] [type=class samples.addressbook.PabloObject] [value=samples.addressbook.PabloObject@1447ea40] [encodingStyleURI=null]]}]
Going to call.invoke(soapURL, "")
We got response!
respParams(just after new ): []
---------------------------
id : 666
used : true
name : It was 15.24
value : 315.24
obj.somefield: testobject ThisShouldBeTheName : tmpMyObject this is somefield
obj.number : 555
oneVector : [111, 333, 777]
---------------------------
We can see how the name has changed, the obj.somefield has been expanded with more text, and the list lost one old value and a new one has been added another one.
POST /soaptest/servlet/rpcrouter HTTP/1.0
Host: localhost:8081
Content-Type: text/xml; charset=utf-8
Content-Length: 1083
SOAPAction: ""
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:sendPabloObject xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<myPabloObject xmlns:ns2="urn:xml-soap-pablo_object-demo" xsi:type="ns2:pablo-object">
<vector xmlns:ns3="http://xml.apache.org/xml-soap" xsi:type="ns3:Vector">
<item xsi:type="xsd:int">111</item>
<item xsi:type="xsd:int">222</item>
<item xsi:type="xsd:int">333</item>
</vector>
<name xsi:type="xsd:string">ThisShouldBeTheName</name>
<used xsi:type="xsd:boolean">false</used>
<value xsi:type="xsd:float">15.24</value>
<obj xmlns:ns4="urn:xml-soap-myObjectClass-demo" xsi:type="ns4:myObjectClass">
<number xsi:type="xsd:int">15</number>
<somefield xsi:type="xsd:string">testobject ThisShouldBeTheName</somefield>
</obj>
<id xsi:type="xsd:long">100</id>
</myPabloObject>
</ns1:sendPabloObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
-----------------------
HTTP/1.1 200 OK
Date: Mon, 26 Mar 2001 13:51:01 GMT
Server: Orion/1.4.7
Content-Length: 1111
Set-Cookie: JSESSIONID=FCOHMEIJHLNG; Path=/
Cache-Control: private
Connection: Close
Content-Type: text/xml; charset=utf-8
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:sendPabloObjectResponse xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xmlns:ns2="urn:xml-soap-pablo_object-demo" xsi:type="ns2:pablo-object">
<vector xmlns:ns3="http://xml.apache.org/xml-soap" xsi:type="ns3:Vector">
<item xsi:type="xsd:int">111</item>
<item xsi:type="xsd:int">333</item>
<item xsi:type="xsd:int">777</item>
</vector>
<name xsi:type="xsd:string">It was 15.24</name>
<used xsi:type="xsd:boolean">true</used>
<value xsi:type="xsd:float">315.24</value>
<obj xmlns:ns4="urn:xml-soap-myObjectClass-demo" xsi:type="ns4:myObjectClass">
<number xsi:type="xsd:int">555</number>
<somefield xsi:type="xsd:string">testobject ThisShouldBeTheName : tmpMyObject this is somefield</somefield>
</obj>
<id xsi:type="xsd:long">666</id>
</return>
</ns1:sendPabloObjectResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This can happen if the server is not running or you invoke a Soap method while Soap is still loading.
[pablo]$ java samples.addressbook.SendPabloObject http://localhost:8080/soaptest/servlet/rpcrouter
Running SendPabloObject...
Press ENTER to continue
---------------------------
id : 100
used : false
name : ThisShouldBeTheName
value : 15.24
obj.somefield: testobject ThisShouldBeTheName
obj.number : 15
oneVector : [111, 222, 333]
---------------------------
[debuging information omitted]
java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:329)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:141)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:128)
at java.net.Socket.<init>(Socket.java:285)
at java.net.Socket.<init>(Socket.java:112)
at org.apache.soap.util.net.HTTPUtils.buildSocket(HTTPUtils.java:119)
at org.apache.soap.util.net.HTTPUtils.post(HTTPUtils.java:165)
at org.apache.soap.transport.http.SOAPHTTPConnection.send(SOAPHTTPConnection.java:208)
at org.apache.soap.rpc.Call.invoke(Call.java:203)
at samples.addressbook.SendPabloObject.main(SendPabloObject.java:95)
Caught an exception: SOAP-ENV:Client, Error opening socket: Connection refused
The last line comes from:
catch (SOAPException e) {
System.err.println("Caught an exception: " + e.getFaultCode() + ", " + e.getMessage());
return;
}
Other errors can happen during the Soap transaction. They usually state that there were problems to find a valid serializer (check deploy description and mapTypes) or that the server does not recognize the method called, ... All this result in a Soap transaction where the reply carries information about the error. Take care because some errors seem to be created in the client but actually are created by the server. In these cases it is useful to use TcpTunnelGui and see if data was transmitted over the wire.
Soap transactions till now traveled in clear text over the wire which means that anyone with access to the line can gather data about our communication. This is not usually desirable, particularly when we are submitting business information. It is usually convenient in business processes to authenticate the client accessing our server too, and this can be done easily using public key cryptography.
There are a couple of options to secure these transactions. Fist one is to use the security extensions added to Soap protocol, but as yet these are not implemented in Apache Soap. Another option is to use a SSL connection for our Soap transaction with the advantages that SSL is a standard encrypting layer and we only need to modify few things in our source to support it.
If we want to understand SSL connections we need to understand what certificates are and how they work. This has to do with public key cryptography and distribution of public keys. Basically you generate a pair of keys and give your public key to be signed by a trusted authority who ensures that your key really belongs to you. In that way if you trust the certification authority (CA) who signed the key (you should trust well known CAs) you are sure who you are talking to.
To use Soap over a SSL connection you will need a server certificate and possible a client certificate. Both should be signed by a CA.
I will try to explain here how to set up Tomcat and Orion server (under Linux) to use SSL and how to modify your Soap source.
5.2.1 Trust between client and server
Before any information between client and server is transmitted in a SSL communication it is usually required that client and server authenticate each other (although not always required). If this fails the communication is closed.
As client and server can be entities which had never before talked to each other it is necessary a trusted third party which states, in a form of a certificate, that each one is the one who it says to be. This entity is called Certification Authority, from now on CA. There are several well known CAs.
Authentication is achieved by using public key cryptography. Both client and server must have a key pair which is signed by a trusted CA after each party showed proof to its CA of themselves being who they say. CA can be different for client and server as long as the other party trusts the CA who signed the certificate.
If client authentication is required it is necessary that the authorized clients certificates are added to server keystore in advance.
This way, at the beginning of a SSL connection and after the initial handshake, certificates are exchanged and checked. After this phase is completed we have a secure encrypted channel which provides us reliable connection by using message integrity check (using hash functions) and in which client and server have authenticated each other.
You need to create a certificate for your server. This means to generate a pair of keys and request a signature from a CA. To generate the key pair you can use keytool, which is provided with Sun's java jdk. When you create your keypair do not forget to specify that you want a RSA keypair as newer DSA keypairs do not work with most browsers and maybe other old programs.
Key pair generation: using keytool (keytool -genkey) you must create a RSA (nowadays still better than DSA) key pair. You can read a good document about keytool in this web page http://java.sun.com/products/jdk/1.2/docs/tooldocs/solaris/keytool.html. It is best that your CN is the server name.
Certificate request: using keytool (keytool -certreq) you must generate a request for a signed certificate. You usually save this file as ".csr", which is called the Certificate Signing Request (CSR).
Getting the certificate signed: now you need that some CA will sign your certificate. This usually involves several steps depending of the CA used and also to spend some money. If you are just trying you can get a test certificate for free from Thawte. If what you need is a real certificate then ask your CA, if you want to ask for a free test certificate go to https://www.thawte.com/cgi/server/test.exe and paste the content of your .csr into the box. Then check where it says "Test SSL Chained CA Cert" and press "Generate Certificate".
Import the server certificate in the server keystore: you will import your new signed certificate into your keystore using keytool (keytool -import -trustcacerts).
Import the root CA certificate into your cacerts: you need to import the root certificate of the CA who issued the certificates that you are using into your cacerts file. That means that you trust all the certificates signed by this authority as valid ones. Thawte test root certificate can be obtained here.
You can import it to your "cacerts" file using keytool. Notice that this root certificate does not go in your server keystore but in a global cacerts file which can already contain some trusted certificates.
Import client certificate: it is necessary to add the certificates of the clients you want to access your services to server keystore. In that case you need to export your signed certificate (keytool -export) to a file and import it back (keytool -import) to the other party so that the client is granted access to the server.
If you plan to do client authentication then you also need to have a client certificate. If you only want to do server authentication then you do not need one. The way to get the certificate is exactly the same as for the server, probably changing the names of the files and the alias of the certificate.
You also need to add the root CA certificate to the client cacerts file. It is not necessary to add the server certificate to the client keystore as the server certificate is signed by a CA that you trust.
If you have followed the steps till now you will have something like this:
Client:
You should see in the cacerts file in client and server the new CA root, although it can be called in any other way. Mine looks like this (you might have also another entries, but I deleted them all):
Keystore type: jks Keystore provider: SUN Your keystore contains 1 entry: thawtetest-pablo, Thu Apr 19 11:46:58 GMT+03:00 2001, trustedCertEntry, Certificate fingerprint (MD5): 5E:E0:0E:1D:17:B7:CA:A5:7D:36:D6:02:DF:4D:26:A4Server keystore:
Keystore type: jks Keystore provider: SUN Your keystore contains 3 entries: soapsslserver, Thu Apr 19 16:23:55 GMT+03:00 2001, keyEntry, Certificate fingerprint (MD5): 34:D5:12:8C:BA:1B:3B:0C:78:B7:7B:2E:DF:C0:F3:71 pablo@client, Fri May 04 17:40:29 GMT+03:00 2001, trustedCertEntry, Certificate fingerprint (MD5): 06:A6:56:6C:8A:EC:40:EA:C5:92:C0:0B:B3:0C:E1:66 mykey, Fri Apr 20 14:47:56 GMT+03:00 2001, trustedCertEntry, Certificate fingerprint (MD5): 34:D5:12:8C:BA:1B:3B:0C:78:B7:7B:2E:DF:C0:F3:71First one is the server key.
Second one is the client certificate that was imported into the server keystore.
Third one is the server signed key, the server certificate.
Client keystore:
Keystore type: jks Keystore provider: SUN Your keystore contains 2 entries: pablo@client, Tue Apr 10 14:34:32 GMT+03:00 2001, keyEntry, Certificate fingerprint (MD5): 06:A6:56:6C:8A:EC:40:EA:C5:92:C0:0B:B3:0C:E1:66 mykey, Mon Apr 23 11:24:12 GMT+03:00 2001, trustedCertEntry, Certificate fingerprint (MD5): 06:A6:56:6C:8A:EC:40:EA:C5:92:C0:0B:B3:0C:E1:66First one is the client key.
Second one is the client certificate.
IMPORTANT: Although server authentication is working fine for me I could not get client authentication working with Orion server, so some steps in this setup procedure could be wrong. Feedback about this will be very much appreciated. When writing this document I'm not installing the servers again so I could be missing some steps. Let me know!
You can get Jakarta-Tomcat from this page: http://jakarta.apache.org/tomcat/index.html. I used version "jakarta-tomcat-3.2.1". After you have installed Tomcat you must copy your Apache-Soap/webapps/soap directory to jakarta-tomcat-3.2.1/webapps and make soap.jar visible to Tomcat.
It is important that you have the CLASSPATH variable correctly set. According to Tomcat instructions, add Xerces lib to the beginning of the CLASSPATH, and add the JSSE .jar files to the tomcat/lib/ directory. Look the place where the CLASSPATH is set in the tomcat.sh script and modify accordingly. Add also the directory where your server files are, and the directory where soap.jar is. For me this works:
CLASSPATH=/home/pablo/libs/xerces-1_3_0/xerces.jar:/opt/javalib/javamail-1.1/mail.jar:/opt/javalib/jaf-1.0.1/activation.jar:/home/pablo/libs/soap-2_1/lib/soap.jar:/home/pablo/path/to/my/server/files:/opt/javalib/jsse/lib/:$CLASSPATH/home/pablo/soft/jakarta-tomcat-3.2.1/lib: total 1256 drwxr-xr-x 3 pablo staff 4096 May 2 13:15 . drwxr-xr-x 10 pablo staff 4096 Apr 26 12:29 .. -rw-r--r-- 1 pablo staff 443047 Dec 12 23:36 ant.jar -rw-r--r-- 1 pablo staff 209875 Dec 12 23:37 jasper.jar -rw-r--r-- 1 pablo staff 5618 Dec 12 23:36 jaxp.jar lrwxrwxrwx 1 pablo staff 31 May 2 13:15 jcert.jar -> /opt/javalib/jsse/lib/jcert.jar lrwxrwxrwx 1 pablo staff 30 May 2 13:15 jnet.jar -> /opt/javalib/jsse/lib/jnet.jar lrwxrwxrwx 1 pablo staff 30 May 2 13:15 jsse.jar -> /opt/javalib/jsse/lib/jsse.jar -rw-r--r-- 1 pablo staff 136133 Dec 12 23:36 parser.jar -rw-r--r-- 1 pablo staff 40836 Dec 12 23:36 servlet.jar drwxr-xr-x 3 pablo staff 4096 Dec 12 23:36 test -rw-r--r-- 1 pablo staff 406998 Dec 12 23:37 webserver.jar
In conf/server.xml you need to add another Connector entry for SSL and specify where your keystore and truststore are:
<Connector className="org.apache.tomcat.service.PoolTcpConnector">
<Parameter name="handler"
value="org.apache.tomcat.service.http.HttpConnectionHandler"/>
<Parameter name="port"
value="9443"/>
<Parameter name="socketFactory"
value="org.apache.tomcat.net.SSLSocketFactory" />
<Parameter name="keystore"
value="/opt/server/certificates/SoapSSLServer.keystore" />
<Parameter name="keypass"
value="Soap-server-password" />
<Parameter name="trustore"
value="/home/pablo/path/to/my/cacerts" />
<Parameter name="trustpass"
value="changeit" />
<Parameter name="clientAuth"
value="true" />
</Connector>
I have changed the default truststore and keystore. Mind the typo!
You can set last parameter "clientAuth" to either "true" or "false", depending whether you want both client and server authentication or only server authentication.
In case you are running Orion server (for Linux) you should edit orion/config/secure-web-site.xml and add some lines to specify the keystore and the trustore.
Have a look at my secure-web-site.xml:
<?xml version="1.0"?>
<!DOCTYPE web-site PUBLIC "Orion Web-site" "http://www.orionserver.com/dtds/web-site.dtd">
<web-site host="[ALL]" port="5000" display-name="Secure Orion WebSite" secure="true">
<ssl-config keystore="/opt/orion/certificates/SoapSSLServer.keystore"
keystore-password="Soap-server-passwd"
trustore="/home/pablo/path/to/my/cacerts"
trustore-password="changeit"
needs-client-auth="true" />
<default-web-app application="default" name="defaultWebApp" />
<web-app load-on-startup="true" application="soapssl" name="soap-ssl" root="/soapssl" />
<access-log path="../log/secure-web-access.log" />
</web-site>
Modifications needed in the client to support SSL are really simple and are reduced to specify the locations of the keystore and trustore and handlers for the SSL connection. Just add the following lines at the begginning of your client and do not forget to specify "https" as the protocol in your URL. URL should look like this: https://yourhost:9443/apache-soap/servlet/rpcrouter
// HTTPS - SSL
System.setProperty("javax.net.ssl.keyStorePassword", "pabloclient");
System.setProperty("javax.net.ssl.keyStore", "/home/pablo/.keystore");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
System.setProperty("javax.net.ssl.trustStore", "/home/pablo/path/to/cacerts");
// Next line not necessary
// System.setProperty("javax.net.ssl.keyStoreType", "jks");
// Add https protocol support
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
// Add SSL Provider
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
You may need to import the following files at the begginning of your source.
import java.security.Security;
import java.security.KeyStore;
import javax.security.cert.X509Certificate;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.*;
import com.sun.net.ssl.*;
It is not easy to debug a SSL connection as all the data exchange is binary encrypted data, but you can specify the following parameter in the command line so that jsse shows some debug information "-Djavax.net.debug=ssl". If you add "-Djavax.net.debug=help" you get some help about debugging options.
A typical debug command line is as follows: java -Djavax.net.debug=ssl samples.addressbook.SendPabloObject
https://yourhost:9443/apache-soap/servlet/rpcrouter
Here is what is the relevant information you should see when debugging a SSL connection. *** means that it only happens if client is going to be authenticated.
keyStore: check that it is using the keystore you want. This might not be necessary if you are debugging a connection without client authentication.
trusStore: check that it is using the truststore where is your CA root certificate.
adding as trusted cert: check that your root CA certificate is found.
ClientHello: here begins SSL handshake.
ServerHello: first message from server where it sends the client its certificate.
stop on trusted cert: check that client finds the CA root for the server certificate. *** CertificateRequest: IF (and only if) the server wants the client to be authenticated then it will notice that with a CertificateRequest field at the end of its message, including the CAs that it is willing to accept. If the client needs not to be authenticated this field is missing. ServerHelloDone: this means that server does not have more information to submit. *** Matching client alias: IF the server requested client certificate then there should say here that a matching alias was found. ClientKeyExchange: Client and server begin to exchange keys. If the server asked for client certificate and the client did not send a valid certificate the server closes the socket (without further notice?). *** CertificateVerify: server checks the certificate. This is somewhere in the middle of ClientKeyExchange. Information transmited: after certificates are exchanged and keys generated normal Soap (encrypted) transaction takes place.
5.5.3 Client does not have a certificate
If your client happens not to have a valid certificate then you will see something like this:
*** CertificateRequest
Cert Types: DSS, RSA,
Cert Authorities:
-----> Here goes a list of valid authorities [omitted]
*** ServerHelloDone
*** Certificate chain
-----> Here there should be a valid certificate [this is the reason of the error]
*** ClientKeyExchange, RSA PreMasterSecret, v3.1
Notice that client certificate should be shown there and there is none.
5.5.4 Untrusted server cert chain
If the client has not imported the root certificate of the CA who signed the server certificate this will be the complain:
main, SEND SSL v3.1 ALERT: fatal, description = certificate_unknown main, WRITE: SSL v3.1 Alert, length = 2 java.lang.reflect.InvocationTargetException: javax.net.ssl.SSLException: untrusted server cert chain at java.lang.Throwable.(Throwable.java:96) at java.lang.Exception. (Exception.java:44) at java.io.IOException. (IOException.java:49) at javax.net.ssl.SSLException. ([DashoPro-V1.2-120198]) [...]
SOAP submission W3C (http://www.w3.org/TR/SOAP): this is the official Soap document
Apache SOAP (http://xml.apache.org/soap/): official site or Apache Soap project
Flash presentation about Soap (http://www.soapwebservices.com/articles/what_is_soap.asp): very interesting and simple presentation about Soap and RPC
Apache Soap Java API (http://www.geocities.com/monkiki5/doxygenDoc/html/)
Tutorial de Soap -Spanish- (http://www.geocities.com/monkiki5/doc/SOAP/index.html): good introduction to installing and programing simple clients and servers using Soap and Java, Perl and PHP.
Setting up Apache Tomcat and a Simple Apache SOAP Client for SSL Communication (http://xml.apache.org/websrc/cvsweb.cgi/~checkout~/xml-soap/java/docs/install/FAQ_Tomcat_SOAP_SSL.html?rev=1.1&content-type=text/html): step by step instructions to set up Soap and SSL. Much more detailed than in this text.
Search in soap-user list (http://marc.theaimsgroup.com/?l=soap-user&r=1&w=2)
SoapWare (http://www.soapware.org/): Interesting links from this web site
Introduction to SOAP using Apache SOAP 2.1 for Java -this document and the source- (http://www.eneris.com/~pablo/)
Soap is a simple protocol that supports remote
procedures over http. The fact that it can use http protocol and
embed in it the information makes it easier to pass through firewalls
(as they usually leave port 80 open).
There is
a lack of good documentation about it, specially detailed information
about programing API. The protocol itself is well defined but there
are not good complex examples, so it takes some time to get used to
Soap.