How to parse a response soap message using soap4r

Posted by win
on Wednesday, April 16

Soap4r is a very helpful utility. Using ‘wsdl2ruby’ you can generate local methods to invoke web service methods via SOAP.

wsdl2ruby.rb --wsdl path_to_your_wsdl --type client | server

The only problem I had with soap4r during my implementation was the difficulty I had with manually parsing the soap response from the web service. Though the web service methods were generated perfectly, I needed additional information from the soap response (the soap header information, not to be confused with the http header info) that was not provided by the wsdl, thus left out of the wsdl2ruby code generation.

SOAP4r uses a private send_post method in the HTTPStreamHandler class of the SOAP module. In the send_post method an net/http instance uses the post method to post the message as a string to the whichever url was sent in the SOAP call. The post method returns the response from the remote server. However, HTTPStreamHandler does not make this available to you. To change all you need to do is make the response variable an instance variable.

The new res instance variable will return an HTTP::Message class. You will then need to access the body instance variable in the Body class. However, this will also need an accessor method monkey-patched through HTTP::Message::Body. (Also included below)

Monkey-patch the private send_post method in SOAP::HTTPStreamHandler to change the res local variable to an instance variable

module SOAP
  class HTTPStreamHandler
  attr_accessor :res
  private
    def send_post(url, conn_data, charset)
        conn_data.send_contenttype ||= StreamHandler.create_media_type(charset)
        if @wiredump_file_base
          filename = @wiredump_file_base + '_request.xml'
          f = File.open(filename, "w")
          f << conn_data.send_string
          f.close
        end

        extheader = {}
        extheader['Content-Type'] = conn_data.send_contenttype
        extheader['SOAPAction'] = "\"#{ conn_data.soapaction }\"" 
        extheader['Accept-Encoding'] = 'gzip' if send_accept_encoding_gzip?
        send_string = conn_data.send_string
        @wiredump_dev << "Wire dumped:\n\n" if @wiredump_dev
        begin
          retry_count = 0
          while true
            %{color:red}@res% = @client.post(url, send_string, extheader)
            if RETRYABLE and HTTP::Status.redirect?(@res.status)
              retry_count += 1
                if retry_count >= MAX_RETRY_COUNT
                  raise HTTPStreamError.new("redirect count exceeded")
                end
              url = @res.header["location"][0]
             puts "redirected to #{url}" if $DEBUG
            else
              break
            end
         end
        rescue
          @client.reset(url)
        raise
       end
       @wiredump_dev << "\n\n" if @wiredump_dev
       receive_string = @res.content
       if @wiredump_file_base
         filename = @wiredump_file_base + '_response.xml'
         f = File.open(filename, "w")
         f << receive_string
         f.close
       end
       case @res.status
       when 405
         raise PostUnavailableError.new("#{ @res.status }: #{ @res.reason }")
       when 200, 202, 500
         # Nothing to do.  202 is for oneway service.
       else
        raise HTTPStreamError.new("#{ @res.status }: #{ @res.reason }")
       end

      # decode gzipped content, if we know it's there from the headers
      if @res.respond_to?(:header) and !@res.header['content-encoding'].empty? and
          @res.header['content-encoding'][0].downcase == 'gzip'
        receive_string = decode_gzip(receive_string)
      # otherwise check for the gzip header
      elsif @accept_encoding_gzip && receive_string[0..1] == "\x1f\x8b" 
        receive_string = decode_gzip(receive_string)
      end
      conn_data.receive_string = receive_string
      conn_data.receive_contenttype = @res.contenttype
      conn_data
    end
  end
end

module HTTP
    class Message::Body    
      attr_accessor :body
    end
end

After the monkey-patching, the response message will be accessible after invoking a web service method using the client class generated by wsdl2ruby. The response message will become available in the res accessor method through the streamhandler method of the client class that was generated by wsdl2ruby. The class will be called something like “ClientAPISoap” – this will be defined in the defaultDriver.rb file.

In my case:

 driver = ClientAPISoap.new
results = driver.some_ws_method(with_parameters).getResults 
#getResults is also a ws method
raw_response_message = driver.streamhandler.res.body.body

You can then use REXML to parse whatever you need in the SOAP response message body.

Comments

Leave a response