Search code examples
ruby-on-railssavon

Can't specify header in SOAP call using Savon


When calling to an API for retrieving labels for post parcels I get frustrated with how hard it is to set up a SOAP call with ruby.

I know that the following works from a sandbox environment:

this is the SHOULD BE call

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://postnl.nl/cif/services/BarcodeWebService/IBarcodeWebService/GenerateBarcode</Action>
    <Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:Username>devc_!R4xc8p9</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">098fd559930983af31ef6630a0bb0c1974156561</wsse:Password>
      </wsse:UsernameToken>
    </Security>
  </s:Header>
  <s:Body>
    <GenerateBarcode xmlns:d6p1="http://postnl.nl/cif/domain/BarcodeWebService/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://postnl.nl/cif/services/BarcodeWebService/">
      <d6p1:Message>
        <d6p1:MessageID>5</d6p1:MessageID>
        <d6p1:MessageTimeStamp>28-06-2017 14:15:41</d6p1:MessageTimeStamp>
      </d6p1:Message>
      <d6p1:Customer>
        <d6p1:CustomerCode>DEVC</d6p1:CustomerCode>
        <d6p1:CustomerNumber>11223344</d6p1:CustomerNumber>
      </d6p1:Customer>
      <d6p1:Barcode>
        <d6p1:Type>3S</d6p1:Type>
        <d6p1:Range>DEVC</d6p1:Range>
        <d6p1:Serie>1000000-2000000</d6p1:Serie>
      </d6p1:Barcode>
    </GenerateBarcode>
  </s:Body>
</s:Envelope>

EDIT

As sugested I used build_request to inspect how my request looks. It actually looks like this:

<?xml version="1.0" encoding="UTF-8"?>
  <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsdl="http://tempuri.org/" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <Username>devc_!R4xc8p9</Username
    <Password>098fd559930983af31ef6630a0bb0c1974156561</Password>
  </env:Header>
  <env:Body><wsdl:GenerateBarcode><CustomerCode>DEVC</CustomerCode>
    <CustomerNumber>11223344</CustomerNumber>
    <Type>3S</Type>
    <Range>DEVC</Range>
    <Serie>1000000-2000000</Serie>
</wsdl:GenerateBarcode>
</env:Body>
</env:Envelope>

Comparing that with the xml above, it becomes clear that stuff is off. How to handle nesting?

This is when I build it myself like:

@client = Savon.client(
                :convert_request_keys_to => :camelcase,
                :raise_errors => false,
                :pretty_print_xml => true,
                :wsdl => 'https://testservice.postnl.com/CIF_SB/BarcodeWebService/1_1/?wsdl',
                :headers => {
                  'Username' => 'devc_!R4xc8p9',
                  'Password' => '098fd559930983af31ef6630a0bb0c1974156561'
                })

      @response = @client.call(:generate_barcode) do
        soap_header "Username" => 'devc_!R4xc8p9',
                    "Password" => '098fd559930983af31ef6630a0bb0c1974156561'
        message customer_code:   'DEVC',
                customer_number: 11223344,
                type:            '3S',
                range:           'DEVC',
                serie:           '1000000-2000000'
      end

I know my connection works, but also get back:

{:fault=>{:faultcode=>"s:CIF Framework Message Interceptor",
   :faultstring=>"Check CIFException in the detail section", :detail=>
     {:cif_exception=>{:errors=>{:exception_data=>{:description=>nil,
    :error_msg=>"Username/password is not specified.",
    :error_number=>"3"}},
  :@xmlns=>"http://postnl.nl/cif/services/common/", 
  :"@xmlns:i"=>"http://www.w3.org/2001/XMLSchema-instance"}}}}

So already in my header stuff goes wrong. Username/password is not specified. I've tried several combinations and I don't get why.

Update

@client = Savon.client(
        :env_namespace => :s,
        # :namespace_identifier => :none, # :none doesn't work... gets interpreted literary. Out-commeting gives wsdl:GenerateBarcode 
        :convert_request_keys_to => :camelcase,
        :raise_errors => false,
        :pretty_print_xml => true,
        :endpoint => 'https://testservice.postnl.com/CIF_SB/BarcodeWebService/1_1/BarcodeWebService.svc',
        :wsdl => 'https://testservice.postnl.com/CIF_SB/BarcodeWebService/1_1/?wsdl')

      soap_header = {
                      "Security" => {
                        "UsernameToken" => {
                          "Username" => 'devc_!R4xc8p9',
                          "Password" => '098fd559930983af31ef6630a0bb0c1974156561'
                        }
                      }
                    }


      message = {
                    "d6p1:Customer" => {
                      "d6p1:CustomerCode"    => 'DEVC',
                      "d6p1:CustomerNumber"  => 11223344
                    },
                    "d6p1:Barcode" => {
                      "d6p1:Type" => '3S',
                      "d6p1:Range" => 'DEVC',
                      "d6p1:Serie" => '1000000-2000000'
                    }
                }



      @request = @client.build_request(:generate_barcode) do
        soap_header soap_header
        message message
      end

      @response = @client.call(:generate_barcode) do
        soap_header soap_header
        message message

Which builds the following request:

