ATL Server is a new set of classes in ATL for creating Web Applications and Web Services.
These Web Services are completely compatible with clients written in C++, C#, VB and other
tools that can call a Web Service through SOAP. The Web Service support in ATL Server only
supports invocation over an HTTP tranport for this version, however like the rest of ATL,
ATL Server is designed to be very flexible. You may wish to create Web Services which can
be called over other transport mechanisms, such as TCP/IP, and this article will help explain
how you would go about changing the default transport ATL Server supports for Web Services.
Two examples are included, one implementing a Web Service which can be called over TCP/IP and
another sample which uses a Floppy Disk for the transport
This article shows how to separate the SOAP support offered by ATL Server from the
ISAPI framework, allowing use of ATL Server to build SOAP servers and clients
over virtually any communication channel.
Summary:
Server Side
The ATL Server Web Service Wizard will generate the code for a Web Service exposed through
HTTP. The Web Service is implemented as a class with the following declaration:
[
request_handler(name="Default", sdl="GenSimpleSoapAppServiceSDL"),
soap_handler(
name="SimpleSoapAppService",
namespace="urn:SimpleSoapAppService",
protocol="soap"
)
]
class CSimpleSoapAppService :
public ISimpleSoapAppService
In this code sequence, the attributes before the class declaration do most of the exposing.
The soap_handler
attribute will inject the following methods : DispatchSoapCall
,
ParseSoapHeaders
and __AtlGetSDL
. These methods are required for required for
handling SOAP messages and mapping them to internal method calls and also for wrapping the result of
internal methods invocations to SOAP responses. (The MSDN library coming with Visual Studio.NET 7.0 Beta1
contains details on the code injected by this attribute).
The request_handler
attribute will make it able to handle HTTP requests. If the transport layer is not going to be HTTP, the
request_handler
attribute is no longer needed, so it can be commented out.
The CSoapHandler<>
class in atlsoap.h is designed to handle SOAP requests
through HTTP. The entry point for handling an HTTP SOAP request is:
HTTP_CODE HandleRequest(AtlServerRequest *, IServiceProvider *)
The request body and the SOAP related HTTP header ("SOAPAction") are retrieved from the ISAPI extension through
the pServerRequest
(an ECB wrapper)member of the AtlServerRequest*
parameter.
The pServerRequest
object provides methods from reading the request body, reading the SOAPAction
HTTP header and writing the SOAP response back to the client. As the purpose is to replace the HTTP transport,
we need to find another way to launch the SOAP request processing and to provide it with a way of performing
the same operations.
This is solved by creating a CSoapHandler
derivative, CSoapTransportHandler
,
that implements the following entry point (file SoapTransportSrv.h of the downloadable code):
HTTP_CODE InvokeSoapMethod(stSoapTransportDescription *pTransInfo)
And the stSoapTransportDescription
structure is defined as:
struct stSoapTransportDescription
{
IWriteStream *pWriteStream;
IStream *pReadStream;
CStringA strSOAPAction;
IServiceProvider *pServiceProvider;
};
Perhaps the only member that needs a few details is pServiceProvider. It allows the SOAP servers to share
services exactly as those in an ISAPI dll. It can be NULL in an implementation, as long as the SOAP servers
will not attempt to use it.
Now, the SOAP server class generated by the wizard has to be modified to use the new CSoapTransportHandler
functionality. The new code would look like:
[
soap_handler(
name="SimpleSoapAppService",
namespace="urn:SimpleSoapAppService",
protocol="soap"
)
]
class CSimpleSoapAppService :
public CSoapTransportHandler<CSimpleSoapAppService>,
public ISimpleSoapAppService
...
There is no other modification that has to be done in the wizard-generated server code.
Right now, the SOAP server can be instantiated and the SOAP methods can be invoked through
CSoapTransportHandler::InvokeSoapMethod
, therefore with custom streams to read from and write to,
and it does not depend on the ATL Server HTTP support anymore.
The instantiation of the server(s) is handled by another class, CSoapDispatcher
,
implemented in the soapDispatch.h file. The CSoapDispatcher
class fills a
stSoapTransportDescription
structure, then instantiates a SOAP server and invokes a
SOAP call like below
stSoapTransportDescription transInfo;
...
CComObjectNoLock<CSimpleSoapAppService> srv;
HRESULT hRet = S_OK;
try
{
hRet = srv.InvokeSoapMethod( &transInfo );
}
catch(...)
...
At this point, a server application only has to:
- get the SOAP request and the SOAP Action header through any transport channel
- wrap the request in an
IStream
interface
- provide an
IWriteStream
to store the response
- invoke
CSoapDispatcher::DispatchCall
- send the content of the write stream (the SOAP response) over any transport channel to the client
Client Side
The SOAP proxy class generated by sproxy.exe has the following prototype:
class CSimpleSoapAppService : public CSoapSocketClient
...
By inspecting the code generated by sproxy.exe, one can notice that only a few methods/members
of the CSoapSocketClient
are actually used by the proxy class. These would be
- the constructor
HRESULT SendRequest(LPCTSTR tszAction) throw()
void Cleanup() throw()
m_Stream
, used as an IStream interface to store the request before sending
m_ReadStream
, used as an IStream interface to store the response before parsing
The method that does most of the work here is SendRequest
. This method is supposed
to send the request from m_Stream
to the SOAP server and to fill m_ReadStream
with
the server response. By replacing (in the sproxy.exe generated file) CSoapSocketClient
with
a custom class that contains all these methods and attributes, the actual channel used to send the request
to the SOAP server only depends on the implementation of the SendRequest
method.
It is exactly what
happens in the attached sample code.
To reuse the sproxy.exe generated code for such different transport classes, the following
modification would be useful:
template<TTransportClass>
class CSimpleSoapAppService : public TTransportClass
...
Transport Channels
The sample implements the following transport channels:
- Communication through files saved on a storage media(in the FloppyTransport folder)
- TCP/IP communication (in the TCPIPTransport folder)
Each transport channel implementation contains a client application and a server application.
The server uses the SOAP server included in the Include folder (which is built as described above).
The code in the server application only takes care of the transport. The client application only
implements the transport specific class to act as a TTransportClass
parameter for the modified
class generated by sproxy.exe
Please note that the modifications specified above only allow sending/receiving SOAP messages over
the custom channel. They do not allow retrieving the description (SDL) for the service. Therefore, a limitation
of the TCPIPTransport and FloppyTransport samples is that the SDL used in generating the
proxy has to be generated outside of the sample. The one used in the applications specified above is
saved as simpleSoapSrv.wsdl in the Include folder. To get the server description, one would have
to create the SOAP server through the wizard, complete the interface, then save the SDL through the
default HTTP way provided by ATL Server.
An Extension : HTTPListenerTransport
This folder contains an enhanced version of the TCPIPTransport server. This server
listens on a specified TCPIP port, but it accepts HTTP requests. It provides a way of responding to
.disco requests, to generate the SDL for the implemented SOAP servers and also to accept SOAP invocations.
This server can provide a very light-weight, complete, SOAP implementation, without requiring an HTTP
server to be installed on the machine. To create a client for this light server, just use the Add Web Reference
wizard from the Visual Studio.NET 7.0 Beta1 IDE, and point the wizard to the following URL:
http:
If you build the HTTPListenerServer sample, the URL would become:
http:
The C# client provided for this server (HttpListenerCSharpClient
) is generated this way
Final Considerations
To build the sample code, just unzip the source archive, then load and build the solution file
(
ATLSSoapTransport.sln)
An updated version of the sample code will be included as an ATL Server sample starting with
Visual Studio.NET Beta2. It will add the following :