Web services have been well accepted by the industry to solve the problem of distributing processing across multiple platforms and systems. However, a well-designed software system should always have a powerful mechanism for handling error conditions. Web service clients need to recognize and to respond to errors on the server side, whether those errors are due to an errant service implementation, a bad client request, or normal operating difficulties. SOAP lets you communicate error information using the SOAP <Fault>. Since web service clients rely on WSDL to understand the format of request and response messages, WSDL also describes the fault details returned by the web services.
In this article, you'll learn what information can be put into a SOAP <Fault> element, and how to design a fault-handling system using version 2.2 of the Apache SOAP toolkit. On the client side, this article will also show you how to describe a SOAP <Fault> in WSDL.
Listing 1 is an example of a SOAP response message that contains a <Fault> element. The message has been transmitted via HTTP.
Listing 1. SOAP <Fault> message
HTTP/1.1 500 Internal Server Error
Server: WebSphere Application Server/4.0
Content-Type: text/xml; charset=utf-8
Set-Cookie: JSESSIONID=0000M1BYT2CGGVGAVUD3IOQG0EA:-1;Path=/
Cache-Control: no-cache="set-cookie,set-cookie2"
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Content-Length: 711
Content-Language: en
Connection: close
<?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/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Exception from service object: Errors occurred while processing the message.
</faultstring>
<faultactor>/FaultHandling/servlet/rpcrouter</faultactor>
<detail>
<field>
<name>ProductId</name>
<error>ProductId does not exist.</error>
</field>
<field>
<name>CurrencyCode</name>
<error>CurrencyCode is not USD, CAD or CNY.</error>
</field>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
From this example, you can see that if an error occurs at the web service's server, the HTTP 500 Internal Server Error is returned.
The SOAP <Fault> element is used to carry error and/or status information within a SOAP message. If present, the SOAP <Fault> element appears only once within a <Body> element. A <SOAP-ENV:Body> may not contain both a <SOAP-ENV:Fault> and a wrapper (that is, a returned value).
The <SOAP-ENV:Body> may contain a <SOAP-ENV:Fault>, which in turn must contain two elements: <faultcode> and <faultstring>. Two other elements, <faultactor> and <detail>, are optional. These elements serve the following functions:
-
<faultcode>: Provides an algorithmic mechanism for identifying the fault. -
<faultstring>: Provides a human-readable explanation of the fault. -
<faultactor>: Provides information about the actor in the message path that caused the fault to happen. -
<detail>: Carries application-specific error information related to the<Body>element. All immediate child elements of the<detail>element are called detail entries, and each detail entry is encoded as an independent element within the<detail>.
Detail entries can be used to communicate customized fault messages. For example, in the SOAP fault message in Listing 1, the <detail> element has multiple <field> elements. And each <field> has <name> and <error> elements.
Fault handling using Apache SOAP 2.2
In order to demonstrate fault handling in web services, we have built for this article a web service called PriceChecker. All of our example code illustrates interaction between PriceChecker and a hypothetical client.
When an error occurs on the server side during PriceChecker's operation, the Apache SOAP server will attempt to capture an error state and then construct a SOAP <Fault> message containing a base set of information about the error.
However, it is useful to pass additional fault information in the SOAP <detail> section when an error state arises. Apache SOAP 2.2 provides a pluggable fault-handling mechanism into which one or more fault listeners may be registered to process faults.
There are two basic fault handlers in Apache SOAP:
-
org.apache.soap.server.DOMFaultListener -
org.apache.soap.server.ExceptionFaultListener
DOMFaultListener adds a <DOM> element representing the root exception which occurred, while ExceptionFaultListener wraps the root exception in a parameter.
Fault handlers are registered by including one or more <faultListener> elements within the deployment descriptor of a service.
After the SOAP toolkit registers the DOMFaultListener, the deployment descriptor file will be similar to Listing 2.
Listing 2. Deployment descriptor registered with DOMFaultListener
<root>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="http://tempuri.org/com.ibm.store.PriceChecker" checkMustUnderstands="false">
<isd:provider type="java" scope="Application" methods="checkPrice">
<isd:java class="com.ibm.store.PriceChecker" static="false"/>
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service>
</root>
|
Listing 3 shows the web service's SOAP response to a malformed request using DOMFaultListener. This response wraps the stack trace of the server-side exception into the SOAP fault detail.
Listing 3. SOAP <Fault> message using DOMFaultListener
HTTP/1.1 500 Internal Server Error
Server: WebSphere Application Server/4.0
Content-Type: text/xml; charset=utf-8
Set-Cookie: JSESSIONID=0000JR51W32C2WEYLCKPDINFBIA:-1;Path=/
Cache-Control: no-cache="set-cookie,set-cookie2"
Expires: Thu, 01 Dec 1994 16:00:00 GMT
Content-Length: 2936
Content-Language: en
Connection: close
<?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/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Exception from service object: ProductId doesn't exist.</faultstring>
<faultactor>/FaultHandling/servlet/rpcrouter</faultactor>
<detail>
<stackTrace>com.ibm.store.InvalidProductIdException: ProductId doesn't exist.
at com.ibm.store.PriceChecker.checkPrice(PriceChecker.java:27)
at java.lang.reflect.Method.invoke(Native Method)
at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
at org.apache.soap.providers.RPCJavaProvider.invoke(RPCJavaProvider.java:129)
at org.apache.soap.server.http.RPCRouterServlet.doPost(RPCRouterServlet.java:287)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at com.ibm.servlet.engine.webapp.StrictServletInstance.doService
(ServletManager.java:827)
... ...
at com.ibm.ws.http.HttpConnection.readAndHandleRequest(HttpConnection.java:313)
at com.ibm.ws.http.HttpConnection.run(HttpConnection.java:242)
at com.ibm.ws.util.CachedThread.run(ThreadPool.java:122)
</stackTrace>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
If you want to create a new fault handler, you should create a Java class that implements the org.apache.soap.server.SOAPFaultListener interface. You need to implement the fault() method, which takes in a SOAPFaultEvent. The SOAPFaultEvent wraps the SOAP <Fault> and SOAP <Exception> objects that were created as a result of the error.
The class diagram of the fault-handling system in PriceChecker is shown in Figure 1.
Figure 1. Class diagram for fault handling in the PriceChecker web service
On the server side, the web service throws InvalidInputException and InvalidServerDataException, which are inherited from InvalidDataException. InvalidDataFaultListener implements the fault() method to construct the SOAP <Fault> message.
The Visitor design pattern is used to separate the exception hierarchy from the code that builds the fault details. When the exception passed in this fault() method is an InvalidDataException (please refer to the InvalidDataFaultListener code in Figure 1), InvalidDataException::doTask(Task) is called. Because of Java polymorphism, it will call either InvalidInputException::doTask(Task) or InvalidServerDataException::doTask(Task) automatically. The code for doTask(Task) is shown in Listing 4.
Listing 4. InvalidInputException::doTask
public class InvalidInputException extends InvalidDataException{
... ...
public Vector doTask(Task task){
return task.buildFaultDetails(this);
}
}
|
You can see that, if an InvalidInputException is thrown, buildFaultDetails(InvalidInputException e) will be called eventually to create the SOAP fault message in Listing 1. If an InvalidServerDataException is thrown, buildFaultDetails(InvalidServerDataException e) will be called. Listings 5 and 6 show the code to build fault details for InvalidInputException and InvalidServerDataException, respectively.
Listing 5. Build fault details for InvalidInputException
public Vector buildFaultDetails(InvalidInputException e){
Vector details = new Vector();
DocumentBuilder xdb = XMLParserUtils.getXMLDocBuilder();
Document doc = xdb.newDocument();
String[] fieldNames = e.getFieldNames();
String[] errMessages = e.getErrMessages();
for (int i=0; i<fieldNames.length; i++){
Element paElem = doc.createElement(FIELD);
Element elem = doc.createElement(NAME);
Text tn = doc.createTextNode(fieldNames[i]);
elem.appendChild(tn);
paElem.appendChild(elem);
elem = doc.createElement(ERROR);
tn = doc.createTextNode(errMessages[i]);
elem.appendChild(tn);
paElem.appendChild(elem);
details.addElement(paElem);
}
return details;
}
|
Listing 6. Build fault details for InvalidServerDataException
public Vector buildFaultDetails(InvalidServerDataException e){
Vector details = new Vector();
DocumentBuilder xdb = XMLParserUtils.getXMLDocBuilder();
Document doc = xdb.newDocument();
Element elem = doc.createElement(ERROR);
Text tn = doc.createTextNode(e.getDetailMsg());
elem.appendChild(tn);
details.addElement(elem);
return details;
}
|
Once you've created the fault handler, you'll need to register it in the deployment descriptor, as noted above. Listing 7 shows the new deployment descriptor.
Listing 7. Deployment descriptor registered with user-defined FaultListener
<root>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="http://tempuri.org/com.ibm.store.PriceChecker" checkMustUnderstands="false">
<isd:provider type="java" scope="Application" methods="checkPrice">
<isd:java class="com.ibm.store.PriceChecker" static="false"/>
</isd:provider>
<isd:faultListener>com.ibm.store.InvalidDataFaultListener</isd:faultListener>
</isd:service>
</root>
|
When a SOAP request is sent to the web service and causes the server throw an InvalidInputException -- the SOAP request message in Listing 8 is an example of such a problematic message -- the SOAP response in Listing 1 is received on the client side. This response is built by the buildFaultDetails code in Listing 5.
Listing 8. SOAP request message that causes the server to throw an InvalidInputException
POST /FaultHandling/servlet/rpcrouter HTTP/1.0
Host: localhost:8080
Content-Type: text/xml; charset=utf-8
Content-Length: 547
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/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:checkPrice xmlns:ns1="http://tempuri.org/com.ibm.store.PriceChecker"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<productId xsi:type="xsd:string">012460</productId>
<currencyCode xsi:type="xsd:string">EUR</currencyCode>
</ns1:checkPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
Similarly, when a SOAP request is sent to a web service and causes the server throw an InvalidServerDataException, the SOAP response in Listing 9 is received by the client. It is built by the buildFaultDetails code in Listing 6.
Listing 9. SOAP <Fault> message resulting from a server-side InvalidServerDataException
HTTP/1.1 500 Internal Server Error
Server: WebSphere Application Server/4.0
Content-Type: text/xml; charset=utf-8
Content-Length: 617
Content-Language: en
Connection: close
<?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/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Exception from service object: Errors occurred while processing the message.
</faultstring>
<faultactor>/FaultHandling/servlet/rpcrouter</faultactor>
<detail>
<error>Exchange rate or unit price should be a number on the server.</error>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
WSDL for the SOAP <Fault> element
WSDL helps web services clients understand the format of SOAP request and response messages.
WSDL can describe the SOAP fault using different WSDL SOAP bindings. There are two commonly used SOAP bindings: document/literal and RPC/encoded. The major difference between the two is that the first contains a schema that fully describes the SOAP messages, while the second needs extra encoding rules to serialize the data into a message. (For more on document and RPC style, see Resources.)
Listing 10 shows the PriceChecker-binding.wsdl file. Most of the file was automatically generated by IBM's Web Services Toolkit (WSTK; see Resources for more information); the lines highlighted in red were added by hand afterwards to describe the SOAP fault for InvalidServerDataException. The description of InvalidInputDataException is not provided because it needs an extra XML schema section to describe the complexType in Listing 1.
Listing 10. PriceChecker-binding.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="PriceCheckerRemoteInterface"
targetNamespace="http://www.pricechecker.com/definitions/PriceCheckerRemoteInterface"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://www.pricechecker.com/definitions/PriceCheckerRemoteInterface"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<message name="checkPriceRequest">
<part name="productId" type="xsd:string"/>
<part name="currencyCode" type="xsd:string"/>
</message>
<message name="checkPriceResponse">
<part name="result" type="xsd:double"/>
</message>
<message name="invalidServerDataFault">
<part name="error" type="xsd:string"/>
</message>
<portType name="PriceCheckerJavaPortType">
<operation name="checkPrice">
<input name="checkPriceRequest" message="tns:checkPriceRequest"/>
<output name="checkPriceResponse" message="tns:checkPriceResponse"/>
<fault message="invalidServerDataFault" name="invalidServerDataFault"/>
</operation>
</portType>
<binding name="PriceCheckerBinding" type="tns:PriceCheckerJavaPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="checkPrice">
<soap:operation soapAction="" style="rpc"/>
<input name="checkPriceRequest">
<soap:body use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://tempuri.org/com.ibm.store.PriceChecker"/>
</input>
<output name="checkPriceResponse">
<soap:body use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://tempuri.org/com.ibm.store.PriceChecker"/>
</output>
<fault name="invalidServerDataFault">
<soap:fault use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://tempuri.org/com.ibm.store.PriceChecker"/>
</fault>
</operation>
</binding>
</definitions>
|
You'll notice a message called invalidServerDataFault, which has one <part> called error. The checkPrice operation contains an <input>, an <output>, and a <fault> element. The corresponding <operation> element inside the <binding> also has a <fault> element. Within this <fault> element, you'll see the <soap:fault> element that defines it as using the encoded mode to serialize the error information.
The WSDL file in Listing 10 describes SOAP fault information. According to the file, you can write some client code to retrieve this information from the SOAP fault <detail> section.
After reading this article, you should have a grasp of fault handling in web services systems. The <detail> section of the SOAP fault message can be used to pass application-specific error information. The pluggable fault-handling mechanism in the Apache SOAP toolkit works on the event/listener model. You can use the basic fault handler provided by the Apache SOAP, or create your own. A better understanding of fault handling in web services should help you build more robust distributed applications and services.
The source code for the PriceChecker web service, which implements the fault handling discussed in this article, can be downloaded here. You can use the WSDL <fault> to describe the SOAP faults. Experiment for yourself to get a better feel for how these systems work.
-
A Busy Developer's Guide to SOAP 1.1 has a lot of information, helpfully arranged.
- Check out the W3C's Simple Object Access Protocol (SOAP) 1.1 Note.
- Read more about Web Services Description Language (WSDL) from the Microsoft Developers Network.
- For more on document and RPC style, read "Reap the Benefits of Document Style," James McCarthy (developerWorks, June 2002).
- You can download the WSTK from IBM's alphaWorks site.
- You can generate WSDL files with WebSphere Studio Application Developer.
Rachel Shen is a software developer at the IBM Vancouver Innovation Centre. She graduated from the University of British Columbia with a master's degree in electrical and computing engineering. She has experience on e-business applications using the WebSphere family of tools, as well as XML and Java technologies. Recently, she has been working on Web services and P2P computing. She is also interested in software development best practices and extreme programming. You can contact her at rshen@ca.ibm.com.
Ernest Choi is a senior IT specialist at the IBM Vancouver Innovation Centre whose current responsibilities include leading project teams to design and implement middleware solutions using Java technology, WebSphere, and C++. After graduating from the University of British Columbia once in 1982 with a bachelor's degree in electrical engineering, he created application software with two startup firms while working towards his 1987 bachelor's degree in computer science (also from UBC). In 1989, he joined the IBM Toronto Lab to develop xlC, the first IBM C++ compiler for AIX. For personal reasons, Ernest moved to Vancouver in 1995 and has developed middleware servers ever since. He's implemented small servers from scratch using C++ and the Java platform as well as larger, more scalable servers using a variety of Web technologies such as EJBs, WebSphere, XML, SOAP, and WSDL. You can contact him at ernestc@ca.ibm.com.




