Web 应用的执行过程 - -- 谈谈 WAD/CGI/ISAPI 的区别
Borland 在 Delphi/C++ Builder 中提供了多种类型的
Web 应用开发,其中最常用的应该是用于调试的 Web App Debugger 应用(WAD应用)和在 Windows/IIS
平台下运行的 CGI/ISAPI 。虽然从理论上说,这几种类型的 Web 应用程序除了类型不同以外,应该没有什么太大的区别。即一般情况下,可以很容易改变其类型,如:假设有一个调试好的
WAD 应用,只要新建一个空的 ISAPI 应用,然后把 WAD 应用中的必要单元加进来编译一下即可生成一个同样功能的 ISAPI
应用。但不幸的是,事实并非如此,在本站发表《C++ Builder 6 BizSnap(2)
-- 自定义数据传输》一文后不久,有几位朋友给我发来 Mail 说明他们在将文中的程序改成 ISAPI 后遇到一些问题,我才发现这几种类型的
Web 应用之间的一些必须关注的区别。
那个问题出在数据库访问控件和 DataModule 上。在那个例子中,我是把数据访问控件放在
WebModule 中,这在 WAD 中是没什么问题的,但在 ISAPI 中却有问题,这与这两种类型的程序执行过程不同有关。另一个则是关于 DataModule
的,在 WAD 中增加一个 DataModule 来放数据访问控件是没什么问题的,但在 CGI 中却会出现程序不执行的情况(即出现下载文件的提示或出错),在
ISAPI 中则出现 AV(Access Violation) 错误。
所以本文将从这几种类型的 Web 应用程序的执行过程分析来看看它们的区别。
先看看 WAD 的执行过程:
分析 WAD 类型的源程序得知:每个 WAD 应用都会用 TWebAppAutoObjectFactory 创建一个 Automation 对象,当 WAD 收到一个发给此 WAD 应用的 Web 请求时就会用 GetActiveObject 来调用一个运行中的实例,如果没有实例在运行就启动一个。一旦取得运行中的 WAD 应用实例就通过此 Automation 对象的接口把 Web 请求发过去,由此 Automation 对象调用 WebModule 来模拟真实的 Web 服务器。之后的执行是一般 Web 应用必要的过程。在 Automation 对象被释放时(如引用计数减为0时),同时释放 WebModule 等。即在整个 SOAP 请求期间,WebModule 都是可用的。
再来看看 CGI 应用:
CGI 是作为 Web 服务器外部的一个进程单独运行,它是一个标准的控制台程序,只是 Web 服务器把 Web 请求重定向到 CGI 程序的标准输入,而把 CGI 程序的标准输入重定向到 Web 响应。但是 Borland 在编译处理时可能有一些特别做法,即当程序中有 DataModule 时会把它编译为非 Web 应用,使这样的 CGI 程序在 IIS 中不能运行(即在浏览器中输入其 URL 时将出现下载对话框)。所以,必须在处理请求时动态创建 DataModule 才行。因为 CGI 是根据每个 Web 请求创建实例的,所以当请求响应返回, CGI 程序也就结束了,此时才释放 WebModule ,所以实际上在整个 SOAP 请求期间,WebModule 都是可用的,这一点跟 WAD 应用一样。
最后来看一下 ISAPI 应用:
实际上, ISAPI 应用是在第一个请求到达 Web 服务器时就被启动(此时创建 WebModule ),此操作在 ISAPI 的入口: DllEntryPoint 函数中实现。请求处理结束后, ISAPI 应用仍存在于内存中,包括 WebModule 也是。 Web 服务器为每个请求创建一个 Worker Thread 来处理,处理完成只是终止 Worker Thread 。即在处理请求时是在 Worker Thread 中,并不与 WebModule 在一起,特别是 IIS 可以用 dllhost.exe 来调用 ISAPI ,使它在独立的进程空间中运行(这种情况下不执行 DllEntryPoint),所以在处理请求时无法访问 WebModule 及同时创建的 DataModule (如果有的话),如果在处理请求时用到它们就将出现 AV 错误。所以在 ISAPI 中,不能使用全局的 DataModule ,只能动态创建。
解决 DataModule 的问题还有一个办法,那就是用 SOAP Server Data Module 来代替 DataModule 。为什么这样是可以的?因为 SoapDataModule 其实只是一个从 IAppServerSOAP 派生的接口,在每个请求处理时会自动创建相应的实例,可以省去手工动态创建 DataModule 的麻烦。但它也有一个问题,那就是在 WSDL 中多了很多不必要的接口: IAppServer/IAppServerSOAP 等,同时,如果在其中放了 DataSetProvider 的时候,会把不必要导出的表导出了。特别是用了 dbExpress 的情况,此时尽可能用 SQLClientDataSet 来代替 SQLDataSet + DataSetProvider + ClientDataSet 组合。