原文
介绍
首先,回顾一下为简化ATLServer
开发而添加的一些新类
.需要时可随时使用这些类
,而不仅是用来ATLServer
开发.
密码学(CCryptProv,CCryptKey,CCryptKeyedHash
等):
加密引用(MSDN)
哈希:
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;
//使用`SHA-1`或其他方法,如`MD5,HMAC`等
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");
加密:
const int ENCRYPT_BLOCK_SIZE = 64;//因为用了使用`分组密码模式`的`TripleDES`,因此数据大小`必须是块大小的倍数`(`TripleDes`为`8`)
DWORD dwBlockLen = 2000 - 1000 % ENCRYPT_BLOCK_SIZE;
ATLASSERT((dwBlockLen%8)==0);
CHeapPtr<BYTE> buff;
buff.Allocate(dwBlockLen);
CCryptProv prov;
//需要`MS_ENHANCED_PROV`或`MS_STRONG_PROV`来取`TripleDES`算法
HRESULT hr = prov.Initialize(PROV_RSA_FULL, NULL, MS_STRONG_PROV);//`待办`:使用一些数据创建叫`testpln.txt`的文件,如加密文件使用`TripleDES`和基于密码的代码
{CCryptDerivedKey derKey;//对`要加密的文件`,使用`md5`哈希来`哈希`密码,一般你会使用`SHA`,这只是为了展示如何需要,可轻松使用`md5`.CCryptMD5Hash md5;hr = md5.Initialize(prov);//哈希文件密码md5.AddString(szPassword);//初化键以使用基于`TripleDES`和基于`md5`的密码哈希hr = derKey.Initialize(prov,md5,CALG_3DES);//对`AES`为`16`,对`3DES`为8BYTE iv[8];//生成加密强度`高随机字节`hr = prov.GenRandom(8, iv);//按`IV`设置上述字节hr = derKey.SetIV(iv);//按`CBC`设置`模式`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);
}
//使用`TripleDES`,并基于密码,解密相同文件
{CCryptDerivedKey derKey;CCryptMD5Hash md5;hr = md5.Initialize(prov);//文件密码md5.AddString(szPassword);//基于`md5`的密码哈希,初化键以使用基于`TripleDES`hr = derKey.Initialize(prov,md5,CALG_3DES);//如前设置`IV`,`模式`,`填充`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);
}
正则式:
CAtlRegExp<> regexp;
CAtlREMatchContext<> mc;
//匹配以`多个数字开头`,有`短划线`并以`多个数字结尾`的行
if(regexp.Parse("^\\d+-\\d+$") == REPARSE_ERROR_OK)
{const char* szNumDashNum="5663-4662";if(regexp.Match(szNumDashNum, &mc)){ATLTRACE("Matched");}
}
支持发送SMTP
电子消息:
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("`文件名`"));`msg.AddText(_T("message text"));msg.SetSubject(_T("Subject line"));conn.SendMessage(msg);
}
CoUninitialize();
编码:
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 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
客户:
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;
}
还有许多其他其他助手类和更具体的助手类
,来处理网络
应用或网络
服务开发.
调试:
ISAPI
过滤/扩展概述:
ISAPI
代表互联网
服务器应用编程接口.ISAPI
编程分为过滤器
和扩展
.两者都是带特定导出函数
的DLL
.
ISAPI
过滤随IIS
一起加载,并在内存中保留
,直到HTTPWeb
服务关闭.ISAPI
扩展按需加载
,并为网络
应用提供扩展功能
.
ISAPI
过滤,对检查/过滤IIS
下的传入和传出
的HTTP
请求(如,实现自定义认证,加密,压缩,记录日志等
)很有用.
ISAPI
扩展对开发动态网页
,和按URL
引用工作非常有用
.
ATL7
下没有ISAPI
过滤的类.ATLServer
以DLL
缓存,文件缓存,页缓存,内存缓存,数据源缓存,线程池
,使用基于网络
或基于网络
服务的接口远程管理
,预定义性能计数器
,会话支持
等形式提供对ISAPI
扩展的支持.
可见,这是大量功能/服务
.此外,可轻松地在现有服务
中添加你自己的服务
.
网络应用
ATL7Web
应用是ISAPI
扩展和处理器DLL
(或两者可组合
成一个DLL
)的组合,来创建动态网页
.
ISAPI
扩展是一个导出三个函数
的DLL
:HttpExtensionProc,GetExtensionVersion,TerminateExtension
.
处理器DLL
或Web
应用DLL
还为ATL
导出三个预定义函数
:InitializeAtlHandlers,GetAtlHandlerByName,UninitializeAtlHandlers
.
ATL7Web
应用基于模板构建.一个一般有.srf
扩展名的文本文件
,在ISAPI
扩展名的虚目录
中,且可有混合静态超文本
和动态/运行时
可替换标签的内容.
在GetExtensionVersion
中,ATL
初化它使用的堆
,预定义的性能显示器,工作线程,线程池
,dllcache
,页缓存和模板缓存
.
TerminateExtension
则相反
.
所有工作的主要函数
是HttpExtensionProc
.负责实现它的类
是CIsapiExtension
.CIsapiExtension::HttpExtensionProc
把传入请求
排队到I/O
完成端口.
由在(CThreadPool)
线程池中运行的工作线程
检查此完成端口
.所有这些工作线程
,只是在上述完成端口
(请求从HttpExtensionProc
排队)上用GetQueuedCompletionStatus
阻止.
默认,池
中有2个
启动线程(由CThreadPool
实现的ATLS_DEFAULT_THREADSPERPROC
或IThreadPoolConfig
控制).
因此,当这些工作者
线程中的可以
并成功
地从端口分离完成请求
时,它们执行该请求
.
请求
表明处理模板文件
,首先在页缓存
中查找整个处理的页
,通过覆盖CachePage()
方法并从CRequestHandlerT
继承类返回真
来控制.
如果找到,则把整个页文件
简单转移
地给用户,则完成了.否则,在缓存模板
中查找模板文件
.
如果不在缓存
中,则从磁盘读取(.srf
)文件,解析处理器DLL
标签,如果尚未在DLL
缓存中,则加载DLL
.
调用处理器
的DLL
入口,即InitializeAtlHandlers
,然后通过GetAtlHandlerByName
找到处理请求
的处理器类
.然后,从模板文件
中解析所有剩余的替换标签
.
模板文件
是已渲染
的,即,如果在CRequestHandlerT
继承类中,使用属性
或REPLACEMENT_METHOD_MAP
,对表示方法
的所有标签
,可通过tag_name
定义的GetAttrReplacementMethodMap
找到处理器类
中方法的地址
.
然后调用方法
并生成网页的动态内容
.再返回生成内容
给用户.最好在调试器
中查看事件序列
,查看它的内部工作原理
很有趣.
通过在HttpExtensionProc
处放置断点
,单步ATL
代码,然后单步到PostQueuedCompletionStatus
.在GetQueuedCompletionStatus
的当
循环中,放置另一个断点
.
现在,可按F5
,并按如下步骤
操作.
1,向导生成的代码
(当你选择DataSourceCache
时,也可自己干,但这可作为示例)还为每线程服务
生成自定义的CIsapiWorker
继承类.
2,这是为线程池
中的每个线程
创建的类.这允许为每线程
添加自己的方法和数据成员
.
3,即不需要同步
,且请求处理器类
,可通过调用IIsapiExtension::GetThreadWorker
来取此每线程工作者
的指针,链接也提供了使用它的示例
.
ATLServer
还提供了多种方法
来控制和配置网络
应用.它为远程控制线程池
,SRF
文件缓存和网络
应用DLL
缓存提供了默认实现
.
你只需为你想要通过HTMLWeb
接口或网络
服务公开
服务,定义
适当的符号
,限制特定用户
等,然后你就可免费
取得完整实现
!
可按扩展管理服务
中描述步骤
逐步操作!
扩展管理XMLWeb
服务客户
创建新项目
时选择服务
:块
缓存,文件缓存,数据源缓存,浏览器函数,会话服务,及帧提供的服务
:DllCache,StencilCache,ThreadPoolConfig
和AtlMemMgr
,都通过CIsapiExtension
继承类实现
的IServiceProvider::QueryService
公开.
还可在运行时通过IIsapiExtension::AddService
和稍后的QueryService
为它们添加自己的服务
.把第一组服务
的代码添加到请求处理器类
中,并注释掉
.
只需取消注释
并用来满足你的需求
.
此外,还有预定义的ISAPI
性能计数器可用.添加你自己的性能计数器
很容易.有一个此内容的简单示例,并在ATLServer
教程的收集统计
节下添加自定义服务
.
网服
ATLWeb
服务支持也可按ISAPI
扩展实现.可用该向导
,也可手动
.创建简单的Win32DLL
项目.
添加def
文件以导出3个ISAPI
扩展函数.然后,按最简单,使用ATL7
实现的网络
服务如下:
//`websvc.h`
#pragma once
namespace ATL7WebService
{[export]struct S{int n;double d;};[uuid("86C903DD-4D9F-4928A3B5AE242EA86D7A")]__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(/*`[out,retval]`*/ 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;}//等};
}
服务器:
//`websvc.cpp`
#define _WIN32_WINNT 0x0403
//需要属性
#define _ATL_ATTRIBUTES
//对此简单项目,不用`COM`
#define _ATL_NO_COM_SUPPORT
#include <atlisapi.h>
#include <atlsoap.h>
#include "websvc.h"
[ module(name="WebSvc", type="dll") ]
//不需要生成`.idl,.tlb`等文件
[ 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
项目属性,并在应用映射下添加dll扩展
,或在IIS
下创建虚目录
,并手动注册应用映射
.
之后,你可在网络
浏览器中访问网络
服务的WSDL
,如下http://localhost/websvc/WebSvc.dllHandler=WSDL
,
WebSvc.dll
有网络
服务的实现,WSDL
是你在request_handler
中,对sql
参数指定的编译器生成的处理器
.
现在,再次访问它时,与上一节中的网络
应用中一样,IIS
会调用HttpExtensionProc
.它会转发给CIsapiExtension
.
CIsapiExtension
会在I/O
完成端口上排队请求
.其中一个工作者
线程会分离请求
.然后,加载处理器DLL(websvc.dll)
.
会初化处理器
.它的作用是,取通过网络
服务类公开的所有函数
,以在WSDL
中写入它们的信息
.ATL
已有为网络
服务生成WSDL
的模板.
可在atlspriv.h(const char*consts_szAtlsWSDLSrf)
中找到它.你会看到它是一个适合Stencil
处理的(带可替换的标签
)简单模板文本
.
因此,此串
是通过CStencil::LoadFromString
加载的,且会解析和替换/渲染
所有标签.
现在,此WSDL
输出可用来生成网络
服务的客户.通过为.NET
客户手动运行wsdl.exe
,或运行ATL7
提供的名为sproxy.exe
的工具来生成代理客户文件
,你可用VS.NET
内置支持来添加网络
服务.
如:sproxy/out:client.h http://localhost/websvc/WebSvc.dllHandler=WSDL
如果使用sproxy.exe
,则生成的文件
有网络
服务按C++
类很好包装
地公开的方法
.要使用它,只需在你的项目中,包含生成的文件
(如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.data<blob.size-1><<std::endl;IAtlMemMgr* pMem = svc.GetMemMgr();pMem->Free(blob.data);WebSvc::S s;svc.GetStruct(&s);}}}CoUninitialize();return 0;
}
如果要使用窗口
的整合认证或基本认证
,可用CNTLMAuthObject/CBasicAuthObject
(CSoapSocketClientT
相关):
CoInitialize(NULL);
{//默认,使用`CSoapSocketClientT`WebSvc::CWebSvc svc;//此代码与`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){//等}
}
CoUninitialize();
如果要使用HTTPS
连接,可查看安全SOAP
示例.
此外,还可选择
使用预定义的类
通信,如CSoapSocketClientT
(默认),CSoapWininetClient,CSoapMSXMLInetClient
:
WebSvc::CWebSvcT<CSoapWininetClient> svc;
CComBSTR str;
if(svc.GetString(&str.m_str) == S_OK)
{//等
}