关闭

使用MFC开发ISAPI Extensions程序

1566人阅读 评论(0) 收藏 举报
  本文主要介绍了如何运用一些Web 服务器所支持的Internet Server API (ISAPI) 编程接口来创建交互式的Web 应用程序(Internet Server Applications, 或者简称为ISAs),以及如何调试ISAPI Extension 程序。在阅读本文时,虽然不要求读者对Web/CGI 开发有很深的了解,但是必须具有使用Visual C++ v4.1 以上版本的MFC 开发应用程序的经验。本文中的示例程序就是在Windows NT 下用Visual C++ v5.0 创建的。

1.引言

  1 .1 ISAPI 与CGI

  通用网关接口Common Gateway Interface (CGI) 很早就作为交互式的Web 应用程序的一个标准广泛应用在Internet 之中。CGI 脚本允许人们用多种编程语言( 如Basic、C、Perl、Shell 等等) 来编写简单的应用程序。这些脚本运行在Web 服务器上,而在客户的Web 浏览器上输出运行结果。客户的输入通过环境变量或者标准输入设备来进行传递,然后CGI 程序根据需要完成特定的功能,并通过标准输出设备送回HTML 格式的结果显示在客户的浏览器中。CGI 的这一特性—设计简单,再加上它支持多种编程语言,使得开发CGI 应用程序非常简单。尽管如此,人们在使用中还是发现了CGI 应用程序的一个很大的缺点:性能不高。虽然有不少办法来使CGI 应用程序运行得更快一些(如把它们变成编译好的二进制代码,而不用Perl 脚本),但执行速度仍然是一个问题。每当通过Web 访问一个CGI 程序时,CGI 执行文件(或者脚本的解释器)都要为每一个请求创建一个新的进程。对于一个信息量比较大的站点来说,这无疑给服务器增加了一个沉重的负担。

  当微软开始开发自己的Web 服务器(Microsoft Internet Information Server 或简称为IIS) 时,意识到了CGI 的这一大的缺陷,于是他们就引入了ISAPI。

  ISAPI 使用动态链接库DLL 而不是可执行代码。这些DLLs 被装入到服务器的内存空间。由于代码在内存中缓存起来了,而不是每次接收到请求时再装入到内存中,因此这一技术极大地提高了交互式的Web 应用程序的性能。

  ISAPI 程序分为两种:一种就是我们要介绍的ISAPI Extensions,它提供了一种比CGI 更好的实现方法;另一种称作ISAPI Filter,它可以对服务器上进入或出去的数据进行过滤。

  总的来说,ISAPI 优于CGI 之处包括:

  ①速度快:ISAPI 在性能上有很大的提高;

  ②功能强:ISAPI 还可以创建Filter 以对数据进行预处理。并且它完全与MFC 集成在一起了。

  相反,ISAPI 的不足之处有:

  ①标准化不够:目前只有一部分服务器支持ISAPI;

  ②开发困难:相关资料很少,并且调试很麻烦。

  1 .2 ISAPI 基础

  ISAs 开发主要基于MFC 的CHttpServer 类。该类控制了所有与服务器的交互操作,同时还包含了用于客户请求的所有函数。尽管一个ISA 只能有CHttpServer 类的一个实例,但每个ISA 仍然可以同时处理多个请求。这是通过CHttpServer 类为每个请求创建一个CHttpServerContext 类来实现的。CHttpServerContext 类包含了每个特定请求的所有数据以及由ISA 返回到客户的所有HTML 代码。

  ISAPI DLLs 的调用方法和CGI 一样:在客户端使用GET 或POST 方法。例如,当客户作出下列请求时:

  http://www.mysite.com/myisa.dll?name=fisherman&id=12345

  "name" 域和"id" 域以及与它们相关的数据都被传递给ISA。ISAPI 在使用这些相关的数据之前把它们存放在相应的数据结构中,这是通过一个请求映射系统来实现的。

  每一个请求都有一个解析映射表。通过定义服务器从客户接收的数据的类型和顺序,该解析映射表可以把数据传递到合适的数据结构中。例如,对于请求"name=fisherman&id=12345",解析映射表将显示一个字符串和一个整型数,并且" fisherman " and "12345" 将被解析出来存放到各自的数据结构中。

  解析映射系统还有另外一个功能:ISAPI 可以把请求传递给ISA 内特定的成员函数。请求字符串可以包含一个命令,解析映射表就使用该命令把请求传递给ISA 内正确的成员函数。

  由于ISAPI 使用命令驱动机制来处理请求,因此在开始开发ISA 程序时可能会觉得有些麻烦,但是一旦学会了,用户就会发现这是一个非常强大的处理请求的方法。

  2 .使用MFC 开发ISA 程序

  2 .1 建立工程

  开发ISA 的第一步工作是建立一个工程。和创建其它Visual C++ (VC++) 工程一样,创建ISA 也有一个wizard 来指导用户完成初始的步骤。打开VC++,在File 菜单中选择New,然后在对话框中选择Projects 面板,在下面的列表中选择"ISAPI Extension Wizard" 工程类型,选择适当的路径,并把它命名为"HelloWeb"。

  接着选择Ok 按钮,于是出现一个对话框让用户选择预创建的ISAPI 程序类型,缺省情况下是ISA 程序,同时MFC 被设置为动态链接。如果用户开发的服务器上有了这些MFC DLLs,这当然是可以的。但是如果没有安装Developer Studio,通常情况下这些DLLs 是没有的,这样用户的ISA 将无法运行。此时应该把工程设置为静态链接。我们建议用户这样设置。

  接着选择Finish 按钮。VC++ 将显示一个对话框给用户一些关于新工程的提示信息。在此对话框中选择OK。

  现在工程已经创建好了,现在该处理一些复杂点的问题了。我们在前面提到过,ISA 在运行时是IIS 的一部分,而IIS 又作为NT 的一个服务而运行。这一事实使用调试过程变得复杂了,因为在IIS 运行时,VC++ 的调试器不能够接管ISA。为了解决这个问题,微软公司以两种形式发行了IIS:作为一项服务,以及作为一个单独的可执行程序。对于后一种情况,我们就可以在命令行上来控制服务器。虽然这样可以解决上述问题并使得开发过程变得容易一些,但实现起来显得很繁琐。下面我们来介绍这个过程。

  当用户处于debug 调试模式时,VC++ ( 以及IIS) 将在用户的帐号和权限下运行。由于通常IIS 完成的一些工作是不允许大多数用户有相应的权限的,因此用户(或用户的系统管理员)需要做以下工作:

  ①在桌面上选择“开始/ 程序/ 管理工具(公用)/ 域用户管理器”, 打开域用户管理器;

  ②在“规则”菜单中选择“用户权限”;

  ③选择“显示高级用户权限”检查框;

  ④在“权限”下拉列表中选择“以操作系统方式操作”;

  ⑤选择“添加”按钮得到“添加用户及组”对话框,选择“显示用户”按钮,并在“名称”列表中选择用户使用的帐号,然后选择“添加”按钮;

  ⑥选择“确定”按钮;

  ⑦对“产生安全审核”权限重复上述步骤。

  为了使这些设置生效,用户必须先退出登录,然后再登录回来。

  IIS 中包含了三项服务:FTP Publishing Service, Gopher Publishing Service 和World Wide Web。由于调试器要在命令行上运行IIS,所以所有这三项服务都必须停止。这可以通过“控制面板”中的“服务”程序或者使用IIS 的“Internet 服务管理器”来实现。如果需要进行大量的调试工作,我们建议用户通过“控制面板”中的“服务”程序来关闭IIS 服务并禁止它们自动启动,这样可以避免用户每次启动计算机时都要进行关闭服务的操作。

  接下来就必须对工程进行一些配置了:

  ①在Project 菜单中选择Settings 菜单项;

  ②选择Debug 面板,并在Category 下拉列表中选择General;

  ③在Executable for debug session 框中输入或者寻找IIS 执行文件的路径(通常情况下位于WINNT/system32/inetsrv/inetinfo.exe);

  ④在Program arguments 框中输入-e w3svc;

  ⑤选择Link 面板;

  ⑥在Output filename 框中输入被编译后的DLL 将被放置的路径和文件名。这个路径必须位于Web 服务器的根目录下或者某个虚拟目标下,以便客户可以通过URL 来访问。例如,我们的Web 服务器的根目录是c:/InetPub/wwwroot/,我们把helloweb.dll 放置在该目录下,这样客户就可以使用下面的URL 来访问它:

  http://www.mysite.com/helloweb.dll

  如果用户现在还没有退出登录以改变权限,请现在行动,然后再登录回来。

  ISAPI Extension Wizard 所产生的缺省代码已经包含了一个可工作的ISA 所需要的一切,虽然该ISA 还没有任何功能,但我们不妨先试着进行编译一下。

  按下F5 键以运行ISA,如果弹出对话框问是否创建工程时选择Yes。几秒钟之后调试器就已经工作了,此时IIS 应该运行在后台。现在一切就绪了,用户可以在自己喜欢的浏览器中输入上面所提到的URL,并在最后面加上一个问号?,如下所示:

  http://www.mysite.com/helloweb.dll?

  请注意把域名换成正确的值。

  第一次连接到ISA 时可能要花几秒钟的时间,此后该DLL 被缓存到了内存中,连接速度就会显著提高。当DLL 被装入后,在浏览器中应该显示如下信息:

  This default message was produced by the Internet Server DLL Wizard. Edit your CHelloWebExtension::Default() implementation to change it.

  我们的第一个ISA 已经工作了。

  2 .2 分析源代码

  一个ISA 包含两个主要组成部分:解析映射表和命令处理函数。

  当一个请求来自EXTENSION_CONTROL_BLOCK(此结构用于在服务器和ISA 之间进行通信)时,它被传递到命令解析映射表。解析映射表由一些宏组成,如HelloWeb 工程(HELLOWEB.CPP) 中的:



