Windows NT Systems Programming: Spring 2001

[ Home | Syllabus | Course Notes | Assignments | Search]


Understanding the SOAP Universe
(see article by the father of SOAP - Don Box)

Advantages of  WSDL (Web Services Description Language)/WSML (Web Services Meta Language)

(an aside URL, URI, URN)


HTTP Examples

HTTP Request

POST /foobar HTTP/1.1 
Host: 209.110.197.12 
Content-Type: text/plain 
Content-Length: 12 

Hello, World 
HTTP Response
200 OK 
Content-Type: text/plain
Content-Length: 12

dlroW ,olleH

////Here is a "C" program which would produce the correct response
//// notice it uses standard input (for the request body) and output (for response)

#include <stdio.h>

int main(int argc, char **argv) {
    char buf[4096];
    int cb = read(0, buf, sizeof(buf));
    buf[cb] = 0;
    strrev(buf);
    printf("200 OK\r\n");
    printf("Content-Type: text/plain\r\n");
    printf("Content-Length: %d\r\n", cb);
    printf("\r\n");
    printf(buf);
    return 0;
}

SOAP in HTTP

POST /string_server/Object17 HTTP/1.1 
Host: 209.110.197.2
Content-Type: text/xml 
Content-Length: 152
SOAPMethodName: urn:strings-com:IString#reverse 

<Envelope>
 <Body>
  <m:reverse xmlns:m='urn:strings-com:IString'>
   <theString>Hello, World</theString>
  </m:reverse>
 </Body>
</Envelope>
200 OK
Content-Type: text/xml
Content-Length: 162

<Envelope>
 <Body>
  <m:reverseResponse xmlns:m='urn:strings-com:IString'> 
   <result>dlroW ,olleH</result>
  </m:reverseResponse>
 </Body>
</Envelope>

IDL to WML

[ uuid(DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA) ]
interface IBank : IUnknown {
  HRESULT withdraw([in] long account,
                  [out] float *newBalance,
              [in, out] float *amount
          [out, retval] VARIANT_BOOL *overdrawn);
}
//// encode the input as a structure
struct withdraw {
    long account;
    float amount;
};
//// and the output also
struct withdrawResponse {
    float newBalance;
    float amount;
    VARIANT_BOOL overdrawn;
};
//// here is a visual basic program to invoke the COM object
Dim bank as IBank
Dim amount as Single
Dim newBal as Single
Dim overdrawn as Boolean
amount = 100
Set bank = GetObject("soap:http://bofsoap.com/am")
overdrawn = bank.withdraw(3512, amount, newBal)
//// Withdrawal Proxy - sort of what MIDL does for you



HRESULT Proxy_withdraw(long account,
           float *newBalance, float *amount,
           VARIANT_BOOL *overdrawn)
{
// serialize [in] params into a struct
    withdraw request;
    request.account = account;
    request.amount = *amount;
// send the request message
    proxy->Send(&request, sizeof(request));
// receive the response message
    withdrawResponse response = { 0 };
    proxy->Receive(&response, sizeof(response));
// deserialize response struct into [out] params
    *newBalance = response.newBalance;
    *amount = response.amount;
    *overdrawn = response.overdrawn;
    return S_OK; // or fail
}
//// now in SOAP (element-normal form (ENF).
<soap:Envelope
   xmlns:soap='urn:schemas-xmlsoap-org:soap.v1'>
  <soap:Body>
    <IBank:withdraw xmlns:IBank=
      'urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA'>
      <account>3512</account>
      <amount>100</amount>
    </IBank:withdraw>
  </soap:Body>
</soap:Envelope>
<soap:Envelope
   xmlns:soap='urn:schemas-xmlsoap-org:soap.v1'>
  <soap:Body>
    <IBank:withdrawResponse xmlns:IBank=
      'urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA'>
      <newBalance>0</newBalance>
      <amount>5</amount>
      <overdrawn>true</overdrawn>
    </IBank:withdrawResponse>
  </soap:Body>
</soap:Envelope>
//// schemas tell the structure of XML documents
<schema
 targetNamespace='urn:schemas-xmlsoap-org:soap.v1'>
  <element name='Envelope'>
    <type>
      <element name='Header' type='Header'
               minOccurs='0' />
      <element name='Body' type='Body'
               minOccurs='1' />
    </type>
  </element>
