ATL Server Implementation


Now let s look at how ATL Server implements all the things that need to happen when processing a Web service request.

ATL Server Web services are implemented using a state machine “based parser. ATL Server uses a stack to maintain information about each state it has entered while parsing the SOAP request. The ATL Server Web service attributes generate metadata information about each Web service class and each method in that class. This information (which you ll examine in detail later in this chapter) includes the name of the method, the SOAP headers used in the method (if any), the parameters of the method, type information for each SOAP header and method parameter, and the invocation type for the method ( rpc / encoded , document / literal , and so on). This metadata is used by the ATL Server framework while parsing the request to determine what the next state that needs to be entered is and how to parse the next piece of information that comes in. For a Web service client, the metadata is generated by sproxy.exe while generating the proxy class.

ATL Server defines several state constants it uses while parsing the request:

  • SOAP_START : This is the initial state of the request, before any parsing has been done.

  • SOAP_ENVELOPE : This state indicates that the SOAP Envelope element has been encountered .

  • SOAP_HEADERS : This state indicates that a SOAP Headers element has been encountered.

  • SOAP_BODY : This state indicates that the SOAP Body element has been encountered.

  • SOAP_PARAMS : This state indicates that the parser is ready to begin parsing the parameters. This state is entered after encountering the MethodName element in the SOAP request.

  • SOAP_HEADERS_DONE : This state indicates that the closing tag of the SOAP Headers element has been encountered. The framework effectively re-enters the SOAP_ENVELOPE state at this point, though it will return an error if any further Headers elements are encountered.

Now let s look at the structure of the metadata ATL Server uses in parsing the request. There are two main objects that the framework uses for metadata information: _soapmapentry and _soapmap . In turn , these objects use the following enumerations: SOAPFLAGS and SOAPMAPTYPE .

SOAPFLAGS is used to specify type information about a particular entry in the _soapmapentry struct. For example, if an entry represents an in parameter, SOAPFLAG_IN would be set in its dwFlags value. Each value in the SOAPFLAGS enumeration serves a similar purpose, either for the metadata or for the parsing state.

  • SOAPFLAG_NONE : This value is used to indicate that a particular field has no other flags.

  • SOAPFLAG_IN : This value is used to indicate that the field represents an in parameter or in header.

  • SOAPFLAG_OUT : This value is used to indicate that the field represents an out parameter or out header.

  • SOAPFLAG_RETVAL : This value is used to indicate that the field represents the return value of the Web service method.

  • SOAPFLAG_DYNARR : This value is used to indicate that the field is a dynamic array.

  • SOAPFLAG_FIXEDARR : This value is used to indicate that the field is a fixed- size array.

  • SOAPFLAG_MUSTUNDERSTAND : This value is used to indicate that the field represents a mustUnderstand header.

  • SOAPFLAG_UNKSIZE : This value is used to indicate that no size was specified for an array element in a SOAP request. This will commonly be the case for arrays in document / literal style.

  • SOAPFLAG_READYSTATE : This value is used to indicate that the parser is in a ready state; that is, it s used to indicate that the parser is prepared to begin reading the value of a parameter or header.

  • SOAPFLAG_FIELD : This value is used to indicate that the entry represents a field in a struct or enumeration.

  • SOAPFLAG_NOMARSHAL : This value is used to indicate that the entry shouldn t be put on to the wire as part of the SOAP response or read from the write as part of the SOAP request. size_is elements are examples of entries that would have this flag set.

  • SOAPFLAG_NULLABLE : This value is used to indicate the entry represents a field that is nullable; that is, it can be omitted from the SOAP request and be set to NULL in the SOAP response. Strings are examples of nullable elements.

  • SOAPFLAG_DOCUMENT : This value is used to indicate that the entry is part of a document -style request. Currently, this implies document / literal .

  • SOAPLAG_RPC : This value is used to indicate that the entry is part of an rpc -style request. Currently, this implies rpc / encoded .

  • SOAPFLAG_LITERAL : This value is used to indicate that the entry is part of a literal -style request. Currently, this implies document / literal .

  • SOAPFLAG_ENCODED : This value is used to indicate that the entry is part of an encoded -style request. Currently, this implies rpc / encoded .

  • SOAPFLAG_PID : This value is used to indicate that the entry is part of a document / literal parameters- in-document (PID) “style request. This means that the parameters will appear under a MethodName element in the request.

  • SOAPFLAG_PAD : This value is used to indicate that the entry is part of a document / literal parameters-as-document (PAD) “style request. This means that the parameters will appear directly under the SOAP Body element without an enclosing MethodName element.

  • SOAPFLAG_CHAIN : This value is used to indicate the first entry in a PAD-style request.

  • SOAPFLAG_SIZEIS : This value is used to indicate a size_is entry for an array.

The SOAPMAPTYPE enumeration is used to indicate what type of object the _soapmap instance is representing.

  • SOAPMAP_ERR : An internal error. This should never be encountered.

  • SOAPMAP_ENUM : This value is used to indicate the instance represents an enumeration.

  • SOAPMAP_FUNC : This value is used to indicate the instance represents a Web service method.

  • SOAPMAP_STRUCT : This value is used to indicate the instance represents a struct.

  • SOAPMAP_HEADER : This value is used to indicate the instance represents a collection of SOAP headers.

  • SOAPMAP_PARAM : This value is used to indicate the instance represents a parameter.

