|
his document describes how to successfuly use WSDL faults in webservices
implemented in C using gSOAP or in Java using Apache Axis for
reporting unusual return values. It assumes that you know how
to use gSOAP and Axis.
Content
What are WSDL faults ?
Exceptions in programming languages
Checked and unchecked exceptions
SOAP Faults
WSDL Faults
WSDL faults in gSOAP and Axis
Generating WSDL
Writing a client in gSOAP
Writing a server in gSOAP
Writing a client in Axis
Deploying a server in Axis
What are WSDL faults ?
Exceptions in programming languages
In some programming languages, like Java and C++, a method (function)
can throw so called "exception", which is basicaly an object
returned instead of the normal return value if something
unusual (exceptional) happens. For example when dividing two integers,
the return value is normally an integer. But if the second one
is zero, no value is returned and an instance of java.lang.ArithmeticException
class is "thrown".
The type of the object indicates the problem
which just occured (ArithmeticException
) so that it can be handled programaticaly,
and in Java each exception has a human-understandable
text associated with it, which can be used by humans to resolve the problem,
in this example the text is "/ by zero
".
The exception can be "catched" and processed using special language
constructs (try{ ... } catch (Exception ex) { ... }
) or
left propagating up from the current method to its calling method.
This greatly simplifies processing errors.
Checked and unchecked exceptions
There are two types of exceptions, checked
and unchecked
ones.
Checked exceptions must be declared in method signatures together with
parameters and return value, while unchecked exception don't have to be declared.
Typicaly checked exceptions are used when a method needs to make
its users aware of the known exceptional states which can happen.
For example most of methods in the java.io
package can throw
checked IOException
when some Input/Output problem happens.
Unchecked exceptions are used for errors which are too common to be declared,
like NullPointerException
thrown when an object variable
is empty and thus a method cannot be called on it.
SOAP Faults
Webservices have similar concept of faults
. As with checked and unchecked
exceptions, there are two types of them. Some are user-defined and declared in WSDL
(like checked exceptions), and some can happen anytime on the SOAP layer
during communication, so they are not declared (like unchecked exceptions).
The declared ones are called WSDL faults.
WSDL Faults
Interface of a webservice is described independently of any programming language
using WSDL language.
The WSDL language allows to have three types of messages - input
, output
and fault
.
The fault message can have only one part, which can be a XML Schema complex type,
thus containing several values. Let's see an example of a service called
MyService
which has one operation called myOperation
with one input parameter myInput
, one return value of type string
and two possible faults named MyFirstException
and MySecondException
. The faults carry values in them, the first one has some text and the second one has a number.
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="MyService"
targetNamespace="urn:myuri:1.0"
xmlns:tns="urn:myuri:1.0"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns1="urn:myuri:1.0"
xmlns:SOAP="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:MIME="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:DIME="http://schemas.xmlsoap.org/ws/2002/04/dime/wsdl/"
xmlns:WSDL="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<schema targetNamespace="urn:myuri:1.0"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns1="urn:myuri:1.0"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<!-- fault element -->
<element name="MyFirstException">
<complexType>
<sequence>
<element name="text" type="xsd:string" minOccurs="1" maxOccurs="1" nillable="false"/>
</sequence>
</complexType>
</element>
<!-- fault element -->
<element name="MySecondException">
<complexType>
<sequence>
<element name="number" type="xsd:int" minOccurs="1" maxOccurs="1"/>
</sequence>
</complexType>
</element>
<!-- operation request element -->
<element name="myOperation">
<complexType>
<sequence>
<element name="myInput" type="xsd:string" minOccurs="0" maxOccurs="1" nillable="true"/>
</sequence>
</complexType>
</element>
<!-- operation response element -->
<element name="myOperationResponse">
<complexType>
<sequence>
<element name="myOutput" type="xsd:string" minOccurs="0" maxOccurs="1" nillable="true"/>
</sequence>
</complexType>
</element>
</schema>
</types>
<message name="myOperationRequest">
<part name="parameters" element="ns1:myOperation"/>
</message>
<message name="myOperationResponse">
<part name="parameters" element="ns1:myOperationResponse"/>
</message>
<message name="MySecondExceptionFault">
<part name="fault" element="ns1:MySecondException"/>
</message>
<message name="MyFirstExceptionFault">
<part name="fault" element="ns1:MyFirstException"/>
</message>
<portType name="MyType">
<operation name="myOperation">
<documentation>Service definition of function ns1__myOperation</documentation>
<input message="tns:myOperationRequest"/>
<output message="tns:myOperationResponse"/>
<fault name="MySecondException" message="tns:MySecondExceptionFault"/>
<fault name="MyFirstException" message="tns:MyFirstExceptionFault"/>
</operation>
</portType>
<binding name="MyService" type="tns:MyType">
<SOAP:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="myOperation">
<SOAP:operation soapAction=""/>
<input>
<SOAP:body use="literal"/>
</input>
<output>
<SOAP:body use="literal"/>
</output>
<fault name="MySecondException">
<SOAP:fault name="MySecondException" use="literal"/>
</fault>
<fault name="MyFirstException">
<SOAP:fault name="MyFirstException" use="literal"/>
</fault>
</operation>
</binding>
<service name="MyService">
<documentation>gSOAP 2.7.1 generated service definition</documentation>
<port name="MyService" binding="tns:MyService">
<SOAP:address location="http://localhost:10000"/>
</port>
</service>
</definitions>
This webservice can be implemented in any language, even in C, and still it will have
the option to return a fault instead of the usual string return value.
Because the WSDL is independent of any language, it does not provide a place
to store a stack trace associated with Java exceptions for example, because a client
implemented in C would not know what to do with the stacktrace.
A SOAP message with the first fault looks like this:
HTTP/1.1 500 Internal Server Error
Server: gSOAP/2.7
Content-Type: text/xml; charset=utf-8
Content-Length: 577
Connection: close
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:myuri:1.0">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Deliberately thrown exception.</faultstring>
<detail>
<ns1:MyFirstException>
<text>Input values are wrong.</text>
</ns1:MyFirstException>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So, in short, WSDL faults are alternative return messages declared in WSDL.
They can be used to return structured information when an error occurs.
WSDL faults in gSOAP and Axis
WSDL faults are working among gSOAP and Axis clients and servers only when
using latest versions of the tools (in the moment of this writing), gSOAP 2.7.1
and Axis 1.2RC3. And you should use "document/literal wrapped"
style for WSDL which is default now in gSOAP and can be used in Axis too.
Older versions and other WSDL styles are known to have problems.
Generating WSDL
The
WSDL example above was generated using gSOAP. I have found that
nowadays gSOAP produces much better WSDL files than Axis, even if
several years ago it was the other way round.
The following file was used to generate it. Please note that the names
of exception structures begin with underscores. That's a requirement
which is not mentioned in the gSOAP user guide as of time of this writing.
//gsoap ns1 service name: MyService
//gsoap ns1 service type: MyType
//gsoap ns1 service port: http://localhost:10000
//gsoap ns1 service namespace: urn:myuri:1.0
struct _ns1__MyFirstException {
char* text 1;
};
struct _ns1__MySecondException {
int number 1;
};
//gsoap ns1 service method-fault: myOperation _ns1__MyFirstException
//gsoap ns1 service method-fault: myOperation _ns1__MySecondException
int ns1__myOperation(
char * myInput,
struct ns1__myOperationResponse { char *myOutput; } *
);
Writing a client in gSOAP
Now it is the time to write a client in gSOAP.
Creat an empty directory and create there a Makefile with following content.
It assumes that GSOAPDIR is set to the location of gSOAP:
Makefile
SOAPCPP2=$(GSOAPDIR)/soapcpp2
WSDL2H=$(GSOAPDIR)/wsdl2h
all: client server
soapC.c: faultdemo.h
"$(SOAPCPP2)" -c faultdemo.h
client: client.c soapC.c
gcc -g -I. -DDEBUG -o client client.c soapClient.c soapC.c stdsoap2.c
server: server.c soapC.c
gcc -g -I. -o server server.c soapServer.c soapC.c stdsoap2.c
clean:
rm -rf *.xml *.nsmap soap* *.xsd *.log client server MyService.wsdl core
You also need a file with source code for the client:
client.c
#include "soapH.h"
#include "MyService.nsmap"
void processFault(struct soap *soap);
int main(int argc,char** argv) {
struct soap *soap = soap_new();
struct ns1__myOperationResponse out;
char * url = "http://localhost:10000/";
//char * url = "http://localhost:8080/axis/services/MyService" ;
if(argc==2) { url = argv[1]; }
printf("calling first ...\n");
if(soap_call_ns1__myOperation(soap,url,"","first",&out) == SOAP_OK) {
printf("OK\n");
} else {
processFault(soap);
}
printf("\ncalling second ...\n");
if(soap_call_ns1__myOperation(soap,url,"","second",&out) == SOAP_OK) {
printf("OK\n");
} else {
processFault(soap);
}
}
void processFault(struct soap *soap) {
soap_print_fault(soap, stderr);
if((soap->fault != NULL) && (soap->fault->detail != NULL)) {
switch (soap->fault->detail->__type) {
case SOAP_TYPE__ns1__MyFirstException: {
struct _ns1__MyFirstException * ex
= (struct _ns1__MyFirstException *) soap->fault->detail->fault;
if(ex!=NULL) { printf("MyFirstException.text=%s\n",ex->text); }
}; break;
case SOAP_TYPE__ns1__MySecondException: {
struct _ns1__MySecondException * ex
= (struct _ns1__MySecondException *) soap->fault->detail->fault;
if(ex!=NULL) { printf("MySecondException.number=%d\n",ex->number); }
}; break;
}
}
}
And finaly you need to copy files stdsoap2.c
and stdsoap2.h
from
your gSOAP installation and type "make client". That will generate WSDL, communication stubs
and compile the client.
Writing a server in gSOAP
In the same directory create a file with source of gSOAP server.
server.c
#include "soapH.h"
#include "MyService.nsmap"
int ns1__myOperation(struct soap* soap,char * myInput,struct ns1__myOperationResponse *out) {
soap_sender_fault(soap,"Deliberately thrown exception.",NULL);
soap->fault->detail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
soap->fault->detail->__any = NULL;
if(strncmp(myInput,"first",5)==0) {
struct _ns1__MyFirstException *ex = (struct _ns1__MyFirstException *) soap_malloc(soap,sizeof(*ex));
ex->text = "Input values are wrong.";
soap->fault->detail->__type = SOAP_TYPE__ns1__MyFirstException;
soap->fault->detail->fault = ex;
} else {
struct _ns1__MySecondException *ex = (struct _ns1__MySecondException *) soap_malloc(soap,sizeof(*ex));
ex->number = 1111;
soap->fault->detail->__type = SOAP_TYPE__ns1__MySecondException;
soap->fault->detail->fault = ex;
}
return SOAP_FAULT;
}
int port = 10000;
int main() {
struct soap soap;
int i, m, s;
soap_init(&soap);
m = soap_bind(&soap, NULL, port, 100);
if (m < 0)
soap_print_fault(&soap, stderr);
else {
fprintf(stderr, "Running on port %d\n",port);
for (i = 1; ; i++) {
s = soap_accept(&soap);
fprintf(stderr, "%d: accepted connection from IP=%d.%d.%d.%d\n", i,
(soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF);
if (s < 0) {
soap_print_fault(&soap, stderr);
break;
}
if (soap_serve(&soap) != SOAP_OK) { // process RPC request
soap_print_fault(&soap, stderr); // print error
}
fprintf(stderr, "request served\n");
soap_destroy(&soap); // clean up class instances
soap_end(&soap); // clean up everything and close socket
}
}
soap_done(&soap); // close master socket
}
Type
"make server". That will compile the server. Now run the gSOAP client
against the gSOAP server. The client makes two calls, each ends with
different fault.
$ ./server &
$ ./client
calling first ...
SOAP FAULT: SOAP-ENV:Client
"Deliberately thrown exception."
MyFirstException.text=Input values are wrong.
calling second ...
SOAP FAULT: SOAP-ENV:Client
"Deliberately thrown exception."
MySecondException.number=1111
Creating a client in Axis
For creating client and server using Axis, you need Axis installation location
set in variable AXIS_HOME and TomCat installation in CATALINA_BASE.
First we must generate stub classes from the WSDL and write a client.
CallMyService.java
import faultdemo.*;
import java.rmi.RemoteException;
import java.net.URL;
public class CallMyService {
public static void main(String [] args) throws Exception {
String url = "http://localhost:10000/";
//String url = "http://localhost:8080/axis/services/MyService";
MyType myType = new MyServiceLocator().getMyService(new URL(url));
String[] inputs = new String[] { "first", "second" };
for(int i=0;i<2;i++) {
try {
myType.myOperation(inputs);
} catch (MyFirstException ex) {
System.out.println("MyFirstException");
System.out.println("ex.faultstring="+ex.getFaultString());
System.out.println("ex.text="+ex.getText());
} catch (MySecondException ex) {
System.out.println("MySecondException");
System.out.println("ex.faultstring="+ex.getFaultString());
System.out.println("ex.number="+ex.getNumber());
}
}
}
}
export CLASSPATH=.
for i in "$AXIS_HOME"/lib/*.jar; do CLASSPATH="$i:$CLASSPATH"; done
for i in "$CATALINA_BASE/common/lib"/*.jar; do CLASSPATH="$i:$CLASSPATH"; done
echo $CLASSPATH
java org.apache.axis.wsdl.WSDL2Java -v \
--server-side \
--deployScope Application \
--NStoPkg urn:myuri:1.0=faultdemo \
--output . \
MyService.wsdl
javac -source 1.4 faultdemo/*.java CallMyService.java
Now we have an Axis client for that service. Run it against the gSOAP server.
$ java CallMyService
MyFirstException
ex.faultstring=Deliberately thrown exception.
ex.text=Input values are wrong.
MySecondException
ex.faultstring=Deliberately thrown exception.
ex.number=1111
You can see that the faults thrown from C were correctly converted to Java
Exceptions.
Deploying a server in Axis
The WSDL2Java command used in the last section produced even
server-side stubs and a deployment descriptor for them.
You just need to edit faultdemo/MyServiceImpl.java
and provide implementation for the server side of the service:
faultdemo/MyServiceImpl.java
/**
* MyServiceImpl.java
*
* This file was auto-generated from WSDL
* by the Apache Axis 1.2RC3 Feb 28, 2005 (10:15:14 EST) WSDL2Java emitter.
*/
package faultdemo;
public class MyServiceImpl implements faultdemo.MyType{
public java.lang.String myOperation(java.lang.String myInput)
throws java.rmi.RemoteException, faultdemo.MyFirstException, faultdemo.MySecondException {
if("first".equals(myInput)) {
MyFirstException ex = new MyFirstException();
ex.setFaultString("Deliberately thrown");
ex.setText("Problem");
throw ex;
} else {
MySecondException ex = new MySecondException();
ex.setFaultString("Deliberately thrown");
ex.setNumber(2222);
throw ex;
}
}
}
Now you have to compile it and deploy it into Axis webservice inside TomCat.
javac -source 1.4 faultdemo/MyServiceImpl.java
cp -r faultdemo/ $CATALINA_BASE/webapps/axis/WEB-INF/classes/
Now you have to restart TomCat, or at least the Axis webapp, because it needs
to be able to find the new classes. Then do:
java org.apache.axis.client.AdminClient faultdemo/deploy.wsdd
The service is now deployed, but I found that the WSDL generated by Axis
on-the-fly is not correct to namespaces, so you need to provide the original WSDL file
to Axis. Copy it to classes dir and edit the deployment configuration:
cp MyService.wsdl $CATALINA_BASE/webapps/axis/WEB-INF/classes/
vi $CATALINA_BASE/webapps/axis/WEB-INF/server-config.wsdd
You need to add a line to the service config:
<service name="MyService" provider="java:RPC" style="wrapped" use="literal">
<wsdlFile>/MyService.wsdl</wsdlFile>
<operation name="myOperation" ...
Now restart the TomCat again and
check that the service is deployed by seeing http://localhost:8080/axis/servlet/AxisServlet
.
Change both clients so that they connect to the Axis server and run them:
$ java CallMyService
MyFirstException
ex.faultstring=Deliberately thrown
ex.text=Problem
MySecondException
ex.faultstring=Deliberately thrown
ex.number=2222
$ ./client
calling first ...
SOAP FAULT: SOAP-ENV:Server.generalException
"Deliberately thrown"
Detail: faultdemo.MyFirstException
MyFirstException.text=Problem
calling second ...
SOAP FAULT: SOAP-ENV:Server.generalException
"Deliberately thrown"
Detail: faultdemo.MySecondException
MySecondException.number=2222
It works ! Amazing :-)
Send any comments to Martin Kuba
.
Last updated: $Date: 2005/05/06 10:11:21 $ |
|