Interoperable WSDL faults in gSOAP and Apache Axis webservices

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

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[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.

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 $

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值