[PATCH] wsdl fault handling

18 views
Skip to first unread message

Peter Gardfjäll

unread,
Feb 24, 2007, 7:30:49 AM2/24/07
to soa...@googlegroups.com
Hi all,
 
I wrote to this list a few weeks ago regarding my problem to get wsdl2ruby to generate proper SOAP faults from operation faults defined in the service wsdl file.
 
Since I did not receive any replies I decided to take matters into my own hands. I have patched soap4r to handle wsdl-defined faults more gracefully -- allowing the service endpoint to raise wsdl-defined faults which then get properly formatted in a <SOAP:Fault> message. I have cross-tested the solution with Java JAX-WS ( i.e. a java client against a ruby service and vice versa) and it works fine. It would be great if anyone could apply the changes (found below) to the soap4r code base and make them part of the next release.
I guess some testing is necessary to make sure that the changes do not break anything, but from my preliminary testing they appear to work well.
 
To raise wsdl-defined faults from your service endpoint, the following steps need to be taken:
In your wsdl file:
  1) define the type of your fault in a global element declaration:
      <element name="AddFault">
        <complexType>
          <sequence>
            <element name="Reason" type="string"/>
            <element name="Severity" type="string"/>
          </sequence>
        </complexType>
      </element>
  2) define a message that references your fault element:
      <message name="AddFaultMessage">
        <part name="fault" element="tns:AddFault"/>
      </message>
  3) Define the fault as a possible fault raised by your operation:
     <operation name="Add">
       <input  name="Add" message="tns:AddMessage"/>
       <output name="AddResponse" message="tns:AddResponseMessage"/>
       <fault  name="AddFault" message="tns:AddFaultMessage"/>
     </operation>
  4) Add the fault to the operation binding
    <binding >
       ...
      <operation name="Add">
        <soap:operation soapAction="Add"/>
        <input>
          <soap:body use="literal"/>
        </input>
        <output>
          <soap:body use="literal"/>
        </output>
        <fault name="AddFault">
          <soap:fault name="AddFault"/>
        </fault>

      </operation>
       ...
    </binding>
 
That's it. Now, you should be able to raise the fault from within your service operation code.
    raise AddFault.new("Value #{request.value} is too large", "Critical")
 
and soap4r will reply to the sender with a SOAP fault looking something like:
 
<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:env=" http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <env:Fault xmlns:n1=" http://schemas.xmlsoap.org/soap/encoding/"
        env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/ ">
      <faultcode>env:Server</faultcode>
      <faultstring xsi:type="xsd:string">AddFault</faultstring>
      <faultactor xsi:type="xsd:string">app</faultactor>
      <detail xmlns:n2="http://fault.test/Faulttest">
        <n2:AddFault>
          <Reason>Value 101 is too large</Reason>
          <Severity>Critical</Severity>
        </n2:AddFault>
      </detail>
    </env:Fault>
  </env:Body>
</env:Envelope>
 
 
I have tried to make the wsdl:fault generation adhere to WS-Interoperability (WS-I) conventions:
1) The (single) message part of the fault must refer to a global _element_ declaration (not a type)
2) If the fault binding specifies a "use" attribute, it must have value "literal"
 
Find the required changes to the soap4r-20061022 code below.
 
cheers, Peter
 
diff -rBu ../../soap4r-20061022/lib/soap/rpc/router.rb ./soap/rpc/router.rb
--- ../../soap4r-20061022/lib/soap/rpc/router.rb 2006-10-22 06:29:59.000000000 +0200
+++ ./soap/rpc/router.rb 2007-02-24 11:18: 54.843750000 +0100
@@ -168,8 +168,12 @@
         op.call(env.body, @mapping_registry, @literal_mapping_registry,
           create_mapping_opt)
       default_encodingstyle = op.response_default_encodingstyle
