轉載:http://www.gridlab.org/WorkPackages/wp-5/guide/faults.htmlhtml
This 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. java
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. web
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. apache
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. app
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. webapp
<?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. socket
A SOAP message with the first fault looks like this: ide
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. ui
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. this
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; } * );
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 coreYou 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.
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
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[i]); } 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.
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.wsddYou 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 :-)