</schema>
---------------
package com.bofsoap.IBank;
public class adjustment {
  public int   account;
  public float amount;
}
//// java to SOAP
<t:adjustment
  xmlns:t='urn:develop-com:java:com.bofsoap.IBank'>
  <account>3514</account>
  <amount>100.0</amount>
</t:adjustment>
----------
Arrays
struct POINTLIST {
  long cElems;
  [size_is(cElems)] POINT points[];
};
<t:POINTLIST xmlns:t='uri for POINTLIST'>
  <cElems>3</cElems>
  <points xsd:type='t:POINT[3]' >
    <POINT><x>3</x><y>4</y></POINT>
    <POINT><x>7</x><y>5</y></POINT>
    <POINT><x>1</x><y>9</y></POINT>
  </points>
<t:POINTLIST>
----------
Faults
<schema
  targetNamespace='urn:schemas-xmlsoap-org:soap.v1'
>
  <element name='Fault'>
    <type>
      <element name='faultcode' type='string' />
      <element name='faultstring' type='string' />
      <element name='runcode' type='string' />
      <element name='detail' />
    </type>
  </element>
</schema>
http://msdn.microsoft.com/library/periodic/period00/soapfigs0300.htm#fig12   
<soap:Envelope
  xmlns:soap='urn:schemas-xmlsoap-org:soap.v1'>
  <soap:Body>
    <soap:Fault>
      <faultcode>400</faultcode>
      <faultstring>
        Divide by zero occurred
      </faultstring>
      <runcode>Maybe</runcode>
      <detail>
        <t:DivideByZeroException xmlns:t="someURI">
           <expression>x = 2 / 0;<expression>
        </t:DivideByZeroException>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

CALC example

Rich set of examples - both on server and client side.
We will focus on C++ server side (because COM based)
There are two versions: one that is basically a classic COM object (in the "rpc" folders)
and one that uses the low level SOAP APIs (in the "srsz" folders).|
First we look at the high-level classic COM implementation of the server

IDL file

//// First the C++ COM (server) object using the high level protocol
//// This IDL is standard COM     

// CalcSvcRpcCpp.idl : IDL source for CalcSvcRpcCpp.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (CalcSvcRpcCpp.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";
   [
	object,
	uuid(320A1D8F-23D3-4BDC-B1AA-412CF8D30E70),
	dual,
	helpstring("ICalc Interface"),
	pointer_default(unique)
   ]
   interface ICalc : IDispatch
   {
	[id(1), helpstring("method Add")] HRESULT Add([in] double A, [in] double B, [out, retval] double* Result);
	[id(2), helpstring("method Subtract")] HRESULT Subtract([in] double A, [in] double B, [out, retval] double* Result);
	[id(3), helpstring("method Divide")] HRESULT Divide([in] double A, [in] double B, [out, retval] double* Result);
	[id(4), helpstring("method Multiply")] HRESULT Multiply([in] double A, [in] double B, [out, retval] double* Result);
   };