-    rescue Exception
-      soap_response = fault($!)
+    rescue Exception => e
+      # If a wsdl fault was raised by service, the fault declaration details
+      # is kept in wsdl_fault. Otherwise (exception is a program fault)
+      # wsdl_fault is nil
+      wsdl_fault_details = op.faults[e.class.name]
+      soap_response = fault(e, wsdl_fault_details)
       default_encodingstyle = nil
     end
     conn_data.is_fault = true if soap_response.is_a?(SOAPFault)
@@ -181,7 +185,7 @@
 
   # Create fault response string.
   def create_fault_response(e)
-    env = SOAPEnvelope.new(SOAPHeader.new, SOAPBody.new (fault(e)))
+    env = SOAPEnvelope.new(SOAPHeader.new, SOAPBody.new(fault(e, nil)))
     opt = {}
     opt[:external_content] = nil
     response_string = Processor.marshal(env, opt)
@@ -318,20 +322,39 @@
   end
 
   # Create fault response.
-  def fault(e)
+  def fault(e, wsdl_fault_details)
     if e.is_a?(UnhandledMustUnderstandHeaderError)
       faultcode = FaultCode::MustUnderstand
     else
       faultcode = FaultCode::Server
     end
-    detail = Mapping.obj2soap(Mapping::SOAPException.new(e),
-      @mapping_registry)
-    detail.elename ||= XSD::QName::EMPTY # for literal mappingregstry
+
+    # If the exception represents a WSDL fault, the fault element should
+    # be added as the SOAP fault <detail> element. If the exception is a
+    # normal program exception, it is wrapped inside a custom SOAP4R
+    # SOAP exception element.
+    detail = nil
+    if (wsdl_fault_details)
+      registry =
+        wsdl_fault_details[:use] == "literal" ? @literal_mapping_registry : @mapping_registry
+      faultQName = XSD::QName.new(wsdl_fault_details[:ns], wsdl_fault_details[:name])
+      detail = Mapping.obj2soap(e, registry, faultQName)
+      # wrap fault element (SOAPFault swallows top-level element)
+      wrapper = SOAP::SOAPElement.new(faultQName)
+      wrapper.add(detail)
+      detail = wrapper
+    else       
+      # Exception is a normal program exception. Wrap it.
+      detail = Mapping.obj2soap(Mapping::SOAPException.new(e),
+                                @mapping_registry)
+      detail.elename ||= XSD::QName::EMPTY # for literal mappingregstry
+    end
+
     SOAPFault.new(
       SOAPElement.new(nil, faultcode),
       SOAPString.new(e.to_s),
       SOAPString.new(@actor),
-      detail)
+      detail)   
   end
 
   def create_mapping_opt
@@ -345,6 +368,7 @@
     attr_reader :response_style
     attr_reader :request_use
     attr_reader :response_use
+    attr_reader :faults
 
     def initialize(soapaction, name, param_def, opt)
       @soapaction = soapaction
@@ -353,6 +377,7 @@
       @response_style = opt[:response_style]
       @request_use = opt[:request_use]
       @response_use = opt[:response_use]
+      @faults = opt[:faults]
       check_style(@request_style)
       check_style(@response_style)
       check_use(@request_use)
diff -rBu ../../soap4r-20061022/lib/wsdl/param.rb ./wsdl/param.rb
--- ../../soap4r-20061022/lib/wsdl/param.rb 2005-07-14 14:46: 02.000000000 +0200
+++ ./wsdl/param.rb 2007-02-19 22:54:45.178378900 +0100
@@ -56,7 +56,7 @@
       o
     when SOAPFaultName
       o = WSDL::SOAP::Fault.new
-      @soap_fault = o
+      @soapfault = o
       o
     when DocumentationName
       o = Documentation.new
