Custom Parsing on the Server Side


You ll begin this section by examining the problem that you re trying to solve. Take the situation where in a client/server architecture, the client is sending various XML fragments to the server. If the communication protocol of choice is SOAP, the request s payload might look like this:

 <SOAP:Envelope   .>    <SOAP:Body   .>      <TreatXMLNode> <!--name of the SOAP method -->        <xmlNodeStartTag   .>  ..       custom XML here   </xmlNodeStartTag>      </TreatXMLNode>    </SOAP:Body>  </SOAP:Envelope> 

There s no way to strictly define the type of the XML fragment. It could be described by the any element of the XML Schema Datatypes document (see http://www.w3.org/TR/2001/REC-xmlschema-1-20010502/#Wildcards for details), but any has no direct correspondent in C++, so ATL Server doesn t support it by default.

A simple workaround for this problem is to have a SOAP method that accepts a string as a parameter. A client could send a regular SOAP request with the XML fragment to be transmitted encoded as a string. The call would then look like this:

 <TreatXMLNode>  &lt;customXMLRootNode&gt;   content here   &lt;/customXMLRootNode&gt;  </TreatXMLNode> 

Notice the escape sequences, because the XML is being passed as a string. During this process, the characters that have a special meaning in an XML document will be neutralized. For example, the brackets used in delimiting the tags ( < and > ) will be replaced by special entities ( & lt; and & gt; , respectively). The string then has to be unescaped on the server side, a new instance of an XML parser has to be launched, and the goal will be achieved.

Although this solution is technically correct, it does have a few disadvantages:

  • It encodes an existing XML node to a string, which requires extra memory and processing time on the client side.

  • The string has to be decoded on the server side, which requires extra memory and processing time there, too.

  • A new instance of the parser (or a different parser) has to be used for parsing the escaped string on the server side.

The third disadvantage requires some discussion: The SOAP payload processing on the server side is, ultimately, an XML parsing. ATL Server uses the SAX parser provided by MSXML, and you can use this parser to get deep into the XML fragment that s passed by the client. But when an unexpected XML tag is encountered , the ATL Server SOAP framework, which ensures the validity of the SOAP payload, will generate an error.

As we mentioned before, ATL Server uses the DispatchSoapCall method to map the SOAP request to an actual function call, before the framework continues with the processing of the parameters. This mapping must happen ”it ensures that the correct server-side function will be invoked. But, immediately after this mapping, you can hijack the parsing of the method parameters. This way, the free-form XML fragment could be processed during the same pass over the payload and, by the time the actual method gets invoked, the results of the parsing are already available.

All the code that is presented here is available as a sample called CustomServerSideParsing , which you can find with the other code for this book in the Downloads section of the Apress Web site (http://www.apress.com).

You ll begin by generating a new ATL Server SOAP server from the Visual Studio s New Project Wizard. In this example you ll call the project CustomParsingServer . The signature of the SOAP method looks, by default, like this:

 [id(1)] HRESULT HelloWorld([in]BSTR bstrInput, [out, retval]BSTR *bstrOutput) 

As you ll take control of the parameter parsing, the default parsing won t take place. This is why, in the new SOAP server, the functions can t take [in] parameters. Only [out] ( optionally [retval] ) will be permitted.

Note  

The ATL Server framework takes care of the memory allocated to hold the parameters. For [in] parameters, memory is allocated during the parsing and released after the method invocation. As the parsing is overridden, the memory won t be allocated anymore, so the attempt to release it at the end will generate an error. This is why only [out] parameters can appear in the signature of a SOAP method with overridden parsing.

First you ll change the function s signature. Assume that you want the following information as a result of processing the XML fragment:

  • A free text description of the XML content:

     "Starting new element - <element_name>    Starting attribute list -   Attribute --<name > -- <value>   " 
  • The number of nodes in the XML fragment

  • The number of attributes in the XML fragment

The new signature will be

 HRESULT TreatXMLNode([out, retval] BSTR *bstrOutput,      [out]unsigned int* pnNodesCount,      [out]unsigned int* pnAttrCount); 

Next, you have to define an object that will take care of the parsing of the XML fragment parameter. The SAX parsing model requires that an ISAXContentHandler interface be provided. This interface contains handlers for all the events that may occur during the XML parsing. ATL Server contains the ISAXContentHandlerImpl class, which is intended to be a base for any SAX content handler. This class simplifies the parsing by providing a do-nothing implementation for all the events. A real content handler inheriting from ISAXContentHandlerImpl will have to implement only those handlers that are of interest.

The object responsible for parsing the parameter XML fragment is defined in the CustomSAXParser.h file that comes with this example. Its definition is as follows :

 class CSAXCustomParser : public ISAXContentHandlerImpl  {  public:    CString    m_strDescription;    unsigned int  m_nNodesCount;    unsigned int  m_nAttrCount;   } 

The public members are used to store the information obtained after the parsing of the XML fragment. m_strDescription will hold the free text description of the XML fragment, m_nNodesCount will store the number of nodes encountered in the fragment, and m_nAttrCount will store the number of attributes in the same fragment. CSAXCustomParser will override the processing of the following XML parsing events:

 HRESULT __stdcall startElement(...)  HRESULT __stdcall endElement(...)  HRESULT __stdcall characters(...) 

You still need to override startElement and endElement to keep track of the element names in the free text description, and then characters to access the element s content for the same free text description. startElement conveniently provides a pointer to an ISAXAttributes implementation. ISAXAttributes is a SAX interface that allows you to get information about the attributes of an XML element, so with these handlers you have access to all the information required to return the free text description, the attributes, and the element count.

The code inside the implementation of these handlers is straightforward. It just increments the counters or appends a description line to the free text description. The CSAXCustomParser also contains the following public methods :

 void SetReader(ISAXXMLReader *pReader)  void SetParent(ISAXContentHandler *pParent) 

SetParent specifies the previous ISAXContentHandler (i.e., the one that lost the control over the parsing when your class took it over). You ll need to let this parent control the payload parsing when you re done with the custom XML fragment.

Now we ll detail how the content handling switches work in SAX. During the SAX parsing there exists a single object that controls everything. Per parsing session, there s a single ISAXXMLReader object that will perform the parsing and invoke an ISAXContentHandler for all the parsing events. The content handler to be invoked is specified by calling ISAXXMLReader- > putContentHandler with an ISAXContentHandler* parameter. This is the way you ll pass the XML event-handling job to your CSAXCustomParser object, and this is also the way that you ll reactivate the ATL Server framework handling when the parameter XML fragment is done.

SetReader specifies the single ISAXXMLReader object during this parsing session, so when the fragment is done, the CSAXCustomParser has to call

 m_pReader->putContentHandler(m_pParent); 

where m_pReader is reached through SetReader and m_pParent through SetParent .

There s still one problem yet to be solved : You need to decide when the parameter XML fragment is done. SAX will take care of identifying malformed XML before calling the event handlers, so you can assume that, during the usage of the CSAXCustomParser , you re dealing with valid XML. That means that each element start has a corresponding element end. As CSAXCustomParser becomes active immediately after the XML element containing the SOAP method name has been processed, you can imagine an element counter that goes up whenever a new XML element starts and goes down whenever an XML element ends. This way, when the counter reaches zero, you re done with the XML node parameter.

Dealing with this internal node counter is the job of the following protected functions of CSAXCustomParser :

 DWORD DisableReset(DWORD dwReset = 1)  DWORD EnableReset() 

These functions increment/decrement an internal counter called m_dReset . DisableReset should be called whenever a new element starts (i.e., called from startElement ). For the first time (when m_dwReset is 0), DisableReset will be called twice (once for the container start-of-SOAP-method element and once for the encountered element). This ensures that, even when the XML fragment contains multiple nodes at the top level, the CSAXCustomParser object will release handling only when the matching end-of-method tag is encountered.

Consequently, EnableReset has to be called whenever the endElement event occurs. The code in endElement is as follows:

 if (EnableReset() == 0)  {    // end of the XML Node, the element counter is back to zero    hr = m_pParent->endElement(wszNamespaceUri, cchNamespaceUri, wszLocalName,      chLocalName, wszQName, cchQName);    m_pReader->putContentHandler(m_pParent);  }  else  {    // Let CSAXCustomParser process the end of element event  } 

endElement must ensure that the end of the XML element associated with the SOAP method will be processed by the ATL Server framework.

Now, let s get back to the SOAP server, the CCustomParsingServerService class. First, you ll add a new member to this class:

 public:  CSAXCustomParser  m_privateParser; 
Note  

You aren t required to make the custom parser a member of the SOAP server class. You could design your application so that the custom parser would live for only as long as the parsing took. If you did this, you would need to provide a mechanism for preserving the results of the parsing until the actual function that the SOAP method maps to gets called. In this case, for the sake of simplicity, you ll keep the parser as a member of the class, as it contains the free text description of the parameter XML and the number of attributes and elements as public variables .

Then override the virtual DispatchSoapCall as follows:

 HRESULT DispatchSoapCall(const wchar_t *wszNamespaceUri,                      int cchNamespaceUri,                      const wchar_t *wszLocalName, int cchLocalName)  {    // Call the base class's DispatchSoapCall    // This allows the appropriate method to be invoked    // after the request parsing is done    HRESULT hRet    = __super::DispatchSoapCall(wszNamespaceUri, cchNamespaceUri,                                  wszLocalName, cchLocalName);    if(SUCCEEDED(hRet))    {    // If the method was recognized, launch the custom parser for the content    m_privateParser.Clear();    ATLASSERT(GetReader() != NULL);    m_privateParser.SetReader(GetReader());    m_privateParser.SetParent(this);    GetReader()->putContentHandler(&m_privateParser);  }    return hRet;  } 

Here you re invoking the base class s implementation for DispatchSoapCall , to be sure the method mapping is performed correctly. Then, if everything is fine, you initialize the custom parser and, for the ISAXXMLReader object of the current parsing session (which you can obtain with GetReader() ), you change the content handler to your own object.

Tip  

You can use the __super keyword to explicitly state that you re calling the base class implementation for a function that you re overriding. The compiler will consider all the accessible base class methods and then call the best match. As you can see in this example, it s a very useful keyword to know!

Now everything is in place, so you continue with the implementation of the SOAP function:

 [ soap_method ]  HRESULT TreatXMLNode(/*[out, retval]*/ BSTR *bstrOutput,  /*[out]*/unsigned int* pnNodesCount,  /*[out]*/unsigned int* pnAttrCount)  {    CComBSTR  bstrTemp;    bstrTemp.Append((LPCTSTR)m_privateParser.m_strDescription);    *bstrOutput = bstrTemp.Detach();    *pnNodesCount = m_privateParser.m_nNodesCount;    *pnAttrCount = m_privateParser.m_nAttrCount;    return S_OK;  } 

The results of the XML parameter parsing have been saved in the public members of the m_privateParser object and you can use them in generating the [out] values.

You re almost finished, so it s important to take care and clean up after your custom parser. A good opportunity for doing this work is the virtual Cleanup function of the CSoapHandler class, which is invoked when the SOAP processing, the actual method invocation, and the response generation are complete:

 public:  virtual void Cleanup()  {    __super::Cleanup();    m_privateParser.Clear();  } 

Everything works! The example included contains a small test client (CustomServerSideParsing\TestClient). It isn t actually a SOAP client; rather, it s an application that writes directly to a socket. It sends a SOAP request similar to one that would be sent by a real client, with a custom XML fragment inside. Listing 23-1 shows what the request looks like.

Listing 23.1: SOAP Payload of a Request Containing an XML Document (Highlighted in Bold)
start example
 <soap:Envelope    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"  >    <soap:Body  soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">      <snp:TreatXMLNode xmlns:snp="urn:CustomParsingServerService">     <m:customXMLNode xmlns:m="http://MyServer/NamespaceM">      <m:binaryinfo bintype="exe">       <m:filename>C:\WINNT\system32\notepad.exe</m:filename>       <m:filesize>0xc600</m:filesize>       <m:versioninfo checkSum="0x0" creationdate="2000-10-23T09:33:11.090"      description="Notepad" manufacturer="Microsoft Corporation" version="4.0"/>      </m:binaryinfo>     </m:customXMLNode>      </snp:TreatXMLNode>    </soap:Body>  </soap:Envelope> 
end example
 

Listing 23-2 shows what the response looks like.

Listing 23.2: SOAP Payload of a Response Containing Results of Parsing the XML Document of Listing 23-1
start example
 <soap:Envelope    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"  >    <soap:Body      soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"    >      <snp:TreatXMLNode        xmlns:snp="urn:CustomParsingServerService"      >        <bstrOutput>   the description here   </bstrOutput>        <pnNodesCount>5</pnNodesCount>        <pnAttrCount>7</pnAttrCount>      </snp:TreatXMLNode>    </soap:Body>  </soap:Envelope> 
end example
 

Notice the values for pnNodesCount and pnAttrCount . The free text description is removed from the payload (because of the XML encoding of the new line and tab characters). Listing 23-3 shows this description as displayed onscreen.

Listing 23.3: Free Text Description of the XML Document Sent As a Request Parameter
start example
 Starting new element : (Namespace - http://MyServer/NamespaceM) customXMLNode  Starting attribute list  Attribute : (Namespace - , Name -) http://MyServer/NamespaceM  End attribute list  Content :  Starting new element : (Namespace - http://MyServer/NamespaceM) binaryinfo  Starting attribute list  Attribute : (Namespace - , Name - bintype) exe  End attribute list  Content :  Starting new element : (Namespace - http://MyServer/NamespaceM) filename  Starting attribute list  End attribute list  Content : C:\WINNT\system32\notepad.exe  Ending element : (Namespace - http://MyServer/NamespaceM) filename  Content :  Starting new element : (Namespace - http://MyServer/NamespaceM) filesize  Starting attribute list  End attribute list  Content : 0xc600  Ending element : (Namespace - http://MyServer/NamespaceM) filesize  Content :  Starting new element : (Namespace - http://MyServer/NamespaceM) versioninfo  Starting attribute list  Attribute : (Namespace - , Name - checkSum) 0x0  Attribute : (Namespace - , Name - creationdate) 2000-10-23T09:33:11.090  Attribute : (Namespace - , Name - description) Notepad  Attribute : (Namespace - , Name - manufacturer) Microsoft Corporation  Attribute : (Namespace - , Name - version) 4.0  End attribute list  Ending element : (Namespace - http://MyServer/NamespaceM) versioninfo  Content :  Ending element : (Namespace - http://MyServer/NamespaceM) binaryinfo  Ending element : (Namespace - http://MyServer/NamespaceM) customXMLNode  Content : 
end example
 

Notice that it closely matches the XML fragment sent as a parameter.




ATL Server. High Performance C++ on. NET
Observing the User Experience: A Practitioners Guide to User Research
ISBN: B006Z372QQ
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net