Tuesday, July 12, 2011

MacOS and WCF struggle

I spent several unforgettable days connecting MacOS client to Windows WCF service. The choice was to use gSOAP, the best open source library I could find (version 2.8.3).

Server had basicHttpBinding over SSL with TransportWithMessageCredential security mode and clientCredentialType="UserName".
The process of generating proxy with gSOAP had to be quite simple. Firstly, create header file with service description with wsdl2h tool; then, generate proxies with soapcpp2 and include them into the build.

Here is the log:
Firstly, I installed ZLIB and OpenSSL libraries in project dir.
Then, gSOAP tool wsdl2h could not work over HTTPS, and required a special build. I was not able to compile it as described in documentation, as 'make secure' simply ignored WITH_OPENSSL flag. I had to modify one line in gsoap-2.8/gsoap/wsdl/Makefile.am:
wsdl2h_CPPFLAGS=$(AM_CPPFLAGS) $(SOAPCPP2_NONAMESPACES) -D$(platform) $(WSDL2H_EXTRA_FLAGS)
so that now wsdl2h is built with SSL support by main make.
Alternatively, it is possible to compile wsdl2h with:
make -f MakefileManual CMFLAGS=-DWITH_OPENSSL LIBS="-lcrypto -lssl"

Now, next important step is to prepare typemap.dat file, which contains namespace definitions, custom classes/structs and headers which will be included later into autogenerated files. As we use message level authentication, the following lines should be added at the end:
[
#import "wsse.h" 
]
where "wsse.h" (can be found in shared/gsoap/import) contains the definition of SOAP_ENV__Header structure as follows:
struct SOAP_ENV__Header {
    mustUnderstand _wsse__Security *wsse__Security;
}
Without these alterations it is not possible to use wsse plugin and set message authentication header in SOAP envelope.
Then, we generate wsdl description:
./bin/wsdl2h -t typemap.dat -x -o testwcf1.h https://zoo.office.local/Service1.svc?wsdl
and create proxies:
./bin/soapcpp2 -j -wx -1 -C -I ./:./share/gsoap:./share/gsoap/import:./share/gsoap/plugin testwcf1.h
I made a project in XCode, so added last step as a script build phase.
In addition, I had to add wsseapi.cpp, mecevp.c and smdevp.c files from ./share/gsoap/plugin to the link, and stdsoap2.cpp and dom.cpp from gSOAP source folder.
Define following pre-processor macros: WITH_OPENSSL, WITH_GZIP, WITH_DOM.

Sample client code:
soap_ssl_init();
soap soap(SOAP_IO_DEFAULT | SOAP_IO_KEEPALIVE);
    
soap_wsse_add_UsernameTokenText(&soap, "Id", "login", "password");

if(soap_ssl_client_context(&soap, SOAP_SSL_DEFAULT, NULL, NULL, "./ca.pem", NULL, NULL))
{
    soap_print_fault(&soap, stderr);
    return 1;
}
    
BasicHttpBinding_USCOREIService1Proxy svc(&soap);
svc.soap_endpoint = "https://zoo.office.local/Service1.svc";
    
_test__GetData gd;
_test__GetDataResponse resp;
    
int input_value = 42;
gd.value = &input_value;
int result = svc.GetData(&gd, &resp);
if(SOAP_OK == result) {
    std::cout << "result: " << *(resp.GetDataResult) << "\n";
}
else {
    svc.soap_stream_fault(std::cout);
    return 1;
}