<?xml version="1.0" encoding="UTF-8"?>
  <s:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://tempuri.org/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Security>
      <UsernameToken>
        <Username>devc_!R4xc8p9</Username
        <Password>098fd559930983af31ef6630a0bb0c1974156561</Password>
      </UsernameToken>
    </Security>
  </s:Header>
  <s:Body>
    <wsdl:GenerateBarcode>
      <d6p1:Customer>
        <d6p1:CustomerCode>DEVC</d6p1:CustomerCode
        <d6p1:CustomerNumber>11223344</d6p1:CustomerNumber>
      </d6p1:Customer>
      <d6p1:Barcode>
        <d6p1:Type>3S</d6p1:Type>
        <d6p1:Range>DEVC</d6p1:Range>
        <d6p1:Serie>1000000-2000000</d6p1:Serie>
      </d6p1:Barcode></s:GenerateBarcode>
</s:Body>

For as of yet, I get errors adding the wsse: namespace straight in the soap_header hash.

EDIT 2

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:wsdl="http://tempuri.org/"
  xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://postnl.nl/cif/services/BarcodeWebService/IBarcodeWebService/GenerateBarcode
    </Action>
    <Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <UsernameToken>
        <Username>devc_!R4xc8p9</Username>
        <Password>xxxxxxxx</Password>
      </UsernameToken>
    </Security>
  </s:Header>
  <s:Body>
    <wsdl:GenerateBarcode>
      <d6p1:Customer>
        <d6p1:CustomerCode>DEVC</d6p1:CustomerCode>
        <d6p1:CustomerNumber>11223344</d6p1:CustomerNumber>
      </d6p1:Customer>
      <d6p1:Barcode>
        <d6p1:Type>3S</d6p1:Type>
        <d6p1:Range>DEVC</d6p1:Range>
        <d6p1:Serie>1000000-2000000</d6p1:Serie>
      </d6p1:Barcode>
    </wsdl:GenerateBarcode>
  </s:Body>
</s:Envelope>

Comparing this to my should be call it seems that my <s:envelope ...> tag has too much information.

I'll make a follow up question of this probably.

EDIT 3

I restarted at Complex SOAP call in Ruby on Rails using Savon gets weird around the envelope and main operation


Solution

  • The Message hash, should be nested in the same way as the call expects it something like

    @client = Savon.client(
      :convert_request_keys_to => :camelcase,
      :raise_errors => false,
      :pretty_print_xml => true,
      :wsdl => 'https://testservice.postnl.com/CIF_SB/BarcodeWebService/1_1/?wsdl',
      :headers => {
        'Username' => 'devc_!R4xc8p9',
        'Password' => '098fd559930983af31ef6630a0bb0c1974156561'
    })
    
    @response = @client.call(:generate_barcode) do
      soap_header "Security" => {
                    "UsernameToken" => {
                      "Username" => 'devc_!R4xc8p9',
                      "Password" => '098fd559930983af31ef6630a0bb0c1974156561'
                    }
                  }
      message "GenerateBarcode" => {
                "Message" => {
                  "MessageID"   => 5, #might be different
                  "MessageTime" => Time.now, #might be different depending on use case
                },
                "Customer" => {
                  "CustomerCode"   => 'DEVC',
                  "CustomerNumber" => 11223344
                },
                "Barcode" => {
                  "Type"  => '3S',
                  "Range" => 'DEVC',
                  "Serie" => '1000000-2000000'
                }
              }
      end
    

    EDIT

    Referencing this page it looks like you can add the action tag and elements like this

     'Action' =>  "http://postnl.nl/cif/services/BarcodeWebService/IBarcodeWebService/GenerateBarcode",
     :attributes! => {
       "s:mustUnderstand" => "1",
       "xmlns" => "http://schemas.microsoft.com/ws/2005/05/addressing/none"
     }
    

    Referencing this page it looks like you can set the namespaces directly "wsse:UsernameToken" instead of "UsernameToken"

    which should result in the following

    @client = Savon.client(
      :convert_request_keys_to => :camelcase,
      :raise_errors => false,
      :pretty_print_xml => true,
      :wsdl => 'https://testservice.postnl.com/CIF_SB/BarcodeWebService/1_1/?wsdl',
      :headers => {
        'Username' => 'devc_!R4xc8p9',
        'Password' => '098fd559930983af31ef6630a0bb0c1974156561'
    })
    
    @response = @client.call(:generate_barcode) do
      soap_header "Security" => {
                    "wsse:UsernameToken" => {
                      "wsse:Username" => 'devc_!R4xc8p9',
                      "wsse:Password" => '098fd559930983af31ef6630a0bb0c1974156561'
                    }     
                  "Action" =>  "http://postnl.nl/cif/services/BarcodeWebService/IBarcodeWebService/GenerateBarcode",
                  :attributes! => {
                    "s:mustUnderstand" => "1",
                    "xmlns" => "http://schemas.microsoft.com/ws/2005/05/addressing/none"
                  }
                }
      message "GenerateBarcode" => {
                "Message" => {
                  "MessageID"   => 5, #might be different
                  "MessageTime" => Time.now, #might be different depending on use case
                },
                "Customer" => {
                  "CustomerCode"   => 'DEVC',
                  "CustomerNumber" => 11223344
                },
                "Barcode" => {
                  "Type"  => '3S',
                  "Range" => 'DEVC',
                  "Serie" => '1000000-2000000'
                }
              }
      end