The Microsoft.com Web Services
went live yesterday, attracting an snarky-sounding post from Mark Pilgrim
and a confused-sounding one from Dave Winer
Having never done anything with SOAP
before, I thought I might as well start here and hack up a Python wrapper for the service.
I won't go into the details of how to use it right here, because I've already written them on that link above and in a big comment at the start of the code. The interesting thing is what it took to put this together.
It seems that SOAP is still not very widely supported. The Microsoft.com Web Services require you to use Microsoft and IBM's new WS-Security
extension to pass your authentication information. This isn't all that hard, but it's the sort of thing that usually comes for free with your protocol library. From what I can see, .NET and one or two Java libraries support it, but anyone on another platform has to do it all by hand.
Luckily, the web service documentation had some example WS-Security headers I could use as templates. Even so, I spent several hours trying to get SOAPPy
to generate the right XML in the SOAP-ENV:Header block, then eventually gave up and switched to the much lighter-weight ZSI
library. ZSI doesn't do as much for you, but it also doesn't stop you from doing much. The following snippet generates the XML I need, and a quick hack to ZSI/client.py let me pass it in in place of the authentication header it was expecting.
wsse = Element('wsse:Security')
ut = SubElement(wsse, 'wsse:UsernameToken')
def mkelem(parent, tagname, text, **attrs):
if tagname.find(':') == -1:
tagname = 'wsse:' + tagname
x = SubElement(parent, tagname)
x.text = text
for k,v in attrs.items():
mkelem(ut, 'Username', mstoken.token)
created = time.strftime('%Y-%m-%dT%H:%M:%SZ')
nonce = sha.new(str(random.random())).digest()
digest = sha.new(nonce + created + mstoken.pin).digest()
mkelem(ut, 'Password', binascii.b2a_base64(digest), Type='wsse:PasswordDigest')
mkelem(ut, 'Nonce', binascii.b2a_base64(nonce))
mkelem(ut, 'wsu:Created', created)
tree = ElementTree(wsse)
wsse = StringIO()
wsse_header = wsse.getvalue()
started accepting my calls, and it wasn't so hard from then on. Being used to XML-RPC, it feels weird to be encoding and decoding XML all the time when calling remote methods, but a bit of hacking (see the
class in the code) made most of that fairly trivial.
And now we have it, the first non-Microsoft client for the Microsoft.com Web Services
Share and enjoy!
... more like this: [