SOAP Faults
The single most important piece of any web service integration is error handling. At some point, your application will not receive the expected answer to its call. You can blame hardware, maintenance policies, natural disasters, missed utility bills, etc but in the end it is still up to you to ensure that your application correctly handles errors that it will encounter. However, it is the service provider's responsibility to return a usable error.
Luckily, SOAP provides a mechanism especially for this: the SOAP Fault. Faults provide a standardized format for which a server can inform a client that some error condition has occurred. Although the format is standard, the specific contents of each fault subelement is pretty much up to the server. For example in SOAP 1.1, the faultcode is supposed to extend from a few predefined values ('Client', 'Server', etc) but many services choose instead to return a custom faultcode. As a client, your application should be robust enough to handle whatever is passed its way.
Now, let's take a look at how various SOAP toolkits handle faults. All of the major toolkits treat a fault as an exception. The big difference that I noticed between the toolkits was in how AXIS handled custom fault definitions. Take the following WSDL snippet from our API:
<schema elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://api.bronto.com">
<element name="fault" type="tns:error" />
<complexType name="error">
<sequence>
<element name="code" type="xsd:int" minOccurs="1" maxOccurs="1" />
<element name="message" type="xsd:string" minOccurs="1" maxOccurs="1" />
<element name="extra" type="tns:errorExtra" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
</schema>
<message name="faultResponse">
<part name="fault" element="tns:fault" />
</message>
<portType name="apiPortType">
<operation name="login">
<documentation>Logs in a user</documentation>
<input message="tns:loginRequest" />
<output message="tns:loginResponse" />
<fault name="fault" message="tns:faultResponse" />
</operation>
</portType>
This WSDL basically says that the login call can return a fault whose message is a complex element of type "error". That element will be within the detail element of the fault (the faultcode, faultstring, and faultactor are reserved).
In .NET, this fault takes the following form. The final generic Exception catch is for any other unforeseen exception that could occur (eg http timeout). Notice how we have to actually parse the fault xml by hand in order to get the "error" contents from the detail element.
try
{
loginResult login = binding.login(username, password, sitename);
}
catch(SoapException e)
{
//we have to parse the xml for a more detailed error message
System.Xml.XmlNode fault = e.Detail.FirstChild;
System.Xml.XmlNode code = fault.FirstChild;
System.Xml.XmlNode message = code.NextSibling;
System.Console.WriteLine("soap exception:");
System.Console.WriteLine(code.InnerText);
System.Console.WriteLine(message.InnerText);
}
catch(Exception e)
{
System.Console.WriteLine("Exception: " + e.Message);
System.Console.WriteLine("Inner: " + e.InnerException);
System.Console.WriteLine(e.StackTrace);
}
In PHP-SOAP, this gets translated a little better into the following. The detail element gets deserialized into a stdClass object that we can use to obtain the "error" contents.
try{
$result = $Binding->login($username, $password, sitename);
}
catch(SoapFault $Error){
echo'Error Fault: ' . $Error->faultcode . "::" . $Error->faultstring;
echo'Error Detail: ' . $Error->detail->fault->code . '::' . $Error->detail->fault->message;
}
In AXIS, this get translated to a custom class Error that extends Exception. The "error" contents are immediately available as instance variables of the exception.
try{
LoginResult login = binding.login(username, password, sitename);
}
catch(com.bronto.api.Error e){
System.out.println("Soap Fault");
System.out.println("code: " + e.getCode());
System.out.println("message: " + e.getMessage1());
System.exit(0);
}
catch(Exception e){
System.out.println("other exception");
System.out.println(e.toString());
System.exit(0);
}
Thus in AXIS, we could provide WSDL that throws a specific exception for each type of error that could occur. However, only AXIS users would benefit from that type of error handling. Since each implementation behaves slightly different, it is generally best to keep it simple. You can never go wrong with a single fault definition that provides a unique identifier for each error (in our case, the detail->fault->code). Providing a string message about the error is also extremely useful for the human user.
Ed
Recent Comments