65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL Server - Web 应用程序/Web 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (26投票s)

2002 年 5 月 10 日

CPOL

8分钟阅读

viewsIcon

416911

使用 ATL Server 类进行 Web 应用程序/Web 服务开发

引言

其他 ATL7 的更改已在 此处此处 进行了介绍。首先,我们来回顾一下为简化 ATL Server 开发而添加的一些新类。这些类可以在任何需要的地方使用,不仅仅局限于 ATL Server 开发。

加密 (CCryptProv, CCryptKey, CCryptKeyedHash 等)

哈希

CCryptProv prov;
prov.Initialize();    
char rand[256];
memset(rand, 0, sizeof(rand));
//generating cryptographically random bytes

HRESULT hr = prov.GenRandom(sizeof(rand), (BYTE*)rand);
if(hr != S_OK) 
    return 0;

/////////////////////////////////////////////

//hashing

//TODO: input password from user. never hard code it like this!

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;
//use SHA-1 or any other such as MD5, HMAC, etc

hash.Initialize(prov, CALG_SHA1, CCryptKey::EmptyKey, 0);

// add data to hash

hr = hash.AddString(szPassword);
if(hr != S_OK) 
    return 0;

//get the hashed value

hr = hash.GetValue((BYTE*)Hash1st, &dwSize);
if(hr != S_OK)
    return 0;
hash.Destroy();

// now we can check the hashed value against original whenever we want

hash.Initialize(prov, CALG_SHA1, CCryptKey::EmptyKey, 0);

//add data to hash

hr = hash.AddString(szPassword);
if(hr != S_OK)
    return 0;
//get hashed value

hr = hash.GetValue((BYTE*)Hash2nd, &dwSize);
if(hr != S_OK)
    return 0;

//hash values should matched

if(memcmp(Hash1st, Hash2nd, sizeof(Hash1st)) == 0)
    ATLTRACE("\npassword matched.\n\n");
 

加密

const int ENCRYPT_BLOCK_SIZE = 64;
//since TripleDES is used, which uses block cipher mode, 

//the data size must be a multiple of block size (8 for TripleDes) 

DWORD dwBlockLen = 2000 - 1000 % ENCRYPT_BLOCK_SIZE;
ATLASSERT((dwBlockLen%8)==0);
CHeapPtr<BYTE> buff;
buff.Allocate(dwBlockLen);

CCryptProv prov;
//need MS_ENHANCED_PROV or MS_STRONG_PROV for TripleDES algo

HRESULT hr = prov.Initialize(PROV_RSA_FULL, NULL, MS_STRONG_PROV);

//TODO: create some file named testpln.txt with some data for example code 

// to work


//encrypt file using TripleDES and based on password