BEGIN_PARSE_MAP(CHelloWebExtension, CHttpServer)

// TODO: insert your ON_PARSE_COMMAND() and

// ON_PARSE_COMMAND_PARAMS() here to hook up your commands.

// For example:

ON_PARSE_COMMAND(Default, CHelloWebExtension, ITS_EMPTY)

DEFAULT_PARSE_COMMAND(Default, CHelloWebExtension)

END_PARSE_MAP(CHelloWebExtension)

  BEGIN_PARSE_MAP 宏标志着解析映射表的开始,它以ISA 的CHttpServer 类和基类作为参数;ON_PARSE_COMMAND 宏则把一个特定的请求或命令映射到一个命令处理函数中。它的参数包括命令处理函数名、函数的类以及请求的格式;DEFAULT_PARSE_COMMAND 宏确定了当请求为空或者与解析映射表不匹配时调用的函数。它的参数包括函数名和函数的类。

  命令处理函数是解析映射表中调用的主CHttpServer 类的成员函数。下面就是HelloWeb 工程中的Default 命令处理函数:



void CHelloWebExtension::Default(CHttpServerContext* pCtxt)

{

StartContent(pCtxt);

WriteTitle(pCtxt);

*pCtxt < < _T("This default message was produced by the Internet");

*pCtxt < < _T

("Server DLL Wizard. Edit your CHelloWebExtension::Default()");

*pCtxt < < _T("implementation to change it./r/n");

EndContent(pCtxt);

}






  当请求为空或者包含Default 时此函数被调用。首先它通过参数得到请求的CHttpServerContext(命令处理函数的第一个参数必须是一个CHttpServerContext),StartContent 把< HTML >< BODY > 标志放到pCtxt 中,然后WriteTitle 放置< TITLE > 标志。紧接着下面的三行语句把缺省的消息写入pCtxt,后者指向一个CHtmlStream 类。当ISA 结束时,该HTML 流缓冲区被发送到客户端。

  2 .3 修改HelloWeb

  下面我们将把缺省的消息换成" 我会编ISAPI 程序了!"。

  找到CHelloWebExtension 类的成员函数Default,修改成如下形式:

