On my current project, I’m working with Axis2 as the server-side web service platform. The question of how to pass information on processing problems back to the client was raised. In the past, I’ve written response schemata that included status codes, error details, and the like. But in looking at the WSDL spec, I noticed that faults can be declared within the operations, so I was wondering how that was handled, and whether doing that might give greater flexibility to the client in how to handle and respond to failed requests.
In particular, how does the exception thrown from within Axis2 get marshalled into the XML of the SOAP response? My thought was that custom Exceptions with instance variables could be used to pass information back to the caller. For example, a TemporaryFailureProcessingException could have a retryInSeconds field, so that the calling client could actually requeue the request accordingly, without human intervention.
Spoiler Alert: I’ve since decided that using SOAP Faults in my current project wouldn’t make sense; it’d basically be extending a common Exception anti-pattern into the web-service arena. But it gave me a chance to explore how SOAP Faults are declared in WSDL and how Axis2 processes and returns them. There’s not a lot about that out there, so maybe this will be helpful for someone.
Axis2 uses a pretty flexible “phase” structure in determining how a given message is handled. Modules can be written that do something with the message (i.e. logging it, validating its body, authenticating it according to headers). That means problems in processing an Axis2 service request can occur before, during, or after the actual processing of the request by the service.
At a high level, there seem to be two types of problems that could occur while processing a web service call:
- The request cannot be processed because of problems with the request document (e.g., incorrect authentication credentials, invalid XML, inconsistent or missing request information, etc.).
- Most problems of this sort require user intervention to correct information in the request, but once it is corrected, the request can be resubmitted immediately
- The request cannot be processed because of problems during the server side processing (e.g. database unavailable, server timeout, service bugs, etc.). There are two categories of failures here:
- A temporary failure due to short term unavailability of some resource. The expectation would be that the same request could be submitted and successfully processed after a short delay
- A permanent failure due to some issue on the server side, such as a bug. The expectation would be that until the server-side problem is corrected, the request will continue to fail if it is resubmitted.
A way of including useful information about the reason for the processing failure as well as any suggestion for how the client should respond to it was desired.
The following design considerations were used:
- The solution must be standards-compliant. In other words, any XML documents must pass validation and any relevant documents should pass the current WS-I Test Tools suite.
- The solution should handle processing problems in modules as well as receivers (services)
- The solution should support the loose coupling and declarative structures of Axis2
- It is permissible to move to the current Axis2 production release (1.4)
- The solution should work with SOAP 1.1 and SOAP 1.2 (Note: Based on my testing, it doesn’t look like the current WS-I standards work with SOAP 1.2)
- Services using the solution will use WSDL 1.1
The structure of a SOAP Fault differs slightly between SOAP 1.1 and SOAP 1.2. For purposes of this overview, it is sufficient to say that unless otherwise indicated, only the names of elements are different. See Section 4.4 of the SOAP 1.1 spec and Section 5.4 of the SOAP 1.2 spec for details. This section will use the SOAP 1.2 element names.
According to the spec:
A SOAP fault is used to carry error information within a SOAP message.
element information item has:
- A [local name] of
- A [namespace name] of “http://www.w3.org/2003/05/soap-envelope”.
- Two or more child element information items in its [children] property in order as follows:
- A mandatory
Codeelement information item.
- A mandatory
Reasonelement information item.
- An optional
Nodeelement information item.
- An optional
Roleelement information item.
- An optional
Detailelement information item.
To be recognized as carrying SOAP error information, a SOAP message MUST contain a single SOAP
Faultelement information item as the only child element information item of the SOAP
For my project purposes, Node and Role can be ignored. A more complex service suite might find them useful; I didn’t explore it. Here is how the other information is used:
- The Code element contains a machine-understandable value describing the fault. This is as a string representation of a qualified name (i.e. namespaces are supported.) In SOAP 1.2, sub-codes can be defined, but SOAP 1.1 only allows the single value.
- The Reason element contains a human-understandable value describing the fault. This can be given a language attribute for internationalization reasons.
- The Detail element contains any extra information relevant for the fault.
Here is a sample document containing a SOAP fault:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://www.example.org/timeouts" xmlns:xml="http://www.w3.org/XML/1998/namespace"> <env:Body> <env:Fault> <env:Code> <env:Value>env:Sender</env:Value> <env:Subcode> <env:Value>m:MessageTimeout</env:Value> </env:Subcode> </env:Code> <env:Reason> <env:Text xml:lang="en">Sender Timeout</env:Text> </env:Reason> <env:Detail> <m:MaxTime>P5M</m:MaxTime> </env:Detail> </env:Fault> </env:Body> </env:Envelope>
Request-response and solicit-response WSDL operations can include any number of faults as part of their definition. Here is an example from section 2.4.2 of the WSDL 1.1 specification:
The grammar for a request-response operation is:<wsdl:definitions .... > <wsdl:portType .... > * <wsdl:operation name="nmtoken" parameterOrder="nmtokens"> <wsdl:input name="nmtoken"? message="qname"/> <wsdl:output name="nmtoken"? message="qname"/> <wsdl:fault name="nmtoken" message="qname"/>* </wsdl:operation> </wsdl:portType > </wsdl:definitions>
The input and output elements specify the abstract message format for the request and response, respectively. The optional fault elements specify the abstract message format for any error messages that may be output as the result of the operation (beyond those specific to the protocol).
Note that a request-response operation is an abstract notion; a particular binding must be consulted to determine how the messages are actually sent: within a single communication (such as a HTTP request/response), or as two independent communications (such as two HTTP requests).
The value of the
message attribute should be the name of a
wsdl:message defined elsewhere in the document. The message will have a
element attribute that describes any data specific to the fault, in the same way as other messages define the structure of requests and responses.
The WSDL operation definition is abstract. It is extended via SOAP binding to represent the actual operation being exposed as a service (and how it is exposed). According to the WSDL 1.1 spec section 3.6,
The soap:fault element specifies the contents of the contents of the SOAP Fault Details element. It is patterned after the soap:body element.<definitions .... > <binding .... > <operation .... > <fault>* <soap:fault name="nmtoken" use="literal|encoded" encodingStyle="uri-list"? namespace="uri"?> </fault> </operation> </binding> </definitions>
The name attribute relates the
wsdl:faultdefined for the operation.
The fault message MUST have a single part. The use, encodingStyle and namespace attributes are all used in the same way as with
style="document"is assumed since faults do not contain parameters.
While this structure is verbose, it is fairly straightforward. The upshot is that faults can be defined as a result of a service call, and additional data structures for a given type of fault can be defined. The way that a particular client would implement the fault in code will depend on the platform and desired functionality of the client, of course.
Axis2 contains an interface
org.apache.axis2.soap.SOAPFault which is used to carry SOAP fault information in a response. It also contains the class
org.apache.axis2.AxisFault which maps cleanly to a SOAP fault and is the “main” exception used within the Axis2 framework.
Any exception thrown during Axis2 request processing will be caught and displayed as a SOAP fault in the response. This is done in the following way:
- If the exception is an AxisFault, then the instance variables of that fault are used to populate the SOAP fault returned to the caller unless the messageContext contains SOAP fault parameters (see below).
- If the exception is not an AxisFault, it is wrapped in a generic AxisFault and handled as above.
Axis2 will return a SOAP Fault compliant with whatever soap version is being used in the request or endpoint.
By default, the fault code will be
Reciever, and the fault reason will be whatever
getMessage() returns. The
Detail node will be empty, unless Axis2 is configured to return a stack trace in faults by setting the sendStacktraceDetailsWithFaults parameter in axis2.xml to
The SOAP Fault can be customized in two different ways.
- Properties in the message context can be set for Code, Reason, and Detail. If set, these trump anything in the actual exception.
- AxisFault constructors and mutators can be used to set the relevant values used during serialization.
The actual serialization is handled by a few static methods in MessageContextBuilder. Those methods seem to be used from everywhere else (receivers, servlets, servers, etc.); there is no way to easily override them or inject other behavior.
I recommend that any exception thrown from modules or services be a subclass of AxisFault. Such exceptions should have the following characteristics:
- An awareness of an applicable namespace and a namespace prefix consistent with that used for the service (or module) that produced it.
- The ability to return specific values for Code, Reason, and Detail as follows:
- Code should be a QName of the exception namespace and the exception short name as the local name. Subcode nodes will not be used at all.
- Reason should be the exception message with the xml:lang set to “en-US”
- Details should contain any instance variables in the exception as a sequence of OMElements
Exceptions that meet the above description can be thrown from modules or services and will behave according to the WSDL and SOAP specs. Exceptions will stop further processing of the request, so anything thrown by modules prior to the service handler will prevent the service.
The specifics of how to implement these exceptions will take a bit of design. I would recommend a base exception class which overrides (at minimum) getFaultCode() and can handle namespaces for itself. Any web service exceptions could extend that class, overriding getDetail() if needed but otherwise staying pretty lean.
I also recommend that SOAP 1.2 service binding be preferred in all WSDL, to allow more flexibility moving forward and better agreement between VUE use and the published spec.
The SOAP 1.1 spec is more restrictive and specific in what can be found in fault codes, and what the presence/absence of a details subelement means. It is possible that a client using SOAP 1.1 with generated code could experience trouble with SOAP faults sent in the manner described in this proposal.
The WSDL must include the schema of any exception information, which is put in the details node of the SOAP fault (see below for possible headerfault exceptions). I haven’t had time to validate that this proposal produces compliant SOAP fault XML or that clients autogenerating code from the WSDL (particularly non-Axis2 clients) properly pick up the fault information. This may not be an immediate concern, but any automated processing of web service responses will depend on this working predictably.
This approach would serialize all exceptions as SOAP faults. However, the SOAP specification states that errors pertaining to headers must be returned in headers. I’m seeing different opinions on the use of the SOAP headerfault element, but whatever is acceptable, it seems likely that exceptions caused by SOAP header issues will need to populate the header part of the AxisFault. This should apply mainly to modules. This is another area where different client frameworks could process WSDL in different ways. AxisFault is aware of headerfaults, so this proposal should allow for them if needed in the future.