Now let s look at the _soapmap and _soapmapentry objects. Listing 19-14 presents the definition of the _soapmap struct.

Listing 19.14: Definition of the _soapmap Struct
start example
 struct _soapmap  {      ULONG nHash;      const char * szName;      const wchar_t * wszName;      int cchName;      int cchWName;      SOAPMAPTYPE mapType;      const _soapmapentry * pEntries;      size_t nElementSize;      size_t nElements;      int nRetvalIndex;      DWORD dwCallFlags;      ULONG nNamespaceHash;      const char *szNamespace;      const wchar_t *wszNamespace;      int cchNamespace;  }; 
end example
 

Here s an overview of the fields in Listing 19-14:

  • nHash : This field is used to store a hash value that s used for more efficient string comparisons for the object s name.

  • szName : This field stores the ASCII version of the name of the object the map represents (function name, struct name, enumeration name, and so forth).

  • wszName : This field stores the wide-character version of the name of the object the map represents.

  • cchName : This field stores the length of szName string.

  • cchWName : This field stores the length of the wszName string (it can be different from szName on the client side).

  • mapType : This field indicates the type of the map (from SOAPMAPTYPE ).

  • pEntries : This field stores a pointer to an array of _soapmapentry objects that represent the fields, values, or parameters for _soapmap .

  • nElementSize : This field indicates the size of the memory block that needs to be allocated to represent the object (the size of a struct, for example) and its fields or parameters.

  • nElements : This field indicates the count of the entries in the pEntries array.

  • nRetvalIndex : This field is used to represent the index into pEntries of the return value for the SOAPMAP_FUNC map. A “1 indicates there s no return value.

  • dwCallFlags : This field is used to indicate what type of invocation method is being used, document / literal or rpc / encoded .

  • nNamespaceHash : This field is used to store a hash value that s used for efficient string comparisons for the object s namespace.

  • szNamespace : This field is used to store the ASCII version of the object s namespace.

  • wszNamespace : This field is used to store the wide-character version of the object s namespace.

  • cchNamespace : This field is used to store the length of the namespace strings.

Listing 19-15 shows the definition of the _soapmapentry struct.

Listing 19.15: Definition of the _soapmapentry Struct
start example
 struct _soapmapentry  {      ULONG nHash;      const char * szField;      const WCHAR * wszField;      int cchField;      int nVal;      DWORD dwFlags;      size_t nOffset;      const int * pDims;      const _soapmap * pChain;      int nSizeIs;      ULONG nNamespaceHash;      const char *szNamespace;      const wchar_t *wszNamespace;      int cchNamespace;  }; 
end example
 

Here s an overview of the fields in Listing 19-15:

  • nHash : This field is used to store a hash value that s used for efficient string comparisons for the entry s name.

  • szField : This field is used to store the ASCII version of the entry s name.

  • wszField : This field is used to store the wide-character version of the entry s name.

  • cchField : This field is used to store the length of the szField and wszField strings.

  • nVal : This field is used to store the entry s type from the SOAPTYPES enumeration.

  • dwFlags : This field is used to store flags for entry ( in , out , retval , and so on).

  • nOffset : This field is used to store the offset of the entry into the block of memory used to represent its parent type (struct, function, or header). You ll examine this field in more detail later in the chapter.

  • pDims : This field is used to specify the array dimensions for an entry that is a fixed-size array. You ll look at the structure of this information later in the chapter.

  • pChain : This field links to another _soapmap instance for entries that are structs or enumerations.

  • nSizeIs : This field specifies the index of the size_is element for an array in the list of entries.

  • nNamespaceHash : This field is used to store a hash value that s used for efficient string comparisons for the entry s namespace.

  • szNamespace : This field is used to store the ASCII version of the entry s namespace.

  • wszNamespace : This field is used to store the wide-character version of the entry s namespace.

  • cchNamespace : This field is used to store the length of the namespace strings.

ATL Server stores the information from these fields in a state stack that s built while parsing the request. The stack contains elements of type CSoapRootHandler::ParseState , as shown in Listing 19-16.