{
    CCryptDerivedKey derKey;

    //let's use md5 hash to hash the password for the file to be encrypted

    //normally you would use sha, this is only to show how you can easily use
//md5 if needed
CCryptMD5Hash md5; hr = md5.Initialize(prov); //hash file password md5.AddString(szPassword); //initialize the key to use TripleDES and md5 based hash of the password hr = derKey.Initialize(prov,md5,CALG_3DES); //16 for AES, 8 for 3DES BYTE iv[8]; //generate cryptographically strong random bytes hr = prov.GenRandom(8, iv); //set above bytes as IV hr = derKey.SetIV(iv); //set mode to CBC hr = derKey.SetMode(CRYPT_MODE_CBC); //set padding hr = derKey.SetPadding(PKCS5_PADDING); FILE* pFilePl = fopen("testpln.txt", "rb"); //original file if(pFilePl == NULL) return 0; FILE* pFileEn = fopen("testenc.txt", "wb"); //to be encrypted file DWORD dwEncLen = 0; DWORD dwOrigBlockLen = dwBlockLen; while(!feof(pFilePl)) { DWORD nRead = fread(buff, 1, dwBlockLen, pFilePl); //read plain data DWORD nEnc = nRead; //encrypt data, data and size is returned back to us hr = derKey.Encrypt(feof(pFilePl), (BYTE*)buff, &nEnc, dwBlockLen); if(hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) //error, buffer size // not enough { 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); //write encrypted data } fclose(pFileEn); fclose(pFilePl); //resize buffer back, in case it was changed dwBlockLen = dwOrigBlockLen; buff.Reallocate(dwBlockLen); } //decrypt same file using TripleDES and based on password { CCryptDerivedKey derKey; CCryptMD5Hash md5; hr = md5.Initialize(prov); //file password md5.AddString(szPassword); //initialize the key to use TripleDES and md5 based hash of the password hr = derKey.Initialize(prov,md5,CALG_3DES); //set IV, MODE, Padding like before FILE* pFileEn = fopen("testenc.txt", "rb"); //encrypted file FILE* pFileDec = fopen("testdec.txt", "wb");//to be decrypted file DWORD dwEncLen = 0; while(!feof(pFileEn)) { //read encrypted data DWORD nRead = fread(buff, 1, dwBlockLen, pFileEn); //decrypt data, data and size is returned back to us hr = derKey.Decrypt(feof(pFileEn), buff, &nRead); if(hr != S_OK) break; fwrite(buff, 1, nRead, pFileDec); //write decrypted data } fclose(pFileDec); fclose(pFileEn); }

正则表达式

正则表达式参考 (MSDN)
CAtlRegExp<> regexp;
CAtlREMatchContext<> mc;
// match any line that starts with any number of digits,

// has a dash, and ends with any number of digits

if(regexp.Parse("^\\d+-\\d+$") == REPARSE_ERROR_OK)
{
    const char* szNumDashNum="5663-4662";
    if(regexp.Match(szNumDashNum, &mc))
    {
        ATLTRACE("Matched");
    }
}

支持发送 SMTP 电子邮件

MIME & SMTP 参考 (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.AttachFile(_T("filename"));

    msg.AddText(_T("message text"));
    msg.SetSubject(_T("Subject line"));
    conn.SendMessage(msg);
}
CoUninitialize();

编码

编码参考 (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 生成

HTML 生成参考 (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 客户端

HTTP 客户端参考 (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.NavigateChunked()

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;
}
还有许多其他杂项辅助类和更具体的类,用于 Web 应用程序/Web 服务开发。

调试

调试 (MSDN)

ISAPI 过滤器/扩展概述

ISAPI 是 Internet 服务器应用程序编程接口的缩写。ISAPI 编程分为过滤器和扩展。两者都是具有特定导出函数的 DLL。ISAPI 过滤器随 IIS 加载,并驻留在内存中,直到 HTTP Web 服务关闭。ISAPI 扩展按需加载,并为 Web 应用程序提供扩展功能。ISAPI 过滤器可用于检查/过滤 IIS 中的传入和传出 HTTP 请求(例如,实现自定义身份验证、加密、压缩、日志记录等)。ISAPI 扩展可用于开发动态网页,并按 URL 引用工作。

ATL7 中没有 ISAPI 过滤器的类。ATL Server 以 DLL 缓存、文件缓存、页面缓存、内存缓存、数据源缓存、线程池、通过基于 Web 或基于 Web 服务的接口进行远程管理、预定义的性能计数器、会话支持等形式为 ISAPI 扩展提供支持。正如您所见,这提供了相当多的功能/服务。此外,还可以轻松地将您自己的服务添加到现有服务中。

Web 应用程序

ATL7 Web 应用程序是 ISAPI 扩展和处理程序 DLL 的组合(也可以将两者合并到一个 DLL 中),用于创建动态网页。ISAPI 扩展是一个导出三个函数的 DLL:HttpExtensionProc, GetExtensionVersion, TerminateExtension。处理程序 DLL/Web 应用程序 DLL 还为 ATL 导出了三个预定义函数:InitializeAtlHandlers, GetAtlHandlerByName, UninitializeAtlHandlers

ATL7 Web 应用程序基于模板文件(stencil)构建。Stencil 是一个文本文件,通常带有 .srf 扩展名,位于 ISAPI 扩展的虚拟目录中,它可以包含静态 HTML 和动态/运行时可替换标签的混合内容。在 GetExtensionVersion 中,ATL 初始化它使用的堆、预定义的性能监视器、工作线程、线程池、DLL 缓存、页面缓存和模板缓存。TerminateExtension 执行相反的操作。

执行所有工作的核心函数是 HttpExtensionProc。负责实现它的类是 CIsapiExtensionCIsapiExtension::HttpExtensionProc 将传入的请求排队到 I/O 完成端口。工作线程(在线程池 CThreadPool 中运行)会检查此完成端口。所有这些工作线程所做的就是阻塞在上述完成端口上的 GetQueuedCompletionStatus(请求是从 HttpExtensionProc 排队到该端口的)。默认情况下,池中启动 2 个线程(由 ATLS_DEFAULT_THREADSPERPROCCThreadPool 实现的 IThreadPoolConfig 控制)。

因此,当任何一个工作线程获得机会并成功从端口中出队完成请求时,它们就会执行该请求。执行请求意味着处理模板文件,首先在页面缓存中查找整个处理后的页面(通过覆盖 CachePage() 方法并从派生的 CRequestHandlerT 类返回 TRUE 来控制)。如果找到,则将整个页面文件简单地传输给用户,一切就绪。否则,在模板缓存中查找模板文件。如果不在缓存中,则从磁盘读取 (.srf) 文件,解析处理程序 DLL 标签,并加载 DLL(如果尚未在 DLL 缓存中)。

调用处理程序的 DLL 入口点,即 InitializeAtlHandlers,并通过 GetAtlHandlerByName 找到处理请求的处理程序类。然后,解析模板文件中的所有剩余可替换标签。渲染模板文件,即,对于所有代表方法的标签,通过使用属性时的 `tag_name` 定义的 `GetAttrReplacementMethodMap` 或派生的 CRequestHandlerT 类中的 REPLACEMENT_METHOD_MAP 来查找处理程序类中的方法地址。然后调用方法并生成动态内容以用于网页。生成的内容会发送回用户。最好在调试器中检查事件序列,并建议您也这样做。了解其内部工作原理很有意思。

要单步调试 ATL 代码,请在 HttpExtensionProc 处设置断点,然后单步调试直到 PostQueuedCompletionStatus。另一个可以设置断点的地方是 GetQueuedCompletionStatuswhile 循环。现在您可以按 F5 并按照整个步骤序列进行。

向导生成的代码(当您选择数据源缓存时,您也可以自己完成,但这可以作为示例)还会为每个线程创建自定义的 CIsapiWorker 派生类,用于线程级服务。这是为线程池中的每个线程创建的类。这允许您为每个线程添加自己的方法和数据成员。这意味着它不需要同步,并且您的请求处理程序类可以通过调用 IIsapiExtension::GetThreadWorker 来获取指向此线程级工作线程的指针(链接中也有一个使用它的示例)。

ATL Server 还提供多种方法来控制和配置您的 Web 应用程序。它为线程池、SRF 文件缓存和 Web 应用 DLL 缓存的远程控制提供了默认实现。您只需定义您想通过 HTML Web 界面或 Web 服务公开的任何服务的适当符号,限制给特定用户等,即可免费获得完整的实现!您可以按照 扩展管理服务 中描述的步骤进行操作,逐步完成!
扩展管理 XML Web 服务客户端

当您创建新项目时选择的服务:Blob 缓存、文件缓存、数据源缓存、浏览器功能、会话服务,以及框架提供的服务:DllCache、StencilCache、ThreadPoolConfig 和 AtlMemMgr,都通过 IServiceProvider::QueryService 公开,而 CIsapiExtension 派生类实现了该接口。您还可以通过 IIsapiExtension::AddService 在运行时添加您自己的任何服务,稍后通过 QueryService 查询它们。第一组服务的代码将添加到您的请求处理程序类中,并且会被注释掉。您所需要做的就是取消注释并根据您的需求应用它们。

此外,还有 预定义的 ISAPI 性能计数器。添加自己的性能计数器也很简单。在 ATL Server 教程的收集统计信息部分有一个简单的示例,演示了这一点以及添加自定义服务。

Web服务

ATL Web 服务支持也作为 ISAPI 扩展实现。您可以使用向导,也可以出于学习目的手动尝试。创建一个简单的 Win32 DLL 项目。添加 .def 文件以导出 3 个 ISAPI 扩展函数。然后,最简单的形式,用 ATL7 实现的 Web 服务可以如下所示:

//websvc.h

#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);
        //etc

    };

    [request_handler(name="Default", sdl="WSDL")]
    [soap_handler(name="WebSvc", namespace="urn:ATL7WebService")]
    class CWebSvc : public IWebSvc
    {
    public:
        [soap_method]
        HRESULT GetString(/*[out,retval]*/ BSTR* pVal)
        {
            CComBSTR strOut("Some String");
            *pVal = strOut.Detach();
            return S_OK;
        }
        [soap_method]
        HRESULT GetData(/*[out, retval]*/ 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(/*[out, retval]*/ S* pVal)
        {
            pVal->n = 10;
            pVal->d = 1.1;
            return S_OK;
        }
        //etc

    };
}
//websvc.cpp

#define _WIN32_WINNT 0x0403
//need for attributes

#define _ATL_ATTRIBUTES
//not using COM for this simple project

#define _ATL_NO_COM_SUPPORT 
#include <atlisapi.h> 

#include <atlsoap.h>

#include "websvc.h"


[ module(name="WebSvc", type="dll") ];
//don't need any .idl, .tlb, etc files generated

[ 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); }

您可以使用 VC++.NET 项目属性进行 Web 部署,并在应用程序映射中添加 .dll 扩展名,或者创建虚拟目录并在 IIS 下手动注册应用程序映射。之后,您可以在 Web 浏览器中以以下方式访问 Web 服务的 WSDL:https:///websvc/WebSvc.dll?Handler=WSDL
WebSvc.dll 包含您的 Web 服务的实现,WSDL 是您在 `request_handler` 中为 sdl 参数指定的编译器生成的处理程序。

现在,当您像前面部分中的 Web 应用程序一样再次访问它时,IIS 将调用 HttpExtensionProc。它会将控制权委托给 CIsapiExtensionCIsapiExtension 会将请求排队到 I/O 完成端口。一个工作线程将出队请求。然后加载处理程序 DLL (websvc.dll)。初始化处理程序。它的作用是获取您 Web 服务类公开的所有函数,以将关于它们的信息写入 WSDL。ATL 已经有了一个用于生成 Web 服务 WSDL 的模板。它可以在 `atlspriv.h` 中找到(const char * const s_szAtlsWSDLSrf)。您会看到它是一个简单的模板文本,适合模板处理(带有可替换标签)。因此,此字符串使用 CStencil::LoadFromString 加载,所有标签都会被解析和替换/渲染。

现在,此 WSDL 输出可用于生成您的 Web 服务的客户端。您可以使用 VS.NET 的内置支持添加 Web 服务,通过手动运行 `wsdl.exe` 来生成 .NET 客户端,或者运行 ATL7 提供的名为 `sproxy.exe` 的工具来生成代理客户端文件。例如:sproxy /out:client.h http://localhost/websvc/WebSvc.dll?Handler=WSDL

如果使用 sproxy.exe,生成的文件将包含一个 C++ 类,该类 nicely 地包装了您的 Web 服务公开的方法。要使用它,只需将生成的文件(例如 client.h)包含在您的项目中。

//client.cpp

#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;
}