[
   uuid(D32945E1-9412-4652-B123-7B04AD454DDC),
   version(1.0),
   helpstring("CalcSvcRpcCpp 1.0 Type Library")
]
library CALCSVCRPCCPPLib
{
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");

   [
	uuid(699815E5-6F9E-403B-8C35-53476BABA89E),
	helpstring("Calc Class")
	
	coclass Calc
	{
		[default] interface ICalc;
	};
};

//// standard COM implementation - nothing SOAPy here

// Calc.cpp : Implementation of CCalc
#include "stdafx.h"
#include "CalcSvcRpcCpp.h"
#include "Calc.h"

/////////////////////////////////////////////////////////////////////////////
// CCalc


STDMETHODIMP CCalc::Add(double A, double B, double *Result)
{
   *Result = A + B;
   return S_OK;
}

STDMETHODIMP CCalc::Subtract(double A, double B, double *Result)
{
   *Result = A - B;
   return S_OK;
}

STDMETHODIMP CCalc::Multiply(double A, double B, double *Result)
{
   *Result = A * B;
   return S_OK;
}

STDMETHODIMP CCalc::Divide(double A, double B, double *Result)
{
   *Result = A / B;
   return S_OK;
}

Exposing interface in XDSL
(luckily it is created by the wizard from the DLL)

<?xml version='1.0' encoding='UTF-8' ?> 
<servicemapping name='Calc'>
  <service name='Calc'>
    <using PROGID='CalcSvcRpcCpp.Calc.1' cachable='1' ID='CalcObject' />
    <port name='CalcSoapPort'>
      <operation name='Multiply'>
        <execute uses='CalcObject' method='Multiply' dispID='4'>
          <parameter callIndex='1' name='A' elementName='A' />
          <parameter callIndex='2' name='B' elementName='B' />
          <parameter callIndex='-1' name='retval' elementName='Result' />
        </execute>
      </operation>
      <operation name='Divide'>
        <execute uses='CalcObject' method='Divide' dispID='3'>
          <parameter callIndex='1' name='A' elementName='A' />
          <parameter callIndex='2' name='B' elementName='B' />
          <parameter callIndex='-1' name='retval' elementName='Result' />
        </execute>
      </operation>
      <operation name='Subtract'>
        <execute uses='CalcObject' method='Subtract' dispID='2'>
          <parameter callIndex='1' name='A' elementName='A' />
          <parameter callIndex='2' name='B' elementName='B' />
          <parameter callIndex='-1' name='retval' elementName='Result' />
        </execute>
      </operation>	
      <operation name='Add'>
        <execute uses='CalcObject' method='Add' dispID='1'>
          <parameter callIndex='1' name='A' elementName='A' />
          <parameter callIndex='2' name='B' elementName='B' />
          <parameter callIndex='-1' name='retval' elementName='Result' />
        </execute>
      </operation>
    </port>
  </service>
</servicemapping>

Meta Data (WMSL)
(gives types)

<?xml version='1.0' encoding='UTF-8' ?> 
<definitions  name ='Calc'   targetNamespace = 'http://tempuri.org/wsdl/'
	 xmlns:wsdlns='http://tempuri.org/wsdl/' 
	 xmlns:typens='http://tempuri.org/type' 
	 xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' 
	 xmlns:xsd='http://www.w3.org/2000/10/XMLSchema' 
	 xmlns:stk='http://schemas.microsoft.com/soap-toolkit/wsdl-extension'
	 xmlns='http://schemas.xmlsoap.org/wsdl/'> 
  <types>
    <schema targetNamespace='http://tempuri.org/type'
      xmlns='http://www.w3.org/2000/10/XMLSchema'
      xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'
      xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'>
    </schema>
  </types>
  <message name='Calc.Multiply'>
    <part name='A' type='xsd:double'/>
    <part name='B' type='xsd:double'/>
  </message>
  <message name='Calc.MultiplyResponse'>
    <part name='Result' type='xsd:double'/>
  </message>
  <message name='Calc.Divide'>
    <part name='A' type='xsd:double'/>
    <part name='B' type='xsd:double'/>
  </message>
  <message name='Calc.DivideResponse'>
    <part name='Result' type='xsd:double'/>
  </message>
  <message name='Calc.Subtract'>
    <part name='A' type='xsd:double'/>
    <part name='B' type='xsd:double'/>
  </message>
  <message name='Calc.SubtractResponse'>
    <part name='Result' type='xsd:double'/>
  </message>
  <message name='Calc.Add'>
    <part name='A' type='xsd:double'/>
    <part name='B' type='xsd:double'/>
  </message>
  <message name='Calc.AddResponse'>
    <part name='Result' type='xsd:double'/>
  </message>
  <portType name='CalcSoapPort'>
    <operation name='Multiply' parameterOrder='A B'>
      <input message='wsdlns:Calc.Multiply' />
      <output message='wsdlns:Calc.MultiplyResponse' />
    </operation>
    <operation name='Divide' parameterOrder='A B'>
      <input message='wsdlns:Calc.Divide' />
      <output message='wsdlns:Calc.DivideResponse' />
    </operation>
    <operation name='Subtract' parameterOrder='A B'>
      <input message='wsdlns:Calc.Subtract' />
      <output message='wsdlns:Calc.SubtractResponse' />
    </operation>
    <operation name='Add' parameterOrder='A B'>
      <input message='wsdlns:Calc.Add' />
      <output message='wsdlns:Calc.AddResponse' />
    </operation>
  </portType>
  <binding name='CalcSoapBinding' type='wsdlns:CalcSoapPort' >
    <stk:binding preferredEncoding='UTF-8'/>
    <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http' />
    <operation name='Multiply' >
      <soap:operation soapAction='http://tempuri.org/action/Calc.Multiply' />
      <input>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </input>
      <output>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </output>
    </operation>
    <operation name='Divide' >
      <soap:operation soapAction='http://tempuri.org/action/Calc.Divide' />
      <input>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </input>
      <output>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </output>
    </operation>
    <operation name='Subtract' >
      <soap:operation soapAction='http://tempuri.org/action/Calc.Subtract' />
      <input>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </input>
      <output>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </output>
    </operation>
    <operation name='Add' >
      <soap:operation soapAction='http://tempuri.org/action/Calc.Add' />
      <input>
        <soap:body use='encoded' namespace='http://tempurSoapg/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </input>
      <output>
        <soap:body use='encoded' namespace='http://tempuri.org/message/'
		  encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' />
      </output>
    </operation>
  </binding>
  <service name='Calc' >
    <port name='CalcSoapPort' binding='wsdlns:CalcSoapBinding' >
      <soap:address location='http://MSSoapSampleServer/MSSoapSamples/Calc/Service/Rpc/IsapiCpp/Calc.wsdl' />
    </port>
  </service>
</definitions>

Low level C++ program
IDL file

// CalcSvcSrSzCpp.idl : IDL source for CalcSvcSrSzCpp.dll
   interface ICalc : IDispatch
   {
	[id(1), helpstring("method Process")]HRESULT Process(
		[in] VARIANT Request,
		[in] VARIANT Response,
		[out, retval] VARIANT_BOOL* pSuccess);
   };
//// this is an "interpreted" call built on the fly!

//// I removed the error checking to simplify the logic
// Calc.cpp : Implementation of CCalc
#include "stdafx.h"
#include "CalcSvcSrSzCpp.h"
#include "Calc.h"
#include "stdio.h"
/////////////////////////////////////////////////////////////////////////////
// CCalc

LPCTSTR CCalc::WRAPPER_ELEMENT_NAMESPACE = _T("http://tempuri.org/message/");


STDMETHODIMP CCalc::Process(
   VARIANT varRequest, 
   VARIANT varResponse,
   VARIANT_BOOL* pbSuccess)
{

   HRESULT hr;
   _variant_t varA;
   _variant_t varB;
   _variant_t varAnswer;
   ISoapSerializerPtr pSerializer;
   ISoapReaderPtr pReader;
   _bstr_t bstrMethodName;
   IXMLDOMElementPtr pParameter;

   *pbSuccess = VARIANT_FALSE;

   hr = pReader.CreateInstance(__uuidof(SoapReader)); 
   pReader->Load(varRequest, ""); 
   bstrMethodName = pReader->RPCStruct->baseName; 
   pParameter = pReader->RPCParameter["A"][""]; 
   varA = pParameter->text;
   varA.ChangeType(VT_R8);
   pParameter = pReader->RPCParameter["B"][""];
   varB = pParameter->text;
   varB.ChangeType(VT_R8);
   if (bstrMethodName == _bstr_t(_T("Add"))){
	varAnswer = varA.dblVal + varB.dblVal; 
   }
   else if (bstrMethodName == _bstr_t(_T("Subtract")))	{
	varAnswer = varA.dblVal - varB.dblVal;
   }
   varAnswer.ChangeType(VT_BSTR);
   hr = pSerializer.CreateInstance(__uuidof(SoapSerializer)); 
   pSerializer->Init(varResponse); 
   pSerializer->startEnvelope(_bstr_t(), _bstr_t(), _bstr_t()); 
   pSerializer->startBody(_bstr_t()); 
   pSerializer->startElement(bstrMethodName + _T("Response"), WRAPPER_ELEMENT_NAMESPACE, _bstr_t(), _T("m"));
   pSerializer->startElement(_T("Result"), _bstr_t(), _bstr_t(), _bstr_t());
   pSerializer->writeString(_bstr_t(varAnswer)); 
   pSerializer->endElement();
   pSerializer->endElement();
   pSerializer->endBody();
   pSerializer->endEnvelope();
   *pbSuccess = VARIANT_TRUE;
   return S_OK;
}

Copyright chris wild 1999-2001.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: April 16, 2001.