Toward mailnews Exchange Web Services support: SOAP calls
February 1, 2010 – 2:28 pmI’ve embarked on an effort to investigate adding support for Exchange server to the mailnews code. Although Exchange in Windows has traditionally used port 135-based protocols, my understanding is that the future for them is SOAP-based Exchange Web Services (EWS). As a first step, I wanted to get a basic SOAP library working in current mailnews code.
I considered a variety of approaches to this. One extension “Asertiva Thunderbird Extension for Sugar” uses the IBM/Prototype js library for SOAP access. Others recommended that I consider one of the open source SOAP libraries, such as a python-based library, or Apache’s AXIS2 library. Or that I cooperate with the existing project to provide an open-source method of accessing Exchange server.
But I’m not sure of how “open source” I want all of this to be. From my perspective, “open source” as a charitable activity is not successful. We all need to eat, and so the revenue model needs to be clear if a project is going to be more than a phase of life I am going through at the moment. So I would rather keep my options open until I understand that better. Anyway, that’s a long discussion which is beyond the scope of the current posting, which is supposed to be a status update.
I am still in an education phase, trying to understand SOAP and the related protocols, and to figure out what exactly I gain from using any existing library versus doing things more directly from the raw XML. So as both a trial and education step (and against the recommendation of others I might add) I’ve tried to update the old Gecko webservices extension to work in current Gecko 1.9.2, and to work with some current Microsoft SOAP protocols.
Rather than start with EWS, I started with the simpler BING search calls. I used existing Microsoft demos in C#, and could capture the communications using Wireshark to see what I was supposed to be sending and receiving.
Updating the abandoned webservices extension to Gecko 1.9.2
After testing webservices some under an old Firefox 2 build, I upgraded portions of the code to work under a current comm-central trunk build, using Gecko 1.9.2. My requirements are somewhat simpler than the original extension:
- Most importantly, my target is chrome-based extensions rather than browser code, so a lot of the security issues that FF folks worried about were not important to me.
- I had no interest also in allowing native JS creation of components, as I could rely on using .createInstance calls instead.
- At least initially, I am not supporting the reading of wsdl files, nor the associated automatic creation of proxy calls and interfaces. Instead, I read in a schema file, and generate my own code for each method. My understanding is that Microsoft, in their EWS libraries, also does not actually automatically generate method calls from a WSDL-based proxy, but follows this same approach of starting with the schema files.
That allowed me to avoid about half of the existing code, and focus on the /schema and /soap directories of the webservices extension.
Looking at my hg logs, it took one week of development time, and 17 patches to get to the point where I could write a unit test under Gecko 1.9.2, and confirm that I could create webservices components using a unit test. (I’m doing things in a test-driven development fashion, writing XPCSHELL unit tests to try out different features of webservices.)
Learning and testing Gecko webservices
Although there was a little old documentation around on Gecko webservices, ultimately I just needed to learn things the old fashioned way, reading the code and its interfaces and experimenting to see what worked. I took little baby steps, starting first with an XML schema primer and eventually working my way toward duplicating the functionality in some Microsoft Bing search demo C# code. This phase, starting from the first demonstration of loading of SOAP components under Gecko 1.9.2 through testing of encoding and decoding of a Bing search message, took about 2 weeks of coding and 23 patches, with the creation of 22 unit tests in the process.
The main issue that I had to deal with in the existing SOAP code is that it did not support maxOccurs>1 schema types, such as this one from the Bing schema:
<xsd:complexType name="ArrayOfNewsArticle">
<xsd:sequence>
<xsd:element minOccurs="0" maxOccurs="unbounded"
name="NewsArticle" type="tns:NewsArticle" />
</xsd:sequence>
</xsd:complexType>
I solved this by using an nsIArray to hold multiple values of the same element.
Sample Code
So now I can do a Bing search in an XPCSHELL test, and decode and test the results. I want to show some of the js that I use, to give some idea of the complexity (or lack thereof) of using this.
I create a class BingSearch, then encode some values to setup the search. A basic call for a search looks like this:
function run_test()
{
do_test_pending();
getNews = new BingSearch();
getNews.Query = "obama";
getNews.Options = ["EnableHighlighting"];
getNews.Sources = ["News"];
getNews.News = {Offset: 0, SortBy: "Relevance", Count: 1};
getNews.invoke(getNewsListener);
}
The “BingSearch” object is presumably what a sophisticated library would create automatically from the WSDL file. Instead, I create it by hand. Here’s my partial implementation, that does not support all of the allowed inputs to a Bing search, but works for my tests:
function BingSearch()
{
// defaults
this.Version = "2.0";
this.AppId = "<you get this from Microsoft for your application>";
this.Market = "en-us";
}
BingSearch.prototype = new BingBase();
BingSearch.prototype.invoke = function BingSearch_invoke(aSOAPResponseListener)
{
let parametersBag = objectPropertyBag({
Version: this.Version,
Market: this.Market,
Query: this.Query,
AppId: this.AppId,
Options: arrayPropertyBag("SearchOption", this.Options),
Sources: arrayPropertyBag("SourceType", this.Sources),
News: objectPropertyBag(this.News)
});
// soap message component
let soapCall = Cc["@mozilla.org/xmlextras/soap/call;1"]
.createInstance(Ci.nsISOAPCall);
let parameters = [];
parameters.push(new soapParameter("parameters",
parametersBag,
this._schema.getTypeByName('SearchRequest')));
soapCall.encode(Ci.nsISOAPMessage.VERSION_1_2,
'SearchRequest',
this._schema.targetNamespace,
0, null, // header blocks
parameters.length, parameters);
soapCall.transportURI = "http://api.bing.net:80/soap.asmx";
soapCall.encoding = this._encoding;
soapCall.asyncInvoke(aSOAPResponseListener);
}
const kSchema = {
file: 'data/bing20.xsd',
schemaNamespace: 'http://www.w3.org/2001/XMLSchema',
targetNamespace: 'http://schemas.microsoft.com/LiveSearch/2008/03/Search'
}
function BingBase()
{
if (typeof this._schema == "undefined")
{
this._schema = getSchema(kSchema);
// use the 2001 SOAP encoder
this._encoding = Cc["@mozilla.org/xmlextras/soap/encoding;1"]
.createInstance(Ci.nsISOAPEncoding);
this._encoding.getAssociatedEncoding("http://www.w3.org/2001/09/soap-encoding", true);
this._encoding.schemaCollection = this._schema.collection;
}
}
So far, the complexity of this does not seem unmanageable to me. I’ve only shown the endoding step. Decoding the response consists of a creating a call-specific translator similar to the “BingSearch.prototype.invoke” function above, which relies on the webservices soap library decode method. All of the other functions I’ve created (such as arrayPropertyBag) are not at all specific to the nature of the SOAP interface being used. I am not seeing the need to process the WSDL file automatically and generate proxy functions.
I’m not yet convinced that this resurrection of the old webservices library is the right approach, but I am not seeing any obstacles to using it either. I can generate and decode soap calls fairly efficiently, debug issues that arise, plus I have code that will integrate fairly easily with either javascript or C++ code in a Gecko chrome environment.
Next steps
I’m trying to decide on the next step toward moving this forward. I’m leaning toward attempting a specific EWS application, such as read-only access to an Exchange calendar as a Lightning extension. Another option might be to add some core mailnews hooks to allow me to create either message accounts or addressbooks using an extension – though I’m hoping jcranmer will beat me to it.