diff -rBu ../../soap4r-20061022/lib/wsdl/soap/classDefCreatorSupport.rb ./wsdl/soap/classDefCreatorSupport.rb
--- ../../soap4r-20061022/lib/wsdl/soap/classDefCreatorSupport.rb 2006-10-01 16:14: 27.000000000 +0200
+++ ./wsdl/soap/classDefCreatorSupport.rb 2007-02-19 16:11:42.000000000 +0100
@@ -54,10 +54,10 @@
 #
 __EOD__
     unless fault.empty?
-      faultstr = (fault.collect { |f| dump_inout_type(f).chomp }).join(', ')
+      faultstr = (fault.collect { |f| dump_inout_type(f).chomp }).join("\n")
       str <<<<__EOD__
 # RAISES
-#   #{faultstr}
+#{faultstr}
 #
 __EOD__
     end
diff -rBu ../../soap4r-20061022/lib/wsdl/soap/definitions.rb ./wsdl/soap/definitions.rb
--- ../../soap4r-20061022/lib/wsdl/soap/definitions.rb 2006-10-20 17:51:38.000000000 +0200
+++ ./wsdl/soap/definitions.rb 2007-02-24 12:53:04.484375000 +0100
@@ -82,10 +82,17 @@
     collect_fault_messages.each do |name|
       faultparts = message(name).parts
       if faultparts.size != 1
- raise RuntimeError.new("expecting fault message to have only 1 part")
+ raise RuntimeError.new("Expecting fault message \"#{name}\" to have ONE part")
       end
-      if result.index(faultparts[0].type).nil?
- result << faultparts[0].type
+      fault_part = faultparts[0]
+      # WS-I Basic Profile Version 1.1 (R2205) requires fault message parts
+      # to refer to elements rather than types
+      if not fault_part.element
+ raise RuntimeError.new("Fault message \"#{name}\" part \"#{fault_part.name}\" must specify an \"element\" attribute")
+      end
+
+      if result.index(fault_part.element).nil?
+ result << fault_part.element
       end
     end
     result
@@ -93,11 +100,54 @@
 
 private
 
+  def operation_binding(binding, operation_qname)
+    binding.operations.each do |op_binding|
+      if (op_binding.soapoperation_name == operation_qname)
+        return op_binding
+      end
+    end
+  end
+
+  def get_fault_binding(op_binding, fault_name)
+    op_binding.fault.each do |fault|
+      return fault if fault.name == fault_name
+    end
+    return nil
+  end
+
+  def op_binding_declares_fault(op_binding, fault_name)
+    return get_fault_binding(op_binding, fault_name) != nil
+  end
+
   def collect_fault_messages
     result = []
     porttypes.each do |porttype|
       porttype.operations.each do |operation|
  operation.fault.each do |fault|
+          # Make sure the operation fault has a name
+          if not fault.name
+            raise RuntimeError.new("Operation \"#{ operation.name}\": fault must specify a \"name\" attribute")
+          end
+          operation_qname = XSD::QName.new(operation.targetnamespace, operation.name )
+          binding = porttype.find_binding()
+          op_binding = operation_binding(binding, operation_qname)
+          # Make sure that portType fault has a corresponding soap:fault
+          # definition in binding section.
+          if not op_binding_declares_fault(op_binding, fault.name)
+            raise RuntimeError.new("Operation \"#{operation.name}\", fault \"#{ fault.name}\": no corresponding wsdl:fault binding found with a matching \"name\" attribute")         
+          end
+         
+          fault_binding = get_fault_binding(op_binding, fault.name)
+          if fault_binding.soapfault.name != fault_binding.name
+            puts "WARNING: name of soap:fault \"#{fault_binding.soapfault.name}\" doesn't match the name of wsdl:fault \"#{fault_binding.name}\" in operation \"#{ operation.name}\" \n\n"
+          end
+          # According to WS-I (R2723): if in a wsdl:binding the use attribute
+          # on a contained soapbind:fault element is present, its value MUST
+          # be "literal".         
+          if fault_binding.soapfault.use and fault_binding.soapfault.use != "literal"
+            raise RuntimeError.new("Operation \"#{ operation.name}\", fault \"#{fault.name}\": soap:fault \"use\" attribute must be \"literal\"")         
+          end
+
    if result.index (fault.message).nil?
      result << fault.message
    end
