如何使用Savon Gem使用PHP to Rails调用SOAP wsdl API

I have to port code from a PHP app calling a WSDL API to Rails

here is my PHP code:

class naf
{
    protected $_soap;
    protected $_token = array('Token' => '');
    protected $_header;

    public function __construct()
    {
        $this->_soap = new SoapClient('http://apiwebsite/api.asmx?WSDL', array('trace' => true));
        $this->_header = new SoapHeader('http://apinamespace/api/', 'Authenticationtoken', $this->_token, true);
    }

    public static function forge() {
        return new static;
    }

    public function get_boards()
    {
        $result = $this->_makeCall('getBoardList', array());
        return $result->getBoardListResult->BoardList;
    }

    public function board_list() {
        $data = array();
        $result = $this->get_boards();
        foreach ($result as $row) {
            $data[$row->Boardid] = $row->LongName;
        }
        return $data;
    }

    public function login($id, $passwrd, $board)
    {
        $args = array('userid' => $id, 'password' => $passwrd, 'board' => $board);
        return $this->_makeCall('login', $args);
    }

    public function logoff($id, $sid)
    {
        $args = array('creaid' => $id, 'sessionid' => $sid);
        return $this->_makeCall('logoff', $args);
    }

    public function isLogin($id, $sid)
    {
        $args = array('creaid' => $id, 'sessionid' => $sid);
        return $this->_makeCall('isAuthenticated', $args);
    }

    protected function _makeCall($action, $args)
    {
        try {
            $result = $this->_soap->__soapCall($action, $args, null, $this->_header, $outHeaders);
            return $result;
        } catch (Exception $e) {
            error_log(print_r($e, true));
        }
    }
}

I tried a Lot of things with "Savon" but it seems that my header is never set. I even port it successfully to C#, but the SoapHeader always seem wrong in rails. I always get

ERROR: (soap:clientfault) System.Web.Services.Protocols.SoapException: Empty tokenid

It seems it can't see my header

P.S. The Token parameter is voluntary ommit in the code below

Here is my rail code:

require 'savon'

class Naf

  NAF_API_TOKEN = { Token: ''}

  def initialize()
    @wsdl = 'http://apiwebsite/api.asmx?WSDL'
    @header = { :Authenticationtoken => NAF_API_TOKEN }
  end

  def get_boards()
    puts "=========== Naf.get_boards ================== "

    result = makeCall(:get_board_list, {})

    return result.getBoardListResult.BoardList
  end

  def board_list()
    data = Array.new()
    results = get_boards()

    results.each do | row |
      data[row.Boardid] = row.LongName
    end

    return data

  end

  def login(id, passwrd, board)
    args = {:userid => id, :password => passwrd, :board => board}
    makeCall('logoff', args)
  end

  def logoff(id, sid)
    args = {:creaid => id, :sessionid => sid}
    makeCall('logoff', args)
  end

  def isLogin(id, sid)
    args = {:creaid => id, :sessionid => sid}
    makeCall('isAuthenticated', args)
  end

  private
  def makeCall(action, args)
    puts "=========== Naf.makeCall ================== "
    begin

      client = Savon.client(
          wsdl: @wsdl,
          soap_header: @header
          )
      return client.call(action, args)

    rescue Exception => e
      puts "ERROR: #{e.message}"
    end
  end

end

I just got the expect XML request:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://apinamespace/api/">
   <SOAP-ENV:Header>
      <ns1:Authenticationtoken SOAP-ENV:mustUnderstand="1">
         <ns1:Token>TOKENHERE</ns1:Token>
      </ns1:Authenticationtoken>
   </SOAP-ENV:Header>
   <SOAP-ENV:Body>
      <ns1:getBoardList />
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Im not sure if I did it the best way but I got it working:

I had to add a namespace definition and had to tweak the header to add my own "tag prefix" like that:

NAF_API_TOKEN = { 'tns:Token' => 'TOKEN'}

here is my updated code:

require 'savon'

class Naf

  #HACK I had to add the "TNS" keyword to make it work
  NAF_API_TOKEN = { 'tns:Token' => 'TOKEN'}

  def initialize()
    @wsdl = 'http://webapi/apiws/api.asmx?WSDL'
    @header = { 'tns:Authenticationtoken' => NAF_API_TOKEN }
  end

  def get_boards()
    puts "=========== Naf.get_boards ================== "
    result = makeCall(:get_board_list, {})

    resulthash = result.to_hash
    boardlist = resulthash[:get_board_list_response][:get_board_list_result][:board_list]

    return boardlist
  end

  def board_list()
    puts "=========== Naf.board_list ================== "

    results = get_boards()
    data = results.map { |row | {:boardid => row[:boardid], :long_name => row[:long_name]}}

    puts data.inspect
    return data

  end

  def login(id, passwrd, board)
    args = {:userid => id, :password => passwrd, :board => board}
    makeCall('logoff', args)
  end

  def logoff(id, sid)
    args = {:creaid => id, :sessionid => sid}
    makeCall('logoff', args)
  end

  def isLogin(id, sid)
    args = {:creaid => id, :sessionid => sid}
    makeCall('isAuthenticated', args)
  end

  private
  def makeCall(action, args)
    puts "=========== Naf.makeCall ================== "
    begin

      client = Savon.client(
          wsdl: @wsdl,
          namespace: 'http://namespace/api/', #We need that to add the "TNS" keyword
          soap_header: @header
          #log: true, pretty_print_xml: true # Uncomment to display wsdl request
          )


      return client.call(action, args)

    rescue Exception => e
      puts "ERROR: #{e.message}"
    end
  end

end