[ Home | Syllabus | Course Notes | Assignments | Search]
SOAP Calc Example (continued)
What we have seen so far:
SERVER:
Client:
Difference between WSDL for ASP server and WSDL for C++ server is the following line
<service name='Calc' > <port name='CalcSoapPort' binding='wsdlns:CalcSoapBinding' > <soap:address location='http://MSSoapSampleServer/MSSoapSamples/Calc/Service/SrSz/AspJs/Calc.asp' /> </port> </service>
Compare this to the JScript client above- although uses same access mechanism (high level RPC), more of the details are exposed in the C++ version.
Illustrates:
Use of Dispatch interface
Variants
//// connect to a server to get WSDL
HRESULT hr; CString NewWSDL; m_WSDLCtl.GetWindowText(NewWSDL); if(m_WSDLConnected != NewWSDL) { if (m_pSoapClient != NULL) m_pSoapClient.Release(); hr = m_pSoapClient.CreateInstance(__uuidof(SoapClient)); if(FAILED(hr)) { DisplayHResult(_T("Cannot create SoapClient."), hr); return false; } hr = m_pSoapClient->mssoapinit((LPCTSTR)NewWSDL, _T(""), _T(""), _T("")); if(FAILED(hr)) { DisplayFault(_T("Cannot initialize SoapClient. ")); return false; } m_WSDLConnected = NewWSDL; }
F1: Knows how to access this com object because stdafx.h contains the following import statement
#import "C:\Program Files\Common Files\MSSoap\Binaries\mssoap1.dll"
//// Using the DISPATCH method - first get the ID in the "function" table //// translates "pMethodName" string into "dispid" index
hr = m_pSoapClient->GetIDsOfNames(IID_NULL, &pMethodName, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
//// Now set up the parameters as variants in variant array
DISPPARAMS dispparams; VARIANTARG params[2]; VARIANT result; CString ParamText; VariantInit(¶ms[0]); params[0].vt = VT_R8; m_BCtl.GetWindowText(ParamText); params[0].dblVal = atof(ParamText); // Set A parameter. VariantInit(¶ms[1]); params[1].vt = VT_R8; m_ACtl.GetWindowText(ParamText); params[1].dblVal = atof(ParamText); // Initialize DISPPARAMS structure. dispparams.cArgs = 2; dispparams.cNamedArgs = 0; dispparams.rgdispidNamedArgs = NULL; dispparams.rgvarg = params; // Prepare result variant. VariantInit(&result);
//// now call the method
hr = m_pSoapClient->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &result, &ExceptInfo, NULL);
//// get the result
// Convert result to a string. VariantChangeType(&result, &result, 0, VT_BSTR); // Display result. m_ResultCtl.SetWindowText(CString(result.bstrVal)); //// cast to CString
// Clean up variants. VariantClear(&result); VariantClear(¶ms[0]); VariantClear(¶ms[1]);
using DISPATCH interface and SOAPCLIENT to access "WSDL" defined functions of server
Can invoke any SOAP server implementation
We will choose JSCRIPT first - easier to see the overall structure
var BASE_SOAP_ACTION_URI = "http://tempuri.org/action/Calc."; Answer = Execute(END_POINT_URL, "Divide", 126, 3) WScript.Echo("126/3=" + Answer); function Execute(EndPointURL, Method, A, B) { var Serializer; var Reader; var Connector; Connector = WScript.CreateObject("MSSOAP.HttpConnector"); Connector.Property("EndPointURL") = EndPointURL; Connector.Property("SoapAction") = BASE_SOAP_ACTION_URI & Method; Connector.BeginMessage(); Serializer = WScript.CreateObject("MSSOAP.SoapSerializer"); Serializer.Init(Connector.InputStream); // attach HTTP connection to Serializer Serializer.startEnvelope(); Serializer.startBody(); Serializer.startElement(Method, WRAPPER_ELEMENT_NAMESPACE, "", "m"); Serializer.startElement("A"); Serializer.writeString(A); Serializer.endElement(); Serializer.startElement("B"); Serializer.writeString(B); Serializer.endElement(); Serializer.endElement(); Serializer.endBody(); Serializer.endEnvelope(); Connector.EndMessage(); Reader = WScript.CreateObject("MSSOAP.SoapReader"); Reader.Load(Connector.OutputStream); //// connect to SOAP response if( Reader.Fault == null ) { return Reader.RPCResult.text; } else { return "FAULT: " + Reader.faultstring.text; } }
//// basically the same in C++
<%@ LANGUAGE=JScript %> <% Response.ContentType = "text/xml"; //// set responce type if( Application("CalcAspJsCppServer") == void 0 ) { Application.Lock(); if( Application("CalcAspJsCppServer") == void 0 ) { var SoapServer; var WSDLFilePath; var WSMLFilePath; WSDLFilePath = Server.MapPath("Calc.wsdl"); WSMLFilePath = Server.MapPath("Calc.wsml"); try { SoapServer = Server.CreateObject("MSSOAP.SoapServer"); } catch(err) { SendFault("Cannot create SoapServer object. " + err.description + " (" + err.number + ")"); } try { SoapServer.Init(WSDLFilePath, WSMLFilePath); //// use the WSDL info to parse } catch(err) { SendFault("SoapServer.Init failed. " & err.description); } Application("CalcAspJsCppServer") = SoapServer; } Application.UnLock(); } SoapServer = Application("CalcAspJsCppServer"); try { SoapServer.SoapInvoke(Request, Response, ""); //// if successful - done } catch(err) { SendFault("SoapServer.SoapInvoke failed. " & err.description); } function SendFault(LogMessage) { var Serializer; // "URI Query" logging must be enabled for AppendToLog to work Response.AppendToLog(" SOAP ERROR: " & LogMessage); try { Serializer = Server.CreateObject("MSSOAP.SoapSerializer"); } catch(err) { Response.AppendToLog("Could not create SoapSerializer object. " & err.description); Response.Status = "500 Internal Server Error"; Response.End(); } try { Serializer.Init(Response); } catch(err) { Response.AppendToLog("SoapSerializer.Init failed. " & err.description); Response.Status = "500 Internal Server Error"; Response.End(); } try { Serializer.startEnvelope("", "", ""); Serializer.startBody(""); Serializer.startFault("Server", "The request could not be processed due to a problem in the server. Please contact the system admistrator. " + LogMessage, ""); Serializer.endFault(); Serializer.endBody(); Serializer.endEnvelope(); } catch(err) { Response.AppendToLog("SoapSerializer failed. " & err.description); Response.Status = "500 Internal Server Error"; Response.End(); } Response.End(); } %>
F2: keeps session state for subsequent SOAP requests - more efficient
F3: Maps to real path in the server's file system
F4: invoke's the Service through the SOAP Server Invoke method - passing Request steam and getting back Repsonse stream
Actually it is the COM object that uses the low level API - the ASP is easy
////It directly accesses the COM object - and let's it parse the request and create the response
<%@ LANGUAGE=JScript %> <% var CalcSrv = Server.CreateObject("CalcSvcSrSzCpp.Calc") ////COM progId Response.ContentType = "text/xml" if(!CalcSrv.Process(Request, Response)) { Response.Status = "500 Internal Server Error" } %>
Needs to parse request
Needs to construct the SOAP response
Similar to non-COM ASP/JSCRIPT service we saw earlier
// 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/"); //// removed error checking to point our main structure STDMETHODIMP CCalc::Process( VARIANT varRequest, //// input request stream VARIANT varResponse, //// output response stream 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; } hr = pSerializer.CreateInstance(__uuidof(SoapSerializer)); varAnswer.ChangeType(VT_BSTR); pSerializer->Init(varResponse); //// send to the output stream 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; }
ASP page is client to real Service
Advantage: real client need not have SOAP DLL's - could be UNIX platform
Could authenticate before accessing real service
soapclient = Server.CreateObject("MSSOAP.SoapClient") soapclient.ClientProperty("ServerHTTPRequest") = true soapclient.mssoapinit(WSDL_URL) //// need to connect to "real" service
//// now get request from form (normal ASP form handling
a = Request.Form("A") b = Request.Form("B") op = Request.Form("SubmitMath") if (op == "Add") { res = soapclient.Add(a, b) } else if (op == "Multiply") { res = soapclient.Multiply(a, b) } else if (op == "Subtract") { res = soapclient.Subtract(a, b) } else if (op == "Divide") { res = soapclient.Divide(a, b) }
<B>Result:</B> <%=res%><P><P> ////
HTML embedded in ASP script "<% ... %>" is script escape
<a href="Form.htm">Calculate Again</a>