diff -rBu ../../soap4r-20061022/lib/wsdl/soap/methodDefCreator.rb ./wsdl/soap/methodDefCreator.rb
--- ../../soap4r-20061022/lib/wsdl/soap/methodDefCreator.rb 2006-10-01 16:14: 27.000000000 +0200
+++ ./wsdl/soap/methodDefCreator.rb 2007-02-24 12:04:09.718750000 +0100
@@ -92,6 +92,19 @@
 private
 
   def dump_method(operation, binding)
+    op_faults = {}
+    binding.fault.each do |fault|
+      op_fault = {}
+      soapfault = fault.soapfault
+      op_fault[:ns] = fault.name.namespace
+      op_fault[:name] = fault.name.name
+      op_fault[:namespace] = soapfault.namespace
+      op_fault[:use] = soapfault.use || "literal"
+      op_fault[:encodingstyle] = soapfault.encodingstyle || "document"
+      op_faults[ fault.name.name] = op_fault
+    end
+    op_faults_str = op_faults.inspect
+
     name = safemethodname(operation.name)
     name_as = operation.name
     style = binding.soapoperation_style
@@ -115,7 +128,8 @@
   #{dq(name)},
   #{paramstr},
   { :request_style =>  #{sym(style.id2name)}, :request_use =>  #{sym(inputuse.id2name)},
-    :response_style => #{sym(style.id2name)}, :response_use => #{sym(outputuse.id2name)} }
+    :response_style => #{sym(style.id2name)}, :response_use => #{sym(outputuse.id2name)},
+    :faults => #{op_faults_str} }
 __EOD__
     if inputuse == :encoded or outputuse == :encoded
       @encoded = true

NAKAMURA, Hiroshi

unread,
Apr 29, 2007, 10:54:30 PM4/29/07
to soa...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi,

Sorry for late reply.

Peter Gardfjäll wrote:
> I wrote to this list a few weeks ago regarding my problem to get
> wsdl2ruby to generate proper SOAP faults from operation faults defined
> in the service wsdl file.
>
> Since I did not receive any replies I decided to take matters into my
> own hands. I have patched soap4r to handle wsdl-defined faults more
> gracefully -- allowing the service endpoint to raise wsdl-defined faults
> which then get properly formatted in a <SOAP:Fault> message. I have
> cross-tested the solution with Java JAX-WS ( i.e. a java client against
> a ruby service and vice versa) and it works fine. It would be great if
> anyone could apply the changes (found below) to the soap4r code base and
> make them part of the next release.

Thank you very much. I've not yet used wsdl:fault definition...

I'll apply the patch and it must help users in the future.

Regards,
// NaHi
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (Cygwin)

iQEVAwUBRjVaZR9L2jg5EEGlAQJoOQf/ZN8D8LSLbvH2n9kBYGl2RkWg8Th1od/c
6f6NYmTR4er5OBdcOYCbMnXGU9iNi3MBoePhtlbYhnf0Wg2PQXBzIfIQq568qRd+
f72Yy+A7Q8y69usrpa7UXKwCf5lYm3LxNGBjoC9Y66vt3viGkIHPrfAHarO+6lLO
fw4UWqzu5RErLApgcS8f8Mp4aViht+ovkEl8v/O0vSCWLcmwKiROsM86F4ICldXV
NPOA3v5AvtbFj3AW1gyHWudHCGz8/cseaxpx8Y0KfW2e1jkBweXQ9EGuCZTQpGjR
KbDMelc2LLMhdaFdfgBIvsBLj1R8WRJNscvD+BSZmhku+5F1zfzQKA==
=G456
-----END PGP SIGNATURE-----

Reply all
Reply to author
Forward
0 new messages