Making Soap Requests with Ruby (on Rails even)
If you want to interact with another web-service on the inter-webs as we know it, sooner or later you will find the need to make a SOAP call using Ruby.
Today I ran into such a need.
I looked at a few things already out there that could do it, but for the most part, it was overkill.
So I tried to figure out how to roll my own, and this is what I found ::
First, I saw this post that seems to be the easiest way to roll your own
I ran into errors ::
/usr/lib/ruby/1.8/net/http.rb:560:in `initialize': getaddrinfo: Name or service not known (SocketError)
Hmm, Name or Service not known, and the error pointed to this line in the code
resp, data = http.post(path, data, headers)
So all my Ruby code worked, but that failed.
So I looked a little deeper, and found this email exchange
Ah, ok, so I tried that – parsed out the URL I was attempting to hit, and then went for it.
Got a new error
/usr/lib/ruby/1.8/net/http.rb:560:in `initialize': getaddrinfo: Servname not supported for ai_socktype (SocketError)
This was the result of a silly mistake of mine wherein for the Headers section I had the following
headers = {
'Referer' => 'http://www.brickfactor.net',
'Content-Type' => 'text/xml',
'Host' => 'https://www.site.co.uk/some_path/to_some_file.pl'
}
The way it needed to look was
headers = {
'Referer' => 'http://www.brickfactor.net',
'Content-Type' => 'text/xml',
'Host' => 'www.site.co.uk'
}
Ah wonderful – let’s hit it again, and see what happens.
Code = 400
Message = Bad Request
connection = close
content-type = text/html; charset=iso-8859-1
date = Mon, 17 Aug 2009 22:24:45 GMT
server = Apache/2.2.3 (CentOS)
content-length = 323
<h1>Bad Request</h1>
Your browser sent a request that this server could not understand.
<hr />
<address>Apache/2.2.3 (CentOS) Server at https://www.site.co.uk Port 443</address>
New error, but this time – atleast it was from the server at the other end. So I’d made a connection, but the server didn’t understand me (ah – story of my life you say? Well, you too might need a SOAPAction)
So I went digging deeper. Adding this line to the Headers section, made it work.
'SOAPAction' => 'https://www.site.co.uk/NAMESPACE1#METHOD_NAME'
Technically SOAPAction is supposed to match uri#method I believe, the above is just what worked for me.
Obviously, if there is an easier way, I’m very very interested.
The final code looks like this
require 'net/http'
require 'net/https'
require 'uri'
# Create the http object
url = "https://www.site.co.uk/some_path/to_some_file.pl"
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if (uri.scheme == 'https')
# Create the SOAP Envelope
data = <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="https://www.site.co.uk/NAMESPACE"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:stock_qty>
<param0 xsi:type="xsd:string">API_KEY</param0>
<param1 xsi:type="xsd:int">INTEGER</param1></ns1:stock_qty>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
EOF
# Set Headers
headers = {
'Referer' => 'http://www.brickfactor.net',
'Content-Type' => 'text/xml',
'Host' => 'www.site.co.uk',
'SOAPAction' => 'https://www.site.co.uk/NAMESPACE#METHOD'
}
# Post the request
resp, data = http.post(uri.path, data, headers)
# Output the results
puts 'Code = ' + resp.code
puts 'Message = ' + resp.message
resp.each { |key, val| puts key + ' = ' + val }
puts data