Search code examples
rubysoapsavon

Ruby Savon 2 error: Savon::SOAPFault ((SOAP-ENV:Server) The namespace prefix is not allowed to start with the reserved string "xml".)


I am trying to connect to a company that uses SOAP remote procedure calls so I am using the Savon Gem. Here is what I have to setup the authentication:

require 'savon'

client = Savon.client(
  wsdl: 'https://customdomain.companydomain.com:port/wsdlcode/wsdl/xmlBridge',
  endpoint: 'https://customdomain.companydomain.com:port/endpointcode/soap/xmlBridge',
  basic_auth: ["username", "password"],
  env_namespace: :soapenv,
  namespace_identifier: :web, 
  logger: Rails.logger, 
  log_level: :debug, 
  log: true, 
  :pretty_print_xml => true, 
  encoding: 'UTF-8'
)

This seems to work. I can even call "client.operations" to get a list of the functions that can be called. When I run

response = client.call(:get_active_employees) 

to try to get an employee list, I get the error message:

Savon::SOAPFault ((SOAP-ENV:Server) The namespace prefix is not allowed to start with the reserved string "xml".)

Does anyone know how to fix this?


Solution

  • The issue here is that the wsdl is likely including incorrect namespaces that Savon is automatically adding into the xml that you are sending back in the request. If you look at the lib/savon/builder.rb file in the Savon Gem by using: gem which savon

    to determine where your salon gem is located and then opening the enclosing gem folder in your IDE. Which should be something along the lines of /Users/username/.rvm/gems/ruby-2.5.1/gems/savon-2.13.0/ if your on a mac

    Open the builder.rb file lib/savon/builder.rb

    Here you'll see a method namespaces which unfortunately includes all the parsed namespaces from the wsdl:

              @wsdl.parser.namespaces.each do |identifier, path|
                next if namespaces.key?("xmlns:#{identifier}")
    
                namespaces["xmlns:#{identifier}"] = path
              end
            end
    

    If that wsdl includes an invalid namespace it will be included right back in the output when you perform your call.

    To fix this you need to add a new file in config/initializers/savon_monkey_patch.rb that contains the attached code:

    Savon::Builder.class_eval do
      private
    
      def namespaces
        @namespaces ||= begin
                          namespaces = Savon::Builder::SCHEMA_TYPES.dup
    
                          # check namespace_identifier
                          namespaces["xmlns#{namespace_identifier.nil? ? '' : ":#{namespace_identifier}"}"] =
                            @globals[:namespace] || @wsdl.namespace
                          # check env_namespace
                          namespaces["xmlns#{env_namespace && env_namespace != "" ? ":#{env_namespace}" : ''}"] =
                            Savon::Builder::SOAP_NAMESPACE[@globals[:soap_version]]
    
                          if @wsdl&.document
                            @wsdl.parser.namespaces.each do |identifier, path|
                              # this is to get rid of the xml namespaces that are invalid in a soap document
                              next if namespaces.key?("xmlns:#{identifier}") || identifier.match?(/^xml/)
    
                              namespaces["xmlns:#{identifier}"] = path
                            end
                          end
    
                          namespaces
                        end
      end
    end
    
    

    The key line being identifier.match?(/^xml/) which will exclude any namespaces that begin with xml from your outgoing request. I hope this helps.