Thursday, June 25, 2009

Installing Ruby on Windows - it's all about the libraries

Ruby on windows is a bit lackluster, primarily due to the poor C compiler support making it hard to compile the source there, the documentation doesn't mention the extra libraries it requires and the fact that the One-Click-Installer is always several versions behind.

The issue here is that Ruby doesn't give you any hints as to where to find the required libraries, or even that it uses them, so installing is a bit of a trial and error affair. The other issue is that a couple of the error messages are a bit cryptic (there's one that states "Ordinal 3873 could not be located in dynamic library... that means openssl isn't installed) making it hard to pinpoint the fix.

So: The Cabin has a great tutorial (that I heartily recommend) for installing 1.9 on windows from the zipped binary distro, I found it only after I'd done most of these steps myself.
For those of you who want the Cliffs Notes version, here it is:


    Add the ruby/bin directory to your system path.

    Find zlib1.dll, libeay32.dll, libssl32.dll and readline5.dll, place them in your ruby/bin directory, they're libraries from the packages zlib, openssl and readline on http://gnuwin32.sourceforge.net

    Rename zlib1.dll to zlib.dll, libssl32.dll to ssleay32.dll and readline5.dll to readline.dll


And that's it. Installing an up to date Ruby distribution in 4 easy steps.

Tuesday, June 16, 2009

Using dsget and dsquery

Being a developer in the support department in a medium-large organisation forces you to do some odd bits and pieces that would usually be performed by the system admins in a bigger company. So this week I was asked to do a bit of command line trickery using some Active Directory administration tools to extract some user information.

The company I work for wanted mobile phone numbers extracted to files based on the email groups. For example I belong to a group, let's call it "Support" (how original), that is an email group in AD. Management wanted the name and mobile phone number of each person who's receives an email when it's sent to "Support" spit out to a file called "Support.csv" in the format "Username", "Mobile Number", to be used for updating an SMS application.

I won't cover the conversion from the odd format that comes out of the tools to CSV, suffice to say it's easy with python, or ruby or perl, so here's the command:


dsquery group ou="User Groups",dc=domainname,dc=net -name "Support" | dsget group -members -expand | dsget user -display -mobile -c > c:\Support.txt


In order from left to right, this command runs dsquery to find the any group called Support in the User Groups organisational unit on the domainname.net domain. It then runs dsget against it to spit out the full list of member objects, the -expand makes it expand all of the groups below it. It finally runs dsget against all of these objects and if it's a user pipes the display name and number out to Support.txt. The -c is important, it ensures that any groups that come out of the "dsget group" call don't crash the "dsget user" call.

Monday, June 1, 2009

Simple SOAP calls over SSL with Ruby

I deal with web services a lot in my day to day, some good, some nightmarish. Having scripts and example code to deal with them makes my job a lot easier. My example last week was a piece of python code that implemented CONNECT to allow you to make SSL encrypted requests across a proxy in Python. I had to write this code because the company I work for exposes it's web services to clients via SSL, and being able to offer example code to leverage our services in multiple languages is a good thing. Not being able to offer example code in a pretty mainstream (in web terms) language is a bad thing.

Therefore I wrote a similar script to last week's example that uses Ruby to post an XMP SOAP envelope request to an SSL secured site over a proxy. For enterprise use in apps expecting to make thousands of SOAP calls a day I would recommend users to build a full SOAP app using SOAP4R or some equivalent framework. However as this is often overkill for smaller apps that simply want to make a few calls to a web service, doing the request manually as a raw HTTP POST to send the SOAP envelope to the service is usually enough, and doesn't obscure the details of what SOAP really is, an HTTP POST request with a rigorously defined XML payload.

As an aside, to build the SOAP envelope for this example I heartily recommend SoapUI, it allows you to open a WSDL for a particular service and easily generate the XML for each SOAP action. It's really good, especially if you build SOAP web services in your day to day job. It's so good, in fact, that as you get more and more used to using it you might find it replacing the test harnesses you inevitably build to test the web services you build.

Back on topic, because we are building the XML as a string we need to inject any parameters for the SOAP call after we have built it. Luckily Ruby lets us replace substrings in strings pretty easily with the .sub! method. I use it to template my SOAP requests with {1} and {2} etc... Also to avoid being stung with content length issues make sure you finalize your XML before creating the headers dictionary so that your Content-Lenght ('Content-Length'=> soap_data.length) is correct. If you don't, the next 2 hours will be wasted while you go round in circles with HTTP errors that don't make too much sense.

Anyway: here's the code, you'll notice that it's a lot shorter that the Python code from last week, this is because the standard library gives you native SSL over Proxy support. Note the call 'http_session.use_ssl = true'.


require 'net/https'
require 'open-uri'

# Create the SOAP Envelope
soap_data = '''
This is where the SOAP xml would be drafted. I use {1} to template value fields.
'''
# normally I'd inject parameters into the SOAP Envelope using a call like:
# soap_data.sub!('{1}', "example string")

# Set Headers
headers = {
'Content-type'=> 'text/xml; charset=utf-8',
'SOAPAction'=> '""',
'User-Agent'=> 'The useragent you wish to use, useful if you ever have to debug at the other end...',
'Host'=> 'www.securedurl.com',
'Content-Length'=> soap_data.length
}

#create session object
uri = URI.parse("https://www.securedurl.com")
path = '/WebServiceHome/services/'
proxy = Net::HTTP::Proxy("aproxyserver",8080)
http_session = proxy.new(uri.host, uri.port)
http_session.use_ssl = true

#start the http session
http_session.start { |http|
# create the request
req = Net::HTTP::Post.new(path)
req.basic_auth mip_user, mip_password
headers.each{|key, val| req.add_field(key, val)}
# Post the request
resp, data = http.request(req, soap_data)
puts 'Code = ' + resp.code
puts 'Message = ' + resp.message
resp.each { |key, val| puts key + ' = ' + val }
puts data
}