void CHelloWebExtension::Default(CHttpServerContext* pCtxt)

{

StartContent(pCtxt);

WriteTitle(pCtxt);

*pCtxt < < _T(" 我会编ISAPI 程序了!");

EndContent(pCtxt);

}


  然后按照前面方法编译并运行该DLL,并在浏览器中重装或者刷新URL,用户即可看到显示的消息已经变了。

  然而,如果用户看到"Server Error 500: Specified module not found." 这样的错误信息,则表示用户的工程是动态链接的,而所必需的DLLs 不存在。为了修正这个错误,需要到Project 菜单中选择Settings 命令,然后选择General 面板,在Microsoft Foundation Classes 下拉列表种选择Use MFC in a Static Library,然后重新编译该工程。

  3.深入理解ISA 编程

  3.1  进一步分析解析映射表

  在解析映射表中使用了五个宏:

  BEGIN_PARSE_MAP:开始解析映射表的定义;

  ON_PARSE_COMMAND:解析客户的命令;

  ON_PARSE_COMMAND_PARAMS:把请求的数据映射到相应的数据结构中;

  DEFAULT_PARSE_COMMAND:定义缺省命令;

  END_PARSE_MAP:结束解析映射表的定义。

  我们在来看一看HelloWeb 中的解析映射表:


