Introduction
Other ATL7 changes are covered
here and
here. First let's go over some of the new classes added for simplifying the ATL Server development. These classes can be used anywhere you need them not just for ATL Server development.
Cryptography (CCryptProv, CCryptKey, CCryptKeyedHash, etc):
Hashing:
CCryptProv prov;
prov.Initialize();
char rand[256];
memset(rand, 0, sizeof(rand));
HRESULT hr = prov.GenRandom(sizeof(rand), (BYTE*)rand);
if(hr != S_OK)
return 0;
const char* szPassword = "#$%^!SDD%$^&%^&~";
char Hash1st[256]; DWORD dwSize=256;
char Hash2nd[256];
memset(Hash1st, 0, sizeof(Hash1st));
memset(Hash2nd, 0, sizeof(Hash2nd));
CCryptKeyedHash hash;
hash.Initialize(prov, CALG_SHA1, CCryptKey::EmptyKey, 0);
hr = hash.AddString(szPassword);
if(hr != S_OK)
return 0;
hr = hash.GetValue((BYTE*)Hash1st, &dwSize);
if(hr != S_OK)
return 0;
hash.Destroy();
hash.Initialize(prov, CALG_SHA1, CCryptKey::EmptyKey, 0);
hr = hash.AddString(szPassword);
if(hr != S_OK)
return 0;
hr = hash.GetValue((BYTE*)Hash2nd, &dwSize);
if(hr != S_OK)
return 0;
if(memcmp(Hash1st, Hash2nd, sizeof(Hash1st)) == 0)
ATLTRACE("\npassword matched.\n\n");
Encryption:
const int ENCRYPT_BLOCK_SIZE = 64;
DWORD dwBlockLen = 2000 - 1000 % ENCRYPT_BLOCK_SIZE;
ATLASSERT((dwBlockLen%8)==0);
CHeapPtr<BYTE> buff;
buff.Allocate(dwBlockLen);
CCryptProv prov;
HRESULT hr = prov.Initialize(PROV_RSA_FULL, NULL, MS_STRONG_PROV);
{
CCryptDerivedKey derKey;
CCryptMD5Hash md5;
hr = md5.Initialize(prov);
md5.AddString(szPassword);
hr = derKey.Initialize(prov,md5,CALG_3DES);
BYTE iv[8];
hr = prov.GenRandom(8, iv);
hr = derKey.SetIV(iv);
hr = derKey.SetMode(CRYPT_MODE_CBC);
hr = derKey.SetPadding(PKCS5_PADDING);
FILE* pFilePl = fopen("testpln.txt", "rb");
if(pFilePl == NULL)
return 0;
FILE* pFileEn = fopen("testenc.txt", "wb");
DWORD dwEncLen = 0;
DWORD dwOrigBlockLen = dwBlockLen;
while(!feof(pFilePl))
{
DWORD nRead = fread(buff, 1, dwBlockLen, pFilePl);
DWORD nEnc = nRead;
hr = derKey.Encrypt(feof(pFilePl), (BYTE*)buff, &nEnc, dwBlockLen);
if(hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA))
{
buff.Reallocate(nEnc);
dwBlockLen = nEnc;
hr = derKey.Encrypt(feof(pFilePl), (BYTE*)buff, &nRead,
dwBlockLen);
nEnc = nRead;
}
if(hr != S_OK)
break;
fwrite(buff, 1, nEnc, pFileEn);
}
fclose(pFileEn);
fclose(pFilePl);
dwBlockLen = dwOrigBlockLen;
buff.Reallocate(dwBlockLen);
}
{
CCryptDerivedKey derKey;
CCryptMD5Hash md5;
hr = md5.Initialize(prov);
md5.AddString(szPassword);
hr = derKey.Initialize(prov,md5,CALG_3DES);
FILE* pFileEn = fopen("testenc.txt", "rb");
FILE* pFileDec = fopen("testdec.txt", "wb");
DWORD dwEncLen = 0;
while(!feof(pFileEn))
{
DWORD nRead = fread(buff, 1, dwBlockLen, pFileEn);
hr = derKey.Decrypt(feof(pFileEn), buff, &nRead);
if(hr != S_OK)
break;
fwrite(buff, 1, nRead, pFileDec);
}
fclose(pFileDec);
fclose(pFileEn);
}
Regular Expressions:
Regular Expression Reference(MSDN)
CAtlRegExp<> regexp;
CAtlREMatchContext<> mc;
if(regexp.Parse("^\\d+-\\d+$") == REPARSE_ERROR_OK)
{
const char* szNumDashNum="5663-4662";
if(regexp.Match(szNumDashNum, &mc))
{
ATLTRACE("Matched");
}
}
Support for sending SMTP email:
MIME & SMTP Reference(MSDN)
CoInitialize(0);
{
CSMTPConnection conn;
conn.Connect("SMTP Server Address");
CMimeMessage msg;
msg.SetSender(_T("sender@address"));
msg.AddRecipient(_T("recepient@address"));
msg.SetPriority(ATL_MIME_HIGH_PRIORITY);
msg.AddText(_T("message text"));
msg.SetSubject(_T("Subject line"));
conn.SendMessage(msg);
}
CoUninitialize();
Encoding:
Encoding Reference(MSDN)
BEncode, Base64Encode, UUEncode, QEncode, QPEncode, AtlHexEncode.
CString sSource = "some string";
int nDestLen = Base64EncodeGetRequiredLength(sSource.GetLength());
CString str64;
Base64Encode((const BYTE*)(LPCSTR)sSource, sSource.GetLength(),
str64.GetBuffer(nDestLen), &nDestLen);
str64.ReleaseBuffer(nDestLen);
cout<<(LPCSTR)str64;
int nDecLen = Base64DecodeGetRequiredLength(nDestLen);
CString strOrig;
Base64Decode(str64, str64.GetLength(), (BYTE*)strOrig.GetBuffer(nDecLen),
&nDecLen);
strOrig.ReleaseBuffer(nDecLen);
cout<<(LPCSTR)strOrig;
HTML Generation:
HTML Generation Reference(MSDN)
CHtmlGen html;
CWriteStreamOnCString stream;
html.Initialize(&stream);
html.html();
html.head();
html.title(_T("Sample Page"));
html.headEnd();
html.body(_T("Gray"));
html.WriteRaw(_T("Using ATL7!"));
html.bodyEnd();
html.htmlEnd();
cout<<(LPCTSTR)stream.m_str;
HTTP Client:
HTTP Client Reference(MSDN)
class CHttpClientWithCookie : public CAtlHttpClient
{
template<>
void OnSetCookie(LPCTSTR cookie)
{
std::cout<<"Got Cookie from server: "<<std::endl<<cookie<<std::endl;
return;
}
};
{
CHttpClientWithCookie client;
client.Navigate("http://www.microsoft.com");
const char* pszBody = (const char*)client.GetBody();
CString sRawHdr; DWORD dwLen;
client.GetRawResponseHeader(0, &dwLen);
client.GetRawResponseHeader((BYTE*)sRawHdr.GetBufferSetLength(dwLen), &dwLen);
std::cout<<"Raw Response Header: "<<std::endl<<(LPCTSTR)sRawHdr;
}
There are many other miscellaneous helper classes and more specific ones that deal with Web Application/Web Service development.
Debugging:
Debugging(MSDN)
ISAPI Filter/Extension Overview:
ISAPI stands for Internet Server Application Programming Interface. ISAPI programming is divided into Filters and Extensions. Both are DLLs with specific exported functions. ISAPI filters are loaded with the IIS and stay in memory until HTTP Web service shuts down. ISAPI Extensions are loaded on demand and provide extended functionality to web applications. ISAPI filters are useful for examining/filtering incoming and outgoing HTTP requests under IIS (for example, implementing custom authentication, encryption, compression, logging, etc). ISAPI extensions are useful for developing dynamic web pages and work per URL reference.
There is no class for ISAPI filter under ATL7. ATL Server provides support for ISAPI extensions in form of DLL cache, file cache, page cache, memory cache, data source cache, thread pool, remote management with web based or web service based interfaces, predefined performance counters, session support, etc. As you can see that's quite a lot of functionality/services. In addition it's easy to add your own services to the existing ones.
Web Application:
ATL7 Web Application is a combination of ISAPI extension and a handler DLL (or both can be combined into one dll) to create dynamic web pages. ISAPI extension is a DLL that exports three functions: HttpExtensionProc, GetExtensionVersion, TerminateExtension
. The Handler DLL/Web Application DLL also exports three predefined functions for ATL: InitializeAtlHandlers, GetAtlHandlerByName, UninitializeAtlHandlers
.
ATL7 web applications are built upon stencils. Stencil is a text file normally with .srf extension, resides in the virtual directory of ISAPI extension, and can have a mixed content of static html and dynamic/run-time replaceable tags. In GetExtensionVersion
, ATL initializes the heap it uses, predefined performance monitors, worker thread, thread pool, dllcache, page cache, and stencil cache. TerminateExtension
does the opposite.
The main function that does all the work is HttpExtensionProc
. The class responsible for implementing it is CIsapiExtension
. The CIsapiExtension::HttpExtensionProc
queues the incoming request unto an I/O completion port. This completion port is checked by the worker threads that run in the thread pool (CThreadPool
). All these worker threads do is block on the GetQueuedCompletionStatus
on the afore mentioned completion port (on which requests were queued from HttpExtensionProc
). By default there are 2 threads started in the pool (controlled by the ATLS_DEFAULT_THREADSPERPROC
or IThreadPoolConfig
which CThreadPool
implements).
So when any of these worker threads get the chance and successfully dequeue the completion request from the port, they execute the request. Executing request means processing the stencil file, first the whole processed page is looked up in page cache (controlled by overriding CachePage()
method and returning TRUE from the CRequestHandlerT
derived class). If found the whole page file simply gets transmitted to user and all is done. Otherwise, the stencil file is looked up in stencil cache. If it's not in the cache, the (.srf) file is read from disk, the Handler DLL tag is parsed, and DLL loaded (if it's not in DLL cache already).
The handler's DLL entry point is called, that is InitializeAtlHandlers
, and the handler class that handles the request is found through GetAtlHandlerByName
. Then all remaining replacement tags are parsed out of stencil file. The stencil file is Rendered, meaning, for all the tags that represent methods the address of the method in handler class is found through the GetAttrReplacementMethodMap
defined by tag_name
if using attributes or REPLACEMENT_METHOD_MAP
in the CRequestHandlerT
derived class. Then methods are called and generate the dynamic content for the web page. The generated content is sent back to user. It's nice to go through in debugger to see the sequence of events and suggest you do the same. It's interesting to see how it works internally.
To step through the ATL code place breakpoints at HttpExtensionProc
and step through up to PostQueuedCompletionStatus
. The other place to put breakpoint is in the while
loop for GetQueuedCompletionStatus
. Now you can hit F5 and follow the whole sequence of steps.
The wizard generated code (when you choose Data Source Cache, you're free to do it yourself also, but this can serve as an example) also produces a custom CIsapiWorker
derived class for per-thread services. This is the class that is created for each thread in the thread pool. What this allows is to add your own methods and data members per thread. This means it doesn't need to be synchronized, and your request handler class can get the pointer to this per-thread worker by calling IIsapiExtension::GetThreadWorker (link has an example on using it also).
ATL Server also provides a number of ways to control and configure your web application. It provides default implementation for remotely controlling the Thread Pool, SRF file cache, and Web App DLL Cache. All you do is define the appropriate symbols for whatever service you want to expose through HTML web interface or Web services, restrict to specific users, etc and you get the full implementation for free! You can follow the steps described in Extension Management Services on how to do it step by step!
Extension Management XML Web Service Client
The services that you choose when you create new project: Blob cache, File Cache, Data Source Cache, Browser capabilities, Session services and in addition the framework provided services: DllCache, StencilCache, ThreadPoolConfig, and AtlMemMgr are all exposed through IServiceProvider::QueryService
, which CIsapiExtension
derived class implements. You can also add any of your own services at runtime through IIsapiExtension::AddService
and later QueryService
for them. The code for first group of services will be added for you to the request handler class and will be commented out. All you need to do is uncomment it and apply it to your needs.
In addition there are Predefined ISAPI Performance Counters available. Adding your own performance counters is easy. There is a simple example on this and adding custom service under the Gathering Statistics section of the ATL Server tutorial.
Web Service:
ATL Web service support is also implemented as ISAPI extension. You can either use the wizard or try it manually for learning purposes. Create simple Win32 DLL project. Add .def file to export the 3 ISAPI extension functions. Then in its most simplest form the web service implemented with ATL7 can look as follows:
#pragma once
namespace ATL7WebService
{
[export]
struct S
{
int n;
double d;
};
[uuid("86C903DD-4D9F-4928-A3B5-AE242EA86D7A")]
__interface IWebSvc
{
HRESULT GetString([out,retval] BSTR* pVal);
HRESULT GetData([out, retval] ATLSOAP_BLOB* pBlob);
HRESULT GetStruct([out, retval] S* pVal);
};
[request_handler(name="Default", sdl="WSDL")]
[soap_handler(name="WebSvc", namespace="urn:ATL7WebService")]
class CWebSvc : public IWebSvc
{
public:
[soap_method]
HRESULT GetString( BSTR* pVal)
{
CComBSTR strOut("Some String");
*pVal = strOut.Detach();
return S_OK;
}
[soap_method]
HRESULT GetData( ATLSOAP_BLOB* pBlob)
{
IAtlMemMgr* pMem = GetMemMgr();
pBlob->size = 1000;
pBlob->data = (unsigned char*)pMem->Allocate(pBlob->size);
memset(pBlob->data, 'a', pBlob->size);
return S_OK;
}
[soap_method]
HRESULT GetStruct( S* pVal)
{
pVal->n = 10;
pVal->d = 1.1;
return S_OK;
}
};
}
#define _WIN32_WINNT 0x0403
#define _ATL_ATTRIBUTES
#define _ATL_NO_COM_SUPPORT
#include <atlisapi.h>
#include <atlsoap.h>
#include "websvc.h"
[ module(name="WebSvc", type="dll") ];
[ emitidl(restricted) ];
CIsapiExtension<> theExtension;
extern "C" DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB)
{ return theExtension.HttpExtensionProc(lpECB); }
extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO* pVer)
{ return theExtension.GetExtensionVersion(pVer); }
extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags)
{ return theExtension.TerminateExtension(dwFlags); }
Either use the VC++.NET project properties for Web Deployment and add .dll extension under Application Mappings or create the virtual directory and register the App mappings manually under IIS. After that you can access the WSDL of the web service in web browser as follows http://localhost/websvc/WebSvc.dll?Handler=WSDL
WebSvc.dll has the implementation of your Web Service, and WSDL is the compiler generated handler that you specified in request_handler
for sdl parameter.
Now when you access it again as in Web Application in previous section, HttpExtensionProc
will be called by IIS. It will delegate to CIsapiExtension
. CIsapiExtension
will queue the request on I/O completion port. One of the worker threads will dequeue the request. Then the Handler DLL will be loaded (websvc.dll). Handler will be initialized. What it does is, it gets all the functions exposed by your web service class to write the info about them to WSDL. ATL already has the template for generating the WSDL for your web service. It can be found in atlspriv.h (const char * const s_szAtlsWSDLSrf)
. You'll see that it's a simple stencil text suitable for Stencil processing (with replaceable tags). So this string is loaded using CStencil::LoadFromString
and all the tags are parsed and replaced/rendered.
Now this WSDL output can be used to generate the client to your web service. You can either use VS.NET built-in support for adding Web Services, by running wsdl.exe
manually for .NET clients, or running ATL7 provided tool called sproxy.exe
to produce proxy client file. For example: sproxy /out:client.h http:
If using sproxy.exe
, the generated file will have the methods exposed by your Web Service nicely wrapped in a C++ class. To use it, simply include the generated file (for example client.h
) file in your project:
#include <iostream>
#include "client.h"
int main()
{
CoInitialize(NULL);
{
WebSvc::CWebSvc svc;
CComBSTR str;
if(svc.GetString(&str.m_str) == S_OK)
{
std::wcout<<str.m_str<<std::endl;
ATLSOAP_BLOB blob;
if(svc.GetData(&blob) == S_OK)
{
std::cout<<"blob of size: "<<blob.size<<std::endl;
std::cout<<blob.data[0]<<blob.data1><<std::endl;
IAtlMemMgr* pMem = svc.GetMemMgr();
pMem->Free(blob.data);
WebSvc::S s;
svc.GetStruct(&s);
}
}
}
CoUninitialize();
return 0;
}
If you want to use Windows Integrated Authentication or Basic, you can use CNTLMAuthObject/CBasicAuthObject
(CSoapSocketClientT
specific):
CoInitialize(NULL);
{
WebSvc::CWebSvc svc;
CAtlHttpClient& httpClient = svc.m_socket;
CNTLMAuthObject authUser;
httpClient.AddAuthObj(_T("NTLM"), &authUser);
httpClient.NegotiateAuth(true);
CComBSTR str;
if(svc.GetString(&str.m_str) == S_OK)
{
}
}
CoUninitialize();
If you want to use HTTPS connection you can look at the Secure SOAP sample:
SecureSOAP Sample: Implements a Secure SOAP Communication (HTTPS) (MSDN)
In addition you have a choice of using predefined classes for communication such as CSoapSocketClientT
(by default), CSoapWininetClient, CSoapMSXMLInetClient
:
WebSvc::CWebSvcT<CSoapWininetClient> svc;
CComBSTR str;
if(svc.GetString(&str.m_str) == S_OK)
{
}
Or your own implementation for the SOAP transport. This sample demonstrates the creation of SOAP servers and clients that communicate using different transports: sockets, Microsoft Message Queue, the file system, and a custom HTTP listener:
SOAPTransport Sample: Communicates SOAP Messages Over Sockets, MSMQ, the File System, and HTTP Listener(MSDN)