如果您想使用 Windows 集成身份验证或基本身份验证,可以使用 CNTLMAuthObject/CBasicAuthObjectCSoapSocketClientT 特有)

CoInitialize(NULL);
{
    //by default uses CSoapSocketClientT

    WebSvc::CWebSvc svc;
    
    //this code is specific to CSoapSocketClientT

    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)
    {
        //etc

    }
}
CoUninitialize();

如果您想使用 HTTPS 连接,可以查看 Secure SOAP 示例。
SecureSOAP 示例:实现安全的 SOAP 通信 (HTTPS) (MSDN)

此外,您还可以选择使用预定义的通信类,例如 CSoapSocketClientT(默认)、CSoapWininetClient、CSoapMSXMLInetClient

WebSvc::CWebSvcT<CSoapWininetClient> svc;
CComBSTR str;
if(svc.GetString(&str.m_str) == S_OK)
{
   //etc

}

或者您自己的 SOAP 传输实现。本示例演示了如何创建使用不同传输方式的 SOAP 服务器和客户端:套接字、Microsoft 消息队列、文件系统以及自定义 HTTP 侦听器。
SOAPTransport 示例:通过套接字、MSMQ、文件系统和 HTTP 侦听器通信 SOAP 消息 (MSDN)

© . All rights reserved.