BEGIN_PARSE_MAP(CHelloWebExtension, CHttpServer)

// TODO: insert your ON_PARSE_COMMAND()and

// ON_PARSE_COMMAND_PARAMS() here to hook up your commands.

// For example:

ON_PARSE_COMMAND(Default, CHelloWebExtension, ITS_EMPTY)

DEFAULT_PARSE_COMMAND(Default, CHelloWebExtension)

END_PARSE_MAP(CHelloWebExtension)


  此映射表定义了两个命令:一个空的请求和缺省的Default 命令。空的请求格式如下:

  http://www.mysite.com/helloweb.dll?

  它是由DEFAULT_PARSE_COMMAND 宏处理的。

  然而,如果Default 命令放在了问号的后面:

  http://www.mysite.com/helloweb.dll?Default

  则此命令由ON_PARSE_COMMAND 宏处理。该宏的第一个参数定义了命令的名称,同时它也是处理该命令的函数的名字。第二个参数是函数的类名。第三个参数用于解析与命令相关的数据,由于在HelloWeb 示例中只传送了命令而没有数据,因此它被设置为ITS_EMPTY。

  如果命令后面还有数据,则需要把它解析到合适的数据类型。例如,如果作出了下面的请求:

  http://www.mysite.com/myisapi.dll?Add&name=fisherman&id=12345

  则"name" 域需要放置到一个字符串中,"id" 域需要放置到一个整型数中。为此,ISAPI 定义了6个数据标识:


ITS_EMPTY:没有数据;

ITS_PSTR:字符串;

ITS_I2:short 整型;

ITS_I4:long 整型;

ITS_R4:float 浮点数;

ITS_R8:double 浮点数。


  因此,在上面的例子中应该有一个ITS_PSTR 数据标识和一个ITS_I4 数据标识。ON_PARSE_COMMAND 宏将变成:

  ON_PARSE_COMMAND(Add, CMyISAPIExtension, ITS_PSTR ITS_I4)

  仅仅这样还不能正确的工作,因为ISAPI 把& 分隔符之间的所有字符都解析成一个域。因此,"name=fisherman" 将被放置到一个字符串中,"id=15248" 域被放置到一个整型数中。这个问题可以通过使用ON_PARSE_COMMAND_PARAMS 宏来解决。它应紧跟在ON_PARSE_COMMAND 宏后面,并创建请求中的域名的一个映射表。因此上面的例子应该这样表示:

  ON_PARSE_COMMAND(Add, CMyISAPIExtension, ITS_PSTR ITS_I4)

  ON_PARSE_COMMAND_PARAMS("name id")

  这样就表明了名为"name" 的域应该与解析映射表中的第一个数据类型相联系,"id" 域应该与第二个数据类型相联系。

  当创建与解析映射表交互的HTML 窗体时,要确保窗体中的动作包含了该命令,并且窗体的方法是post。例如,上面的解析映射表对应的窗体应该是这样的:



< FORM ACTION="myisapi.dll?Add" METHOD=POST >

< INPUT NAME="name" >

< INPUT NAME="id" >

< INPUT TYPE=SUBMIT >