Listing 19.16: Definition of the ParseState Struct
start example
 struct ParseState  {      void *pvElement;      DWORD dwFlags;      size_t nAllocSize;      size_t nExpectedElements;      size_t nElement;      const _soapmap *pMap;      const _soapmapentry *pEntry;      // mark when we get an item      CBitVector vec;      size_t nDepth;  // omitted   }; 
end example
 

Here s an overview of the fields in Listing 19-16:

  • pvElement : This field contains the pointer to the beginning of the allocated memory block for the element currently being parsed.

  • dwFlags : This field contains the flags for the element, plus additional flags set during parsing (such as SOAPFLAG_READYSTATE ).

  • nAllocSize : This field indicates the size of the memory block allocated for the element. For the root element, it will be the size of the memory block for the struct fields or method parameters. For a dynamic-size array, it will the amount of memory currently allocated for the array elements.

  • nExpectedElements : This field indicates the number of child entries that are expected for the entry represented by the state. For a struct, this will be the number of fields; for a method, this will be the number of in parameters; and for an array, this will be the number of array elements.

  • nElement : This field indicates how many child entries have been encountered for the object. This is checked against nExpectedElements when the entry s closing tag is encountered.

  • pMap : The _soapmap for the element.

  • pEntry : The _soapmapentry representing the element.

  • vec : A bit vector that s modified when child elements are encountered. When an entry s closing tag is encountered and nElement < nExpectedElements for the state, the bit vector is examined to ensure that any omitted elements were nullable elements.

  • nDepth : The current depth of the parsing.

Let s look at how these pieces fit together by examining how ATL Server handles the Hello World example you saw earlier. Listing 19-17 shows the Web service definition and the Web service definition after compilation with the attribute provider.

Listing 19.17: The HelloWorld Web Service
start example
 // HelloWorld.h : Defines the ATL Server request handler class  //  #pragma once  namespace HelloWorldService  {  // all struct, enum, and typedefs for your web service  // should go inside the namespace  // IHelloWorldService - web service interface declaration  //  [      uuid("A035C807-3516-422C-A527-AB93D57F2798"),      object  ]  __interface IHelloWorldService  {      // HelloWorld is a sample ATL Server web service method. It shows how to      // declare a web service method and its in parameters and out parameters      [id(1)] HRESULT HelloWorld([in] BSTR InputParam,          [out, retval] BSTR *bstrOutput);      // TODO: Add additional web service methods here  };  // HelloWorldService - web service implementation  //  [      request_handler(name="Default", sdl="GenHelloWorldWSDL"),      soap_handler(name="HelloWorldService",          namespace="urn:HelloWorldService",          protocol="soap")  ]  class CHelloWorldService :      public IHelloWorldService  {  public:      BSTR InputHeader;      int OutputHeader;      // This is a sample web service method that shows how to use the      // soap_method attribute to expose a method as a web method      [ soap_method ]      [ soap_header(value="InputHeader", in=true, out=false) ]      [ soap_header(value="OutputHeader", in=false, out=true) ]      HRESULT HelloWorld(/*[in]*/ BSTR InputParam,          /*[out, retval]*/ BSTR *bstrOutput)      {          CComBSTR bstrOut(L"Hello ");          bstrOut += InputParam;          bstrOut += L"!";          *bstrOutput = bstrOut.Detach();          if (InputHeader && InputHeader[0])              OutputHeader = _wtoi(InputHeader);          else              OutputHeader = 0;          return S_OK;      }      // TODO: Add additional web service methods here  }; // class CHelloWorldService  } // namespace HelloWorldService  // omitted...  // HelloWorld.h : Defines the ATL Server request handler class  //  #pragma once  namespace HelloWorldService  {  // all struct, enum, and typedefs  // for your web service should go inside the namespace  // IHelloWorldService - web service interface declaration  //  [      uuid("A035C807-3516-422C-A527-AB93D57F2798"),      object  ]  __interface IHelloWorldService  {      // HelloWorld is a sample ATL Server web service method. It shows how to      // declare a web service method and its in parameters and out parameters      [id(1)] HRESULT HelloWorld([in] BSTR InputParam,          [out, retval] BSTR *bstrOutput);      // TODO: Add additional web service methods here  };  // HelloWorldService - web service implementation  //  [      request_handler(name="Default", sdl="GenHelloWorldWSDL"),      soap_handler(name="HelloWorldService",          namespace="urn:HelloWorldService",          protocol="soap")  ]  class CHelloWorldService :      public IHelloWorldService,      /*+++ Added Baseclass */ public CSoapHandler<CHelloWorldService>  {  public:      BSTR InputHeader;      int OutputHeader;      // This is a sample web service method that shows how to use the      // soap_method attribute to expose a method as a web method      [ soap_method ]      [ soap_header(value="InputHeader", in=true, out=false) ]      [ soap_header(value="OutputHeader", in=false, out=true) ]      HRESULT HelloWorld(/*[in]*/ BSTR InputParam,          /*[out, retval]*/ BSTR *bstrOutput)      {          CComBSTR bstrOut(L"Hello ");          bstrOut += InputParam;          bstrOut += L"!";          *bstrOutput = bstrOut.Detach();          if (InputHeader && InputHeader[0])              OutputHeader = _wtoi(InputHeader);          else              OutputHeader = 0;          return S_OK;      }      // TODO: Add additional web service methods here      //+++ Start Injected Code For Attribute 'soap_handler'      const _soapmap ** GetFunctionMap() throw();      const _soapmap ** GetHeaderMap() throw();      void * GetHeaderValue() throw();      const wchar_t * GetNamespaceUri() throw();      const char * GetNamespaceUriA() throw();      const char * GetServiceName() throw();      HRESULT CallFunction(void *pvParam,          const wchar_t *wszLocalName,          int cchLocalName,          size_t nItem);      //--- End Injected Code For Attribute 'soap_handler'  };  //+++ Start Injected Code For Attribute 'request_handler'  HANDLER_ENTRY_SDL("Default",                    CHelloWorldService, ::HelloWorldService::CHelloWorldService,                    GenHelloWorldWSDL)  //--- End Injected Code For Attribute 'request_handler'  //+++ Start Injected Code For Attribute 'soap_handler'  struct ___HelloWorldService_CHelloWorldService_HelloWorld_struct      {      BSTR InputParam;      BSTR bstrOutput;  };  extern __declspec(selectany) const _soapmapentry  ___HelloWorldService_CHelloWorldService_HelloWorld_entries[] =      {          {              0xF6041A8C,              "bstrOutput",              L"bstrOutput",              sizeof("bstrOutput")-1,              SOAPTYPE_STRING,              SOAPFLAG_RETVAL  SOAPFLAG_OUT  SOAPFLAG_NULLABLE,              offsetof(___HelloWorldService_CHelloWorldService_HelloWorld_struct,                  bstrOutput),              NULL,              NULL,              -1,          },          {              0xD41C0B61,              "InputParam",              L"InputParam",              sizeof("InputParam")-1,              SOAPTYPE_STRING,              SOAPFLAG_NONE  SOAPFLAG_IN  SOAPFLAG_NULLABLE,              offsetof(___HelloWorldService_CHelloWorldService_HelloWorld_struct,                  InputParam),              NULL,              NULL,              -1,          },          { 0x00000000 }      };  extern __declspec(selectany) const _soapmap  ___HelloWorldService_CHelloWorldService_HelloWorld_map =      {          0x46BA99FC,          "HelloWorld",          L"HelloWorld",          sizeof("HelloWorld")-1,          sizeof("HelloWorld")-1,          SOAPMAP_FUNC,          ___HelloWorldService_CHelloWorldService_HelloWorld_entries,          sizeof(___HelloWorldService_CHelloWorldService_HelloWorld_struct),          1,          0,          SOAPFLAG_NONE  SOAPFLAG_RPC  SOAPFLAG_ENCODED,          0xE6CAFA1C,          "urn:HelloWorldService",          L"urn:HelloWorldService",          sizeof("urn:HelloWorldService")-1      };  extern __declspec(selectany) const _soapmapentry  ___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_entries[] =      {          {              0x45334E39,              "InputHeader",              L"InputHeader",              sizeof("InputHeader")-1,              SOAPTYPE_STRING,              SOAPFLAG_NONE  SOAPFLAG_IN  SOAPFLAG_NULLABLE,              offsetof(CHelloWorldService, InputHeader),              NULL,              NULL,              -1,              0xE6CAFA1C,              "urn:HelloWorldService",              L"urn:HelloWorldService",              sizeof("urn:HelloWorldService")-1          },          {              0x647BAD1A,              "OutputHeader",              L"OutputHeader",              sizeof("OutputHeader")-1,              SOAPTYPE_INT,              SOAPFLAG_NONE  SOAPFLAG_OUT,              offsetof(CHelloWorldService, OutputHeader),              NULL,              NULL,              -1,              0xE6CAFA1C,              "urn:HelloWorldService",              L"urn:HelloWorldService",              sizeof("urn:HelloWorldService")-1          },          { 0x00000000 }      };  extern __declspec(selectany) const _soapmap  ___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_map =      {          0x46BA99FC,          "HelloWorld",          L"HelloWorld",          sizeof("HelloWorld")-1,          sizeof("HelloWorld")-1,          SOAPMAP_HEADER,          ___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_entries,          0,          0,          -1,          SOAPFLAG_NONE  SOAPFLAG_RPC  SOAPFLAG_ENCODED,          0xE6CAFA1C,          "urn:HelloWorldService",          L"urn:HelloWorldService",          sizeof("urn:HelloWorldService")-1      };  extern __declspec(selectany) const _soapmap *  ___HelloWorldService_CHelloWorldService_funcs[] =      {          &___HelloWorldService_CHelloWorldService_HelloWorld_map,          NULL      };  extern __declspec(selectany) const _soapmap *  ___HelloWorldService_CHelloWorldService_headers[] =      {          &___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_map,          NULL      };  ATL_NOINLINE inline const _soapmap ** CHelloWorldService::GetFunctionMap()  {      return ___HelloWorldService_CHelloWorldService_funcs;  };  ATL_NOINLINE inline const _soapmap ** CHelloWorldService::GetHeaderMap()  {      return ___HelloWorldService_CHelloWorldService_headers;  }  ATL_NOINLINE inline void * CHelloWorldService::GetHeaderValue()  {      return this;  }  ATL_NOINLINE inline HRESULT CHelloWorldService::CallFunction(void *pvParam,          const wchar_t *wszLocalName,          int cchLocalName,          size_t nItem)  {      wszLocalName;      cchLocalName;      HRESULT hr = S_OK;      switch(nItem)  {      case 0:          {              ___HelloWorldService_CHelloWorldService_HelloWorld_struct *p =                  (___HelloWorldService_CHelloWorldService_HelloWorld_struct *)                  pvParam;              hr = HelloWorld(p->InputParam, &p->bstrOutput);              break;          }      default:          hr = E_FAIL;      }      return hr;  }  ATL_NOINLINE inline const wchar_t * CHelloWorldService::GetNamespaceUri()  {      return L"urn:HelloWorldService";  }  ATL_NOINLINE inline const char * CHelloWorldService::GetNamespaceUriA()  {      return "urn:HelloWorldService";  }  ATL_NOINLINE inline const char * CHelloWorldService::GetServiceName()  {      return "HelloWorldService";  }    //--- End Injected Code For Attribute 'soap_handler'    // class CHelloWorldService  } // namespace HelloWorldService 
end example
 

As you can see, the attribute provider injects the metadata (_soapmap , _soapentry ) as described earlier in this chapter. ___HelloWorldService_CHelloWorldService_HelloWorld_entries contains information about the input and output parameters of the HelloWorld method of the Web service, and ___HelloWorldService_CHelloWorldService_HelloWorld_map contains information about the HelloWorld method itself, including a link to the _soapmapentry array for the parameters. ___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_entries contains information about the individual SOAP headers for the HelloWorld method, and ___HelloWorldService_CHelloWorldService_HelloWorld_atlsoapheader_map contains higher-level SOAP header information for the HelloWorld method.

In addition to the metadata, the attribute provider also injects several functions:

  • GetFunctionMap : This method returns a pointer to the _soapmap array for the methods of the Web service that are exposed over SOAP.

  • GetHeaderMap : This method returns a pointer to the _soapmap array for the header information for all the methods of the Web service that are exposed over SOAP.

  • GetHeaderValue : This method returns a void* pointer that points to the instance of the Web service (i.e., the this pointer). This is used in assigning and extracting header values for marshaling because headers are stored as member variables .

  • GetNamespaceUri : This method returns a wide-character string for the namespace URI of the Web service.

  • GetNamespaceUriA : This is an ASCII version of GetNamespaceUri .

  • GetServiceName : This method returns the name of the Web service specified in the name parameter of the soap_handler attribute.

  • CallFunction : This method invokes the Web service method for the current SOAP request.

Upon receiving an HTTP request, the ATL Server framework will attempt to retrieve a SAXXMLReader object from ATL Server s per-thread data. This object will be used to parse the SOAP request. ATL Server will then attempt to extract the SOAPAction header from the HTTP request. For ATL Server Web services, the SOAPAction headers are always of the following form:

 SOAPAction: "#MethodName" 

ATL Server uses the SOAPAction header to determine which function is being invoked. If the header is absent, ATL Server will attempt to continue processing. The framework will determine which function is being invoked from the SOAP request body later on. It will only fail if it encounters any SOAP headers as part of the request, because at that point, the SOAP body hasn t been encountered and it isn t possible to determine which method is being invoked.

Let s assume you received an appropriate SOAPAction :

 SOAPAction: "#HelloWorld" 

The framework will then push an initial parse state on the stack. Because the parsing will not have encountered any SOAP headers yet, it will set the pvElement value of the ParseState to the instance of the Web service class (as the offsets and elements in this case are member variables of the class). Figure 19-2 shows the start state of the state stack.

click to expand
Figure 19-2. The initial state of the state stack

Once all this has been set up, the framework is ready to begin parsing the request. For the purposes of this example, assume the request is well- formed and skip over the error-handling portions of the code.

The first element encountered is the Envelope element, and the framework sets the current state to SOAP_ENVELOPE . Next, the parser encounters the Headers element. The state is set to SOAP_HEADERS . The InputHeader element is next and the framework invokes the CSoapRootHandler::ProcessParams function. The ProcessParams function attempts to look up InputHeader in the _soapmapentry array. If it s found, a value is set in the current state s bit vector indicating that this particular element was received (based on its ordinal position in the _soapmapentry array). Once it s found, the ProcessParams method checks the type and sees that it s a string and invokes the ProcessString function. ProcessString sets the active SAX parser to be the CSAXStringBuilder class. Because the string might contain embedded XML, it s necessary to parse it as XML elements and build the final string before copying it into the appropriate memory location for the SOAP header. The ProcessString function also pushes a new state on the stack that includes the SOAPFLAG_READYSTATE flag to indicate it s ready to set the string value. The new state s pvElement value is set to the value of the InputHeader member variable. This is done by using the nOffset value in the metadata and adding that offset to the pvElement value in the current state. Figure 19-3 shows the state stack after encountering the InputHeader element.

click to expand
Figure 19-3. The state stack after the InputHeader element

Once the CSAXStringBuilder class encounters the final element of the string, it invokes the characters function of CSoapRootHandler . The characters function sees the type of the element is the SOAPTYPE_STRING enumeration value and that the state includes the SOAPFLAG_READYSTATE flag. It then invokes the AtlSoapGetElementValue function, which switches on the type of the element, and then invokes the AtlGetSAXValue template function, which does the actual data conversion from the XML data to the C++ type. In this case, for SOAPTYPE_STRING , it s simply a matter of putting the string into a BSTR. The state s pvElement value is passed in as the memory location of the BSTR.

The endElement method is invoked for the closing InputHeader tag, which in turn invokes the CheckEndElement method of CSoapRootHandler . The CheckEndElement function ensures that the data received in the XML packet is valid. This function does most of its work when validating the end tags of structs, ends of header blocks, and ends of method blocks. You ll examine this function in more detail later. After CheckEndElement is called, the current state is popped from the stack.

The endElement method is then invoked for the closing Header tag. Because the current state is SOAP_HEADERS , the CheckEndHeaders method is invoked. This method sets the state to SOAP_HEADERS_DONE .

The next element encountered is the Body element, and the framework sets the state to SOAP_BODY . When the HelloWorld element is encountered next, the framework invokes the DispatchSoapCall method, which looks up the HelloWorld method in the Web service class s metadata and allocates a struct based on the nElementSize field that will hold the method s parameters. It then pushes this state on the stack and sets the current state to SOAP_PARAMS . Figure 19-4 shows the state of the state stack after encountering the HelloWorld element.

click to expand
Figure 19-4. The state stack after encountering the HelloWorld element

The next element encountered is the InputParam element. The framework invokes the ProcessParams function, which then continues in the same way as when it was invoked for the InputHeader element. The processing for the closing tags in the Body section is the same as for that in the Header section, with the exception that when the closing HelloWorld tag is encountered, the CheckEndElement function does additional processing. It ensures that the number of parameters received is the same as the number of in parameters specified by the metadata. If the number of elements is fewer than expected, the CheckEndElement function will walk the _soapmapentry array and compare the in parameters against what has been set in the bit vector. For any missing element, it ensures that that element is considered a nullable element. If there are any missing non-nullable elements, an error is returned.

Once the XML processing is completed and all the XML data has been converted to C++ types, the Web service method is invoked using the CallFunction method. This method will invoke the actual Web service method using the pvElement value at the top of the state stack to create the parameters. Listing 19-18 shows how the framework invokes the user s method after the request has been parsed.

Listing 19.18: Invocation of User Code
start example
 ATL_NOINLINE inline HRESULT CHelloWorldService::CallFunction(void *pvParam,          const wchar_t *wszLocalName,          int cchLocalName,          size_t nItem)      {      wszLocalName;      cchLocalName;      HRESULT hr = S_OK;      switch(nItem)      {      case 0:          {              ___HelloWorldService_CHelloWorldService_HelloWorld_struct *p =                  (___HelloWorldService_CHelloWorldService_HelloWorld_struct *)                  pvParam;              hr = HelloWorld(p->InputParam, &p->bstrOutput);              break;          }      default:          hr = E_FAIL;      }      return hr;  } 
end example
 

Once the method returns, the SOAP response has to be generated. This is done using the GenerateResponse method. The GenerateResponse method first walks the out headers for the Web service method by invoking the GenerateResponseHelper method using the value retrieved from the injected GetHeaderValue method and the header metadata for the method. Different response packets are generated depending on whether the Web service is implemented specifying rpc / encoded or document / literal . The framework uses different types of response generator classes for the different encodings. All the classes inherit from the private CResponseGenerator base class, which defines this basic interface:

  • StartEnvelope : Begin the SOAP Envelope element.

  • StartHeaders : Begin the SOAP Headers element.

  • EndHeaders : End the SOAP Headers element.

  • StartBody : Begin the SOAP Body element.

  • EndBody : End the SOAP Body element.

  • EndEnvelope : End the SOAP Envelope element.

  • StartMap : Begin a subelement for a _soapmapentry that has a _soapmap (such as a struct or a function).

  • EndMap : End a subelement for a _soapmap .

  • StartEntry : Start an element for a _soapmapentry (such as a struct field or a method parameter).

  • EndEntry : End an element for a _soapmapentry .

Different methods in this interface are overridden for rpc / encoded or document / literal generation.

The GenerateResponseHelper method walks each of the entries in the root _soapmap and uses the pvElement field from the root of the state stack in combination with the _soapmapentry s nOffset field to generate the XML packet from the C++ data. In the case of headers, the pvElement is the value retrieved from the injected GetHeaderValue method, and for method generation, the pvElement is the value allocated in the DispatchStencilCall method for storing the parameters extracted from the XML data. For each _soapmapentry that isn t a UDT (such as an enumeration or a struct), the AtlSoapGenElementValue function is called to generate the output. This function is similar to the AtlSoapGetElementValue function in that it switches based on the SOAPTYPE of the element and then dispatches to the AtlGenXMLValue template function, which generates the XML data from the C++ data type passed in.

Finally, after the response has been generated, it s necessary to clean up any memory that has been allocated during the processing of the request. This is the same basic operation as generating the response. The framework walks each of entries based on the metadata, but instead of outputting the XML representation of the C++ type using AtlSoapGenElementValue , the AtlSoapCleanupElement function is called. The AtlSoapCleanupElement function switches on the type and invokes the AtlCleanupValue template function for any data type that has to be deallocated or freed in any way.

Error Checking

The ATL Server framework must perform error checking during the processing of the response to ensure that the SOAP is well-formed and is what s expected and supported for the Web service. In cases where the SOAP packet is malformed or makes use of a part of the SOAP standard that isn t supported by ATL Server, the framework must return an appropriate SOAP fault to the client.

One of the errors the framework checks for is that all SOAP headers that are sent with the mustUnderstand attribute set are expected by the server. If, during the processing of the request, the framework encounters a SOAP header that it doesn t recognize, it will then check to see if the header has the mustUnderstand attribute set. If it doesn t, the framework will simply skip the header using the CSkipHandler class; if it does, the framework will return a SOAP fault indicating that a mustUnderstand header was encountered that wasn t expected by the Web service. This checking happens in the ProcessParams function, which invokes the CheckMustUnderstandHeader function, which looks for the mustUnderstand attribute.

The framework needs to check that the same element isn t specified more than once within the SOAP packet. It performs this check using the bit vector described earlier in this chapter. Once an element is encountered, the position in the bit vector corresponding to it (based on its ordinal position in the pEntries array of the _soapmap struct) is then set to true . The bit vector is always checked before processing an element; if the element has already been processed , an error is returned. The bit vector is also checked during the endElement SAX content handler method to ensure that all array elements that were expected were received and that any omitted elements (for a struct or a function) are nullable.

The framework must also ensure that the SOAP packet doesn t contain data for elements that are specified as null or href in the SOAP message. The framework accomplishes this using a member variable ( m_bNullCheck ) that is set to true in href and null situations. This variable is then checked in the startElement SAX content handler method implementation. If it s true , then the framework returns an error, because it s expecting endElement to be the next SAX method to be invoked ( endElement will clear m_bNullCheck ).

Similarly, the framework needs to check that elements that should be followed by character data (such as integer values) don t have children. This is done using a member variable ( m_bCharacters ) that is set to true in these situations. This variable is then checked in the endElement and startElement SAX content handler method implementations . If it s true, then the framework returns an error, because it s expecting characters to be the next SAX method to be invoked.

Another malformed packet check occurs for UDTs. If a struct is encountered and it isn t an href , then the top-level struct element must be followed by child elements. This verification is accomplished using a member variable ( m_bChildCheck ) that s set to true when a non- href UDT is encountered. The member variable is checked in endElement . If it s true , the framework returns an error, because it s expecting startElement to be the next SAX method to be invoked.

Structs and Enums

The framework supports enumerations in much the same way it deals with other simple types, such as integers. The primary difference is that it must assign the value based on the name of value, rather than doing some other string conversion operation. Listing 19-19 presents an example Web service that uses an enumeration.

Listing 19.19: Web Service Using an Enumeration
start example
 // EnumTest.h : Defines the ATL Server request handler class  //  #pragma once  namespace EnumTestService  {  // all struct, enum, and typedefs for  // your web service should go inside the namespace  [ export ]  enum TestEnum { EnumVal1, EnumVal2, EnumVal3 };  // IEnumTestService - web service interface declaration  //  [      uuid("3484E1E4-D46C-44DD-871D-52799101ADCE"),      object  ]  __interface IEnumTestService  {      // HelloWorld is a sample ATL Server web service method. It shows how to      // declare a web service method and its in parameters and out parameters      [id(1)] HRESULT EnumTest([in] TestEnum InputEnum,          [out, retval] TestEnum *OutputEnum);      // TODO: Add additional web service methods here  };  // EnumTestService - web service implementation  //  [      request_handler(name="Default", sdl="GenEnumTestWSDL"),      soap_handler(name="EnumTestService",          namespace="urn:EnumTestService",          protocol="soap")  ]  class CEnumTestService :      public IEnumTestService  {  public:      // This is a sample web service method that shows how to use the      // soap_method attribute to expose a method as a web method      [ soap_method ]      HRESULT EnumTest(/*[in]*/ TestEnum InputEnum,          /*[out, retval]*/ TestEnum *OutputEnum)      {          *OutputEnum = InputEnum;          return S_OK;      }      // TODO: Add additional web service methods here  }; // class CEnumTestService  } // namespace EnumTestService 
end example
 

Listing 19-20 shows the attribute-injected metadata for the enumeration.

Listing 19.20: Injected Code for a Web Service Using an Enumeration
start example
 __if_not_exists(___EnumTestService_TestEnum_entries)  {  extern __declspec(selectany) const  _soapmapentry ___EnumTestService_TestEnum_entries[] =      {          { 0xDA154F6B, "EnumVal3", L"EnumVal3",            sizeof("EnumVal3")-1, 2, SOAPFLAG_FIELD,            0, NULL, NULL, -1 },          { 0xDA154F6A, "EnumVal2", L"EnumVal2",            sizeof("EnumVal2")-1, 1, SOAPFLAG_FIELD,            0, NULL, NULL, -1 },          { 0xDA154F69, "EnumVal1", L"EnumVal1",            sizeof("EnumVal1")-1, 0, SOAPFLAG_FIELD,            0, NULL, NULL, -1 },          { 0x00000000 }      };  extern __declspec(selectany) const _soapmap ___EnumTestService_TestEnum_map =      {          0xF8EFE655,          "TestEnum",          L"TestEnum",          sizeof("TestEnum")-1,          sizeof("TestEnum")-1,          SOAPMAP_ENUM,          ___EnumTestService_TestEnum_entries,          sizeof(::EnumTestService::TestEnum),          1,          -1,          SOAPFLAG_NONE,          0xF29B2B15,          "urn:EnumTestService",          L"urn:EnumTestService",          sizeof("urn:EnumTestService")-1      };  } 
end example
 

The ___EnumTestService_TestEnum_entries _soapmapentry array maps the enumeration value names to their actual integer value. When an enumeration is encountered, instead of calling AtlSoapGetElementValue , the characters SAX content handler function walks this array to match the enumeration value name to its integer value. It returns a failure if it doesn t find a matching value name. When generating the SOAP response, it matches the integer value to the name to get a string representation of the enumeration value.

The framework supports structs in the same way it supports Web service methods. Because Web service methods are modeled as structs, you can think of structs as a kind of nested function within the SOAP packet. Listing 19-21 presents an example Web service that uses a struct.

Listing 19.21: Web Service Using a Struct
start example
 // StructTest.h : Defines the ATL Server request handler class  //  #pragma once  namespace StructTestService  {  // all struct, enum, and typedefs for your web  // service should go inside the namespace  [ export ]  struct TestStruct  {      int IntegerValue;      BSTR StringValue;  };  // IStructTestService - web service interface declaration  //  [      uuid("FFCF67A4-D9C9-4F54-BE04-E30A6B62E9C2"),      object  ]  __interface IStructTestService  {      // HelloWorld is a sample ATL Server web service method.      // It shows how to declare a web service method and its      // in parameters and out parameters      [id(1)] HRESULT StructTest([in] TestStruct InputStruct,          [out, retval] TestStruct *OutputStruct);      // TODO: Add additional web service methods here  };  // StructTestService - web service implementation  //  [      request_handler(name="Default", sdl="GenStructTestWSDL"),      soap_handler(name="StructTestService",          namespace="urn:StructTestService",          protocol="soap")  ]  class CStructTestService :      public IStructTestService  {  public:      // This is a sample web service method that shows how to use the      // soap_method attribute to expose a method as a web method      [ soap_method ]      HRESULT StructTest(/*[in]*/ TestStruct InputStruct,          /*[out, retval]*/ TestStruct *OutputStruct)      {          OutputStruct->IntegerValue = InputStruct.IntegerValue;          if (InputStruct.StringValue)          {              OutputStruct->StringValue = SysAllocString(InputStruct.StringValue);              if (!OutputStruct->StringValue)                  return E_OUTOFMEMORY;          }          else          {              OutputStruct->StringValue = NULL;          }          return S_OK;      }      // TODO: Add additional web service methods here  }; // class CStructTestService  } // namespace StructTestService 
end example
 

Listing 19-22 shows the attribute-injected metadata for the TestStruct struct.

Listing 19.22: Injected Code for a Web Service Using a Struct
start example
 extern __declspec(selectany) const _soapmapentry      ___StructTestService_TestStruct_entries[] =  {      {          0x8A8BD84B,          "IntegerValue",          L"IntegerValue",          sizeof("IntegerValue")-1,          SOAPTYPE_INT,          SOAPFLAG_FIELD,          offsetof(::StructTestService::TestStruct, IntegerValue),          NULL,          NULL,          0    },      {          0xE257E474,          "StringValue",          L"StringValue",          sizeof("StringValue")-1,          SOAPTYPE_STRING,          SOAPFLAG_FIELD  SOAPFLAG_NULLABLE,          offsetof(::StructTestService::TestStruct, StringValue),          NULL,          NULL,          0    },      { 0x00000000 }  };  extern __declspec(selectany) const _soapmap      ___StructTestService_TestStruct_map =  {      0x15962585,      "TestStruct",      L"TestStruct",      sizeof("TestStruct")-1,      sizeof("TestStruct")-1,      SOAPMAP_STRUCT,      ___StructTestService_TestStruct_entries,      sizeof(::StructTestService::TestStruct),      2,      -1,      SOAPFLAG_NONE,      0x57848C05,      "urn:StructTestService",      L"urn:StructTestService",      sizeof("urn:StructTestService")-1  }; 
end example
 

As you can see, the metadata is largely the same as the kind of metadata injected for Web service methods, with ___StructTestService_TestStruct_entries containing information about the fields of the struct and ___StructTestService_TestStruct_map containing information about the struct itself.

An example SOAP request/response for this Web service might look like Listings 19-23 and 19-24.

Listing 19.23: SOAP Request
start example
 <Envelope>      <Body>          <StructTest>              <InputStruct>  <IntegerValue>0</IntegerValue>  <StringValue>String</String>              </InputStruct>          </StructTest>      </Body>  </Envelope> 
end example
 
Listing 19.24: SOAP Response
start example
 <Envelope>      <Body>          <StructTest>              <OutputStruct>  <IntegerValue>0</IntegerValue>  <StringValue>String</String>              </OutputStruct>          </StructTest>      </Body>  </Envelope> 
end example
 

On processing the request, when the framework encounters the InputStruct element, it will push a new state on the stack with the pMap field set to ___StructTestService_TestStruct_map and the pvElement field set to the value of InputStruct in the parameter struct for the Web service method. It then continues processing as normal using the new top state of the stack to process IntegerValue and StringValue . When it encounters the InputStruct close element, it verifies the fields of InputStruct and then pops the struct state off of the stack to continue processing the SOAP request.

When generating the response, the framework again treats the struct like a nested Web service method. When it encounters a struct while walking the metadata for the Web service method, it recursively invokes GenerateResponseHelper with the _soapmap for the struct.

Structs of structs are handled in a similar nesting fashion.




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