< /FORM >


  3.2  编写SimpleCalc 示例

  为了进一步加深认识,我们再举一个例子。SimpleCalc 是一个简单的基于Web 的计算器,它可以进行加、减、乘、除运算。缺省情况下SimpleCalc ISA 会显示一个窗体,其中包含两个用于输入数字的编辑框和一个选择运算模式的选择框。当此窗体被提交时,它将传送一个"Calc" 命令,服务器计算答案,最后在客户端显示结果。

  首先按照前面介绍的步骤来创建一个名为SimpleCalc 的新工程。接着需要建立解析映射表。该表要处理一个缺省的命令和一个Calc 命令。对于后者,编辑域num1 和num2 必须映射到double 浮点型数,选择框mode 必须映射到字符串。如下所示:


BEGIN_PARSE_MAP(CSimpleCalcExtension, CHttpServer)

//Handle "Calc" command.

ON_PARSE_COMMAND(Calc, CSimpleCalcExtension, ITS_R8 ITS_R8 ITS_PSTR)

//Maps "num1" and "num2" to the ITS_R8s, and "mode" to the ITS_PSTR.

ON_PARSE_COMMAND_PARAMS("num1 num2 mode")

//Display form if request is empty.

ON_PARSE_COMMAND(Default, CSimpleCalcExtension, ITS_EMPTY)

DEFAULT_PARSE_COMMAND(Default, CSimpleCalcExtension)

END_PARSE_MAP(CSimpleCalcExtension)


  下一步需要改变default 命令处理函数以便显示Calc 窗体。代码如下:


void CSimpleCalcExtension::Default(CHttpServerContext* pCtxt)

{

//Print the < HTML > < BODY > tags.

StartContent(pCtxt);

//Print the title.

WriteTitle(pCtxt);

//The next six lines print the default calc form.

//For this form to work correctly the action must contain the "Calc"

//command, and the method must be POST.

*pCtxt < < _T("< H3 >SimpleCalc< /H3< BR >< BR >");

*pCtxt < < _T("< FORM ACTION=/"simplecalc.dll?Calc/" METHOD=POST >");

*pCtxt < < _T("< INPUT NAME=/"num1/" SIZE=5 > ");

*pCtxt < < _T("< SELECT NAME=/"mode/" >< OPTION >+< OPTION >-");

*pCtxt < < _T("< OPTION >*< OPTION >/< /SELECT >");

*pCtxt < < _T("< INPUT NAME=/"num2/" SIZE=5 >< BR >< BR >");

*pCtxt < < _T("< INPUT TYPE=SUBMIT >< /FORM >");

//Print < /HTML > < /BODY > tags.

EndContent(pCtxt);

}


  现在需要编写Calc 命令处理函数。此函数必须决定用户选择的操作符并相应计算num1 和num2 的运算结果,然后返回该结果。其实现代码如下:



void CSimpleCalcExtension::Calc(CHttpServerContext* pCtxt,

double num1, double num2, LPTSTR mode)

{

double result;

//Prints the < HTML > < BODY > tags.

StartContent(pCtxt);

//Prints the title.

WriteTitle(pCtxt);

//Determine the operator.

switch( mode[0] )

{

//Add

case '+' :

result = num1 + num2;

//Print result.

*pCtxt < < num1 < < _T(" + ") < < num2 < < _T(" = ") < < result;

break;

//Subtract

case '-' :

result = num1 - num2;

//Print result.

*pCtxt < < num1 < < _T(" - ") < < num2 < < _T(" = ") < < result;

break;

//Multiply

case '*' :

result = num1 * num2;

//Print result.

*pCtxt < < num1 < < _T(" * ") < < num2 < < _T(" = ") < < result;

break;

//Divide

case '/' :

result = num1 * num2;

//Print result.

*pCtxt < < num1 < < _T(" / ") < < num2 < < _T(" = ") < < result;

break;

}

//Print < /HTML > < /BODY > tags.

EndContent(pCtxt);

}

  最后重新编译并运行此工程。当用户在浏览器中装入此ISA 时将会显示窗体。在此窗体中输入两个数字,并选择一个操作符,然后按下submit 按钮。片刻之后浏览器中将显示如下的结果:

  1123.000000 + 23565.000000 = 24688.000000
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:22130次
    • 积分:261
    • 等级:
    • 排名:千里之外
    • 原创:3篇
    • 转载:8篇
    • 译文:0篇
    • 评论:13条
    文章分类
    文章存档
    最新评论