发信人: dragon (海狼), 信区: BorlandDev
标  题: Delphi与Internet (1)
发信站: 哈工大紫丁香 (2000年12月12日09:27:01 星期二), 站内信件

                             DELPHI AND THE INTERNET
                                  by Charlie Calvert
由Boen翻译,转载请注明: Translate by Boen.
中文译稿版权属Boen所有,使用权归 CNM 编程版
《第一》
----------------------------------------------------------------------------
----
这篇文章主要讲述如何使DELPHI和因特网配合工作。本文中将详述两个专门技术:
WININET:构建 FTP,HTTP 和 Gopher 用户端程序
ISAPI:扩充因特网信息服务,例如,获得服务器上的信息并把它们显示在浏览器上。
现今的计算机世界中,由于微软公司的因特网战略而掀起了一个巨大发展潮流。那些制
作CGI(公共网关接
口)和第三方工具(即使是最基本的因特网工具)的日子将最终一去不复返了。对复杂
的第三方工具的需求
总是存在的,但现在程序员将会发现他们所需的大量的嵌入操作系统的因特网工具,简
言之,并不需要进一
步的投资,你就能够使用免费的DELPHI资源来:
*开发 WEB 浏览器
*运行 FTP,HTTP 和 Gopher ,在两个DELPHI应用软件之间或DELPHI应用软件和基于TC
P(传输控制协议)的
服务器之间操作TCP
    因为DELPHI能够轻松地调用Windows API,并且它支持OCX/ActiveX,因此微软的新
战略和我们的计划配合
的恰到好处。微软生产工具,而DELPHI程序员获得收成!
----------------------------------------------------------------------------
----
在本篇中有些什么?
这篇文章中包含了三个大部分和一些小部分,有三个大主题:
*寻找资料: 那里能搞到本文中提及的技术资料,而且包含了关于您所需的运行文中代
码的软硬件的简短
说明。
*ISAPI:怎样使用ISAPI
*WININET:怎样使用WININET
  在大多数情况下,本文中的ISAPI和WININET部分是完全独立的,您可以自由地选择阅
读时的顺序。
----------------------------------------------------------------------------
----
查找资料,硬件和软件的要求
您需要一份Microsoft Windows NT 3.51 Server 或 NT 4.0 Server 的拷贝,其中应附
有因特网信息服务
文档,因为您需要甬道其中所提到的技术。这份文档应随NT Server4.0 附送,NT 3.51
的用户可从微软的
网址上下载。运行Windows NT,您的机器的最低配置应为486兼容,20兆以上内存。
您必须有另一台计算机装有网页浏览器。为使本文中的ISAPI部分能够顺利运行,第二台
机器必须能够运行
所有支持网页浏览器的软件。如果在您的机器上运行的是Windows 95 或 Windows NT,那
么本文中的WININET
代码片就能运行的最好。任何符合条件的网页浏览器在这种技术环境下都能够使用。
在1996年六月以后发布的Delphi2.0以上的版本中,有您所需的把Delphi连接到因特网上
的几乎全部资源。
如果您没有最新的Delphi版本[注:此处作者指的是2.0版本(译者)],那么您需要本文
档中提到的特殊文
件,所有这些几乎都可以从万维网上免费获得[注:如果您正在使用Delphi2.0以上版本
,则不许考虑
(译者)]。所有本文中提到的技术在Delphi2.0环境下都能顺利工作,但在16位Delphi
环境下则不一定能
顺利工作。
如果您需要从万维网上下载信息,链接为:http://www.borland.com/TechInfo/delphi
/index.html
[注:现在已经不存在了!:-((  (译者)]
Delphi2.0的新版本中附有 WININET.PAS 文档,如果你的拷贝中不包含它,那么上面那
个万维网节点可以
为您提供。WININET.PAS包括为扩展微软视窗因特网所设计的变量清单、函数、类型和属
性。这意味着您能
够轻而易举地为您的应用程序增添FTP、HTTP和Gopher支持。微软公司的WININET.DLL是
免费发布的,如果
它不在您的Windows/System 或 Windows/System32 目录下的话,您可以从微软公司那里
得到它。下面是可
获得WININET.H这个视窗帮助文件的万维网节点:
http://www.microsoft.com/intdev/sdk/docs/wininet/default.htm [注:好象也没了
!:-( (译者)]
一般来说,微软因特网开发者的网上之家是微软节点的 INTDEV 部分。
除了WININET和ICP之外,另一个为Delphi支持的关键技术就是ISAP。正如微软公司文档
中所描述的,这项技
术能使您“‘写入’服务器端的原本和过滤本,从而扩充微软因特网信息服务和其他IS
API万维网服务”。
如果您需要找到关于ISAPI的描述,可以去:
  http://www.microsoft.com/intdev/sdk/servapi.htm [注:上帝保佑您!;-) (译者
)]
在本文最后,附加了一个名为HTTPEXT.PAS的关键的ISAPI文档的拷贝。
微软公司免费发布的因特网控制包(ICP)是一个OCX/ActiveX控制集,您可以在Delphi
中把它们拖放到应用
程序上(Delphi2.0中包含这些控件)。他们提供了创建Delphi应用程序的即时支持,他
们知道如何浏览网页、
如何应用FTP、WINSOCK和其他因特网技术。如果您的Delphi拷贝中没有包含这些控件,
那么您在使用它们之
前您应该把这些文档添加进Delphi所在的目录中的Lib目录下。这些文档位于上面提及的
链接中的Borland的
INDEX.HTML站点下。在本文中我没有提到ICP控件,但是任何对这项技术有兴趣的人应该
明确确认他拥有这些
控件的拷贝。
您可以从我的站点下载我的Pascal应用文件,他们的名字是STRBOX.PAS 和 MATHBOX.PA
S。
经常察看一下这个站点上的关于本文提到的信息的更新情况是很有好处的。
在这里我假设读者对于Delphi和Object Pascal都很熟悉,并且读者对于因特网,HTML,
浏览器和万维网服
务器有基本的了解。
ISAPI
ISAPI是一项很容易使用然而功能强大的技术,它能够让您扩充因特网信息服务的功能。
这项技术随WindowsNT
4.0附送,让您在您的服务器上建立WEB、FTP和GOPHER站点。同时这项技术与WindowsNT
3.51 Server[注:指服
务器版本,另一个版本是工作站版本(译者)]兼容。
在过去,扩充网页服务器的最佳办法是建立CGI应用程序。它们是强有力的工具,但是也
被他们的执行格式所
限制[注:如PERL是解释执行的(译者)]。当您从浏览其上发出一个基于CGI的请求到服
务器上时,这个CGI
应用程序将极有可能先被强制装入内存中,这会消耗很多时间。而且,在某些环境下,
CGI技术显得稍微难用
了一点。
ISAPI是一种通过写入DLLs[注:动态链结程序(译者)]从而替代CGI应用的方法。您也
可以通过ISAPI来写
过滤文本,但这项技术我不会在本篇中提及。同CGI相比,ISAPI更容易使用,而且它更
快,同时能更好地利
用系统资源。在下面几点中,我将详细地介绍为什么ISAPI DLLs比CGI应用要更为出色:

ISAPI DLLs与HTTP服务位于相同的地址,因此他们能够从服务器上直接存取HTTP服务。
与CGI应用相比,它们
能更快地装入内存;当他们在服务器上发出请求时,所需的停悬的时间[注:指发出请求
到接受服务器应答的
时间(译者)]要少的多。这点当服务器的负荷很重时更加重要。
您可以控制DLLs何时被装载和卸载。例如:您可以在第一次尝试请求时预先装载DLLs;
当它们不被使用时卸载
这个ISAPI应用DLLs以便释放系统资源。
正如前文所述,您可以利用ISAPI写过滤文本[注:一般指C/S结构中的脚本(译者)],
更具微软的文档,您可
以通过ISAPI过滤文本做下面这些事情:
用户授权方案
压缩
加密
登入
通信分析或其他请求分析(例如,寻找 "..\..\etc\password" 中的请求)
在本文中,我会着重介绍如何编写返回数据集的DLLs,或者是如何与运行浏览器的用户
进行简单的联系。
ISAPI 基础
HTTPEXT.PAS文件包含了使用ISAPI的关键声明。这个文件应随1996年6月以后发表的Del
phi版本分发。它也可
以在Borland的站点上找到,在本文的ISAPI部分附有这份文档。因为这是基于NT的技术
,您必须使用Delphi2.0
以上的版本来应用这项技术。您不可能在16位的编辑器上应用它。
HTTPEXT.PAS包含了微软公司创立的ISAPI技术的接口[注:指Delphi接口,ISAPI由C++编
写(译者)]。在编写
Delphi的时候并没有提供ISAPI的用户接口,我会仅仅就如何使用微软公司的现有技术进
行描述。不过,ISAPI
太容易使用了,而且对大多数用户来说,用户的Delphi对象的版本并不是必须的。
有三个函数可作为ISAPI DLLs的入口,前两个是必须的,第三个时可选的。
GetExtensionVersion: 进对最低版本做检查
HttpExtensionProc: 这是DLL的入口,就象是Delphi应用程序中的 begin...end 块
TerminateExtension: 这是个可选的程序,它可以用作清除其他内存分配的线程。
当您在创建ISAPI DLL的时候,您必须引用上面列出的三个函数中的头两个函数,执行这
两个函数是所有ISAPI
编程的关键。
这三个语句都包含了“字输出”,使用这项术语是因为ISAPI DLLs扩充了因特网信息服
务器。(记住,因特网
信息服务器指的是微软服务器。如果您要把一台NT服务器作为体格网页服务器的话,那
么,这正是您所需的工
具。ISAPI DLLs随NT4.0分发,在安装操作系统是自动安装。)
ISAPI提供了一个制作服务器可遵循的标准。例如,它可以把网景公司的复杂的NSAPI接
口压缩至相关的简练而
优美的ISAPI来对NSAPI接口进行操作。
下面是这两个重要函数的声明
function GetExtensionVersion(var Ver: THSE_VERSION_INFO):   BOOL; stdcall;
function HttpExtensionProc(var ECB: TExtensionControlBlock):   DWORD; stdcal
l;
您只要把GetExtensionVersion粘贴到您的DLLs救行了.当ISAPI向公众发布新版本时您只
需要做轻微的改动。
function GetExtensionVersion(var Ver: THSE_VERSION_INFO):
  BOOL; stdcall;
begin
  Ver.dwExtensionVersion := $00010000;  // 1.0 support
  Ver.lpszExtensionDesc := 'Delphi 2.0 ISAPI DLL'; // Description
  Result := True;
end;
The parameter passed to this function is declared in HTTPEXT.PAS as follows:

有关的参数在HTTPEXT.PAS中声明如下:
PHSE_VERSION_INFO = ^THSE_VERSION_INFO;
THSE_VERSION_INFO = packed record
  dwExtensionVersion: DWORD;
  lpszExtensionDesc: array[0..HseMaxExtDLLNameLen-1] of Char;
end;
常量 HseMaxExtDllNameLen 在声明中的值为256。纪录中的这两个变量是“自声明”的
,前一个包含了
ISAPI的版本号[注:即变量dwExtensionVersion (译者)],后一个则表示用户定义的
一个用来描述
DLLs的字符串。
在您引用GetExtensionVersion语句的同时,您必须在您的DLL程序的DPR文件部分增添输
出部分。在您写
这段语句时您还应该写下:
exports
  GetExtensionVersion,
  HttpExtensionProc;
这就是您在建立这两个重要ISAPI DLL的函数时所要做的。下一步,使用 HttpExtensio
nProc,稍微复杂一
点,因此我将把它作为一个独立的部分。
与 HttpExtensionProc 一起工作
HttpExtensionProc语句是DLL的入口。它的作用就好比C语言中的 main() 语句,或者D
elphi 中的
begin...end 部分
这里有一个简单的使用GetExtensionVersion语句的例子
function HttpExtensionProc(var ECB: TExtensionControlBlock):
   DWORD; stdcall;
var
  ResStr: string;
  StrLen: Integer;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := '<HTML><TITLE>Test server result</TITLE>' +
            '<H1>Test server results</H1>' +
            '<BODY>Hello from ISAPI<BR></BODY>' +
            '</HTML>';
  ResStr := Format(
    'HTTP/1.0 200 OK'#13#10+
    'Content-Type: text/html'#13#10+
    'Content-Length: %d'#13#10+
    'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
如果您在浏览其中向这个DLL发出请求,那么您会得到一页这样的回应:
Test Server Results
Hello from ISAPI
函数体内的大部分域提供基本信息的简单的HTML代码密切相关。您还需要填写TExtensi
onControlBlock
中的一些域,如下所示。
注意到在这个纪录里有一个叫做WriteClient的函数指针,您可以引用这个函数把信息传
送回浏览器。当呼叫
这个函数时,您使用到了下面提到的TExtensionControl块中的ConnID字段。当函数被呼
叫时,ConnID为您自
动填充。
在察看函数的代码之前,请让我为您演示所有用到的上文提及的HttpExtensionProc函数
的ISAPI DLL的完
整程序
library Isapi1;
library Isapi1;
uses
  Windows, SysUtils, HTTPExt;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ): BOOL; stdcall;
begin
  Ver.dwExtensionVersion := $00010000;  // We're expecting version 1.0 suppo
rt
  Ver.lpszExtensionDesc := 'Written in Delphi 2.0';
  Result := True;
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ): DWORD; stdc
all;
var
  ResStr: string;
  StrLen: Integer;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := '
' +
            '
Test server results
' +
            '
Isapi says hello to DevRel
';
  ResStr := Format(
    'HTTP/1.0 200 OK'#13#10+
    'Content-Type: text/html'#13#10+
    'Content-Length: %d'#13#10+
    'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
为了运行这个DLL程序,您应该把它复制到您的NT服务器下的脚本目录中去。在我的NT4
.0机器中,它就像这
样:
  c:\winnt\system32\inetsrv\scripts\mystuff\isapi1.dll
在这个例子中,我已经创建了我的名为“mystuff”的目录,它只不过是用来存储我创建
的ISAPI DLLs。您的
目录,当然和我的机器上的不完全一样,取决于您的“inetsrv”目录位置和其它因素。

为成功调用这个DLL,您应该在您的HTML页上增添这个超链接:
<A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR>
当用户点击这个超链接时,ISAPI1 Dll会被呼叫,然后字符串“Hello from ISAPI”会
显示在用户的浏览器
上。如果您并不是把 ISAPI.DLL放在 mystuff 目录下,那么您应该修改上面的HTML代码
来使之与您的情况
适应。注意,您的目录必须与目录 inetsrv 有关,不应,也不能包含您的整个DLL所在
的目录。
下面是呼叫的完整的HTML脚本:
<HTML>
<HEAD>
<TITLE>CharlieC Home Page</TITLE>
</HEAD>
<BODY>
<H1>My Home Page </H1>
<P>
This is the home page for my home computer.
<P>
<A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR>
</BODY>
</HTML>
注意,如果您多次把程序ISAPI1.DLL复制到 mystuff 目录下,在每一次复制之前您应该
关掉网络服务器的
万维网端口。这是因为,在第一次复制这个DLL时,您可以不受限制,但在此之后,它就
属于服务器了。因
此,在您复制第一次拷贝的更新版本时,因当关掉万维网服务。您可以使用网络管理程
序来关掉万维网服
务。这个程序应该在微软网络管理程序组(Microsoft Internet Server group)下面,
在安装网络信息服
务时被安装到程序管理器(Explorer/Program Manager)下。
与 TExtensionControlBlock 一起工作
通过本文中的这一要点,您能够建立您的第一个ISAPI DLL,并且能在第二台机器上的网
页浏览器调用它。
在本文中接下来的ISAPI的其余部分将会更加深入。
这里是HttpExtensionProc参数中比较复杂的部分
PExtensionControlBlock = ^TExtensionControlBlock;
TExtensionControlBlock = packed record
  cbSize: DWORD;           // = sizeof(TExtensionControlBlock)
  dwVersion: DWORD;        // version info of this spec
  ConnID: HCONN;           // Context Do not modify!
  dwHttpStatusCode: DWORD; // HTTP Status code
  // null terminated log info specific to this Extension DLL
  lpszLogData: array [0..HSE_LOG_BUFFER_LEN-1] of Char;
  lpszMethod: PChar;            // REQUEST_METHOD
  lpszQueryString: PChar;       // QUERY_STRING
  lpszPathInfo: PChar;          // PATH_INFO
  lpszPathTranslated: PChar;    // PATH_TRANSLATED
  cbTotalBytes: DWORD;          // Total bytes from client
  cbAvailable: DWORD;           // Available number of bytes
  lpbData: Pointer;             // pointer to cbAvailable bytes
  lpszContentType: PChar;       // Content type of client data
  GetServerVariable: TGetServerVariableProc;
  WriteClient: TWriteClientProc;
  ReadClient: TReadClientProc;
  ServerSupportFunction: TServerSupportFunctionProc;
end;
注意到这个纪录中包含了上面提到过的ConnID字段,并且向 WriteClient 传送第一个参
数。
这个纪录中的第一个参数是为版本控制而设的。它应该是TExtensionControlBlock的大
小的规定。如果微软公
司改变了它的结构,那么它们能够通过检查纪录的大小来判断它们正在处理的结构版本
。您永远也不要这个纪
录中的前三个字段,它们早已被ISAPI填充,在您的程序中,它们只能被访问,而不能被
改变。
这个纪录中最重要的字段可能就是lpszQueryString了,它包含了从服务器上传来的请求
的信息。例如,假设您
已经创建了一个名叫 ISAPI1.Dll。为了调用这个DLL,您就要在您的浏览器的一页上创
建一个像这样的HREF
[注:HTML语言中的一种格式(译者)] :
<A HREF="/scripts/mystuff/test1.dll">Test One</A>
如果您希望响应这个DLL,您就要对上面那行做这样的改动:
<A HREF="/scripts/mystuff/test1.dll?MyQuery">Test One</A>
假如HTML代码段中有像上面两行中的第二行,那么,您的DLL就会在lpszQueryString参
数中得到“MyQuery”
的字符串,特别要注意跟在请求字符串后的请求标志的使用。
当然,您可以随心所欲地改变请求字符串。例如,您可以这样写:
<A HREF="/scripts/mystuff/test1.dll?ServerName">Test One</A>
在这个请求中,这个DLL会回答服务器的名称。您在传递这个参数时,不受任何限制。您
可以传递任何您想要
的东西,而且,如何分析DLL中的信息也由您的喜好决定。
当您从服务器返回信息至浏览器时,您使用到了这个纪录中的“WriteClient”函数指针
。在初始化这个指针
时您不需做任何事;它已经自动地有网络信息服务器传递给您了。
CGI应用程序的作者会注意到传送请求字符串的语法十分熟悉。事实上,ISAPI跟随了CG
I的大多数习惯,在
TExtensionControlBlock中的多数字段可以简单地被CGI技术借用。
在TExtensionControlBlock中的另一个关键字段是 lpbData ,它包含了从浏览起上传给
您的附加信息。
例如,您有一个伴随几个字段的HTML窗体,这些自断中包含的信息就会被一个叫做“lp
Data”的指针传
递。本文中的下一个主题,“从‘确认’按钮中获得信息”,将会着重讲述怎样处理这
种情况。
到现在为止我已经介绍了TExtensionControlBlock中的四个关键字段:
WriteClient: 一个能够让您传递格式化的HTML数据到浏览器上的指针。这个函数用到了

        TExtensionControlBlock的ConnID字段。
lpszQueryString: 从浏览骑上传来的请求。
lpbData: 从浏览器上传给你的人一的附加数据。通常是一个HTML窗体的任意字段的内容
。我将在“确认
按钮”这部分进一步讨论。
要获得其他TExtensionControlBlock中的字段是如何工作的感觉,最好的办法就是亲自
在浏览其中将他们
做对照。换句话说,您会希望创建一个HTML页,使得用能够调用客户端的ISAPI DLL。这
个ISAPI DLL的目
的仅仅是在HTML中格式话TExtensionControlBlock中的每一个字段,然后把它们传回浏
览器。这样就把您
的浏览器变成了一个有点可怕的调试器,来显示TExtensionControlBlock中的所有字段

这里有一个程序,由Borland公司的 Danny Thorpe 编写,他会执行这个任务:
library test1;
uses
  Windows,
  SysUtils,
  HTTPExt;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
  BOOL; stdcall;
begin
  Ver.dwExtensionVersion := $00010000;  // 1.0 support
  Ver.lpszExtensionDesc := 'A test DLL written in Delphi 2.0';
  Result := True;
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
  DWORD; stdcall;
var
  ResStr: string;
  StrLen: Integer;
  Buf: array [0..1024] of Char;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := Format(
    '<HTML><TITLE>Test server result</TITLE>' +
    '<H1>Test server results</H1>' +
    'Size = %d<BR>'+
    'Version = %.8x<BR>'+
    'ConnID = %.8x<BR>'+
    'Method = %s<BR>' +
    'Query = %s<BR>' +
    'PathInfo = %s<BR>'+
    'PathTranslated = %s<BR>'+
    'TotalBytes = %d<BR>'+
    'AvailableBytes = %d<BR>'+
    'ContentType = %s<BR><BR>'+
    '<H1>Some Server Variables</H1>',
    [ECB.cbSize, ECB.dwVersion, ECB.ConnID,
     ECB.lpszMethod, ECB.lpszQueryString,
     ECB.lpszPathInfo, ECB.lpszPathTranslated,
      ECB.cbTotalBytes, ECB.cbAvailable,
      ECB.lpszContentType]);
  with ECB do
  begin
    StrLen := Sizeof(Buf);
    GetServerVariable(ConnID, 'REMOTE_ADDR', @Buf, StrLen);
    ResStr := ResStr + 'REMOTE_ADDR = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'REMOTE_HOST', @Buf, StrLen);
    ResStr := ResStr + 'Remote_Host = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'REMOTE_USER', @Buf, StrLen);
    ResStr := ResStr + 'Remote_User = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'SERVER_NAME', @Buf, StrLen);
    ResStr := ResStr + 'SERVER_NAME = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'SERVER_PORT', @Buf, StrLen);
    ResStr := ResStr + 'SERVER_PORT = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'SERVER_PROTOCOL', @Buf, StrLen);
    ResStr := ResStr + 'SERVER_PROTOCOL = '+Buf+'<BR>';
    StrLen := SizeOf(Buf);
    GetServerVariable(ConnID, 'SERVER_SOFTWARE', @Buf, StrLen);
    ResStr := Format('%sSERVER_SOFTWARE = %s<BR>'+
      'ThreadID = %.8x<BR>',[ResStr, Buf, GetCurrentThreadID]);
  end;
  ResStr := ResStr + '</HTML>';
  ResStr := Format(
    'HTTP/1.0 200 OK'#13#10+
    'Content-Type: text/html'#13#10+
    'Content-Length: %d'#13#10+
    'Content:'#13#10#13#10'%s', [Length(ResStr), ResStr]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
为了调用这个DLL,您应该建立一个包括下面这行的 HRML 脚本
<A HREF="/scripts/mystuff/test1.dll">Test One</A> <BR>
从“确认”按钮获得信息
   通常向您发送信息的HTML窗体中都有一个确认按钮。只要信息量小于49KB,您就可以
认为TExetensionControlBlock中的 lpbData 字段是可用的。这里显示了您可以如何在
大多数情况下获得由这个字段的指针发来的信息:
var
   S: string;
begin
  …
  S := PChar(ECB.lpbData);
  …
end;
    如果从这个字段传来的信息大于48KB,那么您必须呼叫 ReadClient 来获得其余的
信息。
    如果您想要确切地知道在 lpbData 字段中哪些信息是可用的,您可以使用下面两个
函数把数据传回到您的网页浏览器中:
function SetUpResString: string;
begin
  Result := '<HTML>' +
            '<TITLE>Test server result</TITLE>' +
            '<H1>Test server results</H1>' +
            '<BODY>lpbData = %s </BODY>' +
            '</HTML>';
end;
function HttpExtensionProc(var ECB: TExtensionControlBlock):
   DWORD; stdcall;
var
  ResStr: string;
  StrLen: Integer;
  S, S1: string;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := SetUpResString;
  S := PChar(ECB.lpbData);
  ResStr := Format(ResStr, [S]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
假设您已经有了附有下面代码的HTML窗体:
<FORM ACTION="/scripts/mystuff/isapi2.dll" METHOD="POST"
  ENCTYPE="application/x-www-form-urlencoded">
<P>
Enter Number to Square: <INPUT NAME="GetSquare" VALUE=""
  MAXLENGTH="25" SIZE=25>
<P>
 <INPUT TYPE=SUBMIT VALUE="Submit" NAME="GetSquare">
</FORM>
    这段代码会产生一个包含一个供您输入数字的文本区和一个叫做“submit”按钮的
窗体,按钮的名字叫做“GetSquare”。如果有了这个窗体,接着您可以预计上面的两段
程序会返回如下的字符串,假设用户在窗体中的文本区输入了数字23:
lpbData = GetSquare=23&GetSquare=Submit
    为了理解这时究竟发生了什么,注意一下从上面函数中摘录HTML语句中的主体部分
,这部分语句驻留在服务器上,反映如下:
'<BODY>lpbData = %s </BODY>' +
    如果您研究过上面 HttpExtensionProc 函数中的代码,您会发现就在这句之前,它
使用了 Format 语句中的 %s 参数来代替了 ECB.lpbData 中的值。(如果您不清楚语句
 Format 是怎样工作的,请参阅有关的 Delphi 文档)[注:在作者所著的 Delphi2 编
程大全(Delphi2 Unleashed)中的第三章《字符串与文本文件》中有详细说明(译者)
]
    假设上面所示的窗体中,当用户按下“确认”按钮时,lpbData 传递给
ISAPI DLL的值是:
GetSquare=23&GetSquare=Submit
    为了让您有清晰的概念,让我重复一下上面两个语句传回给浏览器的信息是下面的
字符串,您已经看过了:
lpbData = GetSquare=23&GetSquare=Submit
      观看这个过程的最好办法试运行下面列出的 ISAPI2 程序。 ISAPI2 和
ISAPI1 差不多,但他包含了上面显示的新的 HttpExtensionProc 函数,并且它还包含
了 SetUpResString 这个实用函数。
library Isapi2;
uses
  Windows, SysUtils, HTTPExt;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
  BOOL; stdcall;
begin
  Ver.dwExtensionVersion := $00010000;  // 1.0 support
  Ver.lpszExtensionDesc := 'DLL written in Delphi 2.0';
  Result := True;
end;
function SetUpResString: string;
begin
  Result := '<HTML>' +
            '<TITLE>Test server result</TITLE>' +
            '<H1>Test server results</H1>' +
            '<BODY>lpbData = %s </BODY>' +
            '</HTML>';
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
  DWORD; stdcall;
var
  ResStr: string;
  StrLen: Integer;
  S, S1: string;
  Len: Integer;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := SetUpResString;
  S := PChar(ECB.lpbData);
  ResStr := Format(ResStr, [S]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
   一旦您从窗体中获得了由 lpbData 变量传来的信息,您就能分析这些信息或者把它
们返回给用户。比如说,您可以从上面例子中把数字23抽出来,做平方后返回用户。通
过这样做可以使您从用户中获得信息,在这里是数字,对数字进行一些数学运算,最后
把结果返回给用户。这意味着您可以在电波中创建互动的网页,这可是现在因特网编程
中最流行的哦!
   以下是一个通过网络提交数字平方给浏览器的完整的程序代码:
library Isapi3;
{  This code shows how to take input from the user via a browser,
   parse that information, and then return an answer to the user.
   In particular, the user submits a number, this code squares it,
   and then sends the result back to user.
   Here is the form from the browser that submits the information
   for parsing:
  <FORM ACTION="/scripts/mystuff/isapi2.dll" METHOD="POST"
  ENCTYPE="application/x-www-form-urlencoded">
  <P>
  Enter Number to Square: <INPUT NAME="GetSquare" VALUE=""
    MAXLENGTH="25" SIZE=25>
  <P>
  <INPUT TYPE=SUBMIT VALUE="Submit" NAME="GetSquare">
  </FORM>
 }
uses
  Windows, SysUtils, HTTPExt,
  StrBox;
function GetExtensionVersion( var Ver: THSE_VERSION_INFO ):
  BOOL; stdcall;
begin
  Ver.dwExtensionVersion := $00010000;  //  version 1.0 support
  Ver.lpszExtensionDesc := 'ISAPI3.DLL';
  Result := True;
end;
// Parse lpbData and retrieve the number the user passed to us.
function ParseData(S: string): Integer;
begin
  S := StripLastToken(S, '&');
  S := StripFirstToken(S, '=');
  Result := StrToInt(S);
end;
function SetUpResString: string;
begin
  Result := '<HTML>' +
            '<TITLE>Test server result</TITLE>' +
            '<H1>Test server results</H1>' +
            '<BODY>Answer = %d </BODY>' +
            '</HTML>';
end;
function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ):
  DWORD; stdcall;
var
  ResStr: string;
  StrLen: Integer;
  S, S1: string;
  Num: Integer;
begin
  ECB.lpszLogData := 'Delphi DLL Log';
  ECB.dwHTTPStatusCode := 200;
  ResStr := SetUpResString;
  S := PChar(ECB.lpbData);
  Num := ParseData(S);
  Num := Sqr(Num);
  ResStr := Format(ResStr, [Num]);
  StrLen := Length(ResStr);
  ECB.WriteClient(ECB.ConnID, Pointer(ResStr), StrLen, 0);
  Result := HSE_STATUS_SUCCESS;
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
      这段代码从按下确认按钮的用户那里接受下面的字符串,用户要求平方后的数字

GetSquare=5&GetSquare=Submit
       假设这样输入,这段代码会通过因特网返回用户下面的字符串:
Answer = 25
    一句话,用户输入数字5,你返回用户数字25。如果用户提交数字10,那么您返回数
字100。这看起来微不足道,但在这里重要的是因特网上发生的行为[注:指互动网页(
译者)]
      分析用户传来的函数像这样:
// Parse lpbData and retrieve the number the user passed to us.
function ParseData(S: string): Integer;
begin
  S := StripLastToken(S, '&');
  S := StripFirstToken(S, '=');
  Result := StrToInt(S);
end;
    这两个语句在单元中,在本文开头提到过,也包含在我的站点上。[注:这个文件在
网络上几乎到处可见,您也可以向译者索取(译者)][
    在本篇文章中,关于ISAPI我只想谈这么多了。这些内容对于启发您利用这项优越的
技术并获得乐趣来说应该是够用的了。接下来我要谈一下 GetServerVariable 、ReadC
lient 这两个语句,在这方面我只进行了极其有限的试验。在本文中,我附加了 HTTPE
XT.PAS 文件,因为除了这分关键文档,在其他地方您不会找到它。
GetServerVariable 和 ReadClient 语句
    正如您的CGI应用程序中的请求信息一样,您可以使用语句来从服务器上获得信息。
下面是呼叫这个语句的例子:
  Len := HseMaxExtDllNameLen;
  SetLength(S1, Len);
  Dec(Len);
  ECB.GetServerVariable(ECB.ConnID,
                        'CONTENT_LENGTH',
                        PChar(S1),
                        Len);
    首先,这段代码设定了保留从服务器上取得的信息的缓冲区的长度。接着它呼叫服
务器并发出请求,在本例中,它要求获得服务器传来的信息的"CONTENT_LENGTH"。
    微软公司的文献告诉我们,您可以通过 GetServerVariable 的第二个参数来传递跟
着的字符串:
AUTH_TYPE   它包含了使用授权的类型。比如,如果使用的是基本(basic)授权,那么
字符串就是"basic";如果是 NT challenge 回应,字符串就是"NTLM"。其他的授权属尤
其对应的字符串。因为不断有新的授权类型被增添到服务器上,列出所有可能的字符串
是不可行的。如果字符串为空,那么并没有使用任何授权。
CONTENT_LENGTH 脚本预计从客户端回收到的字节数。
CONTENT_TYPE 由请求布告的主体部分提供的信息的内容类型。[注:小弟才疏学浅,a 
POST request 暂译作"请求布告",望方家指正(译者)]
PATH_INFO 附加的路由信息,由客户机提供。它包含了跟在脚本名字之后的URL的漫游路
由。如果有的话,它在请求字符串的前面。
PATH_TRANSLATED 它是 PATH_INFO 的值,但包含了扩充到一个路径标志的所有虚拟路由
的名字。
QUERY_STRING 跟在参考这个脚本的URL中的"?"后面的信息。
REMOTE_ADDR 发出请求的客户机或其代理商(例如,网关或防火墙)的IP地址。
REMOTE_HOST 发出请求的客户机或其代理商(例如,网关或防火墙)的主机名。
REMOTE_USER 它包含了由客户机提供并且由服务器授权的用户名。如果返回空串那么用
户使你名的(但是经过授权)。
UNMAPPED_REMOTE_USER  它是有如下特征的用户的名称:该用户向NT用户帐目发出请求
(这是他以身份出现),在此之前ISAPI应用程序过滤起映射了该用户。
REQUEST_METHOD 是 HTTP 请求方法。
SCRIPT_NAME  执行的脚本程序名称。
SERVER_NAME 当它以自参考URLs形式出现时的主机名或IP地址。
SERVER_PORT 接受请求的TCP/IP的端口。
SERVER_PORT_SECURE 一个非0即1的字符串。当请求由安全端口处理时,它是1;否则是
0。
SERVER_PROTOCOL 接受与这个请求相关的协议的信息的名称和版本。他通常是 HTTP/1.
0 。
SERVER_SOFTWARE 是ISAPI应用DLL程序运行时所在的网页服务器的名称和版本。
ALL_HTTP 先前的变量并没有分析全部的HTTP字段头。这些变量从HTTP_<字段头名>中得
出。字段头(由行标分离)包含了各自的字符串,这些字符串并不会终止。
HTTP_ACCEPT  HTTP字段头的特例。接受的值是:字段由逗号(,)分离。例如:如果下
面的几行是HTTP头的一部分:
     接受:*/*,q=0.1
则URL(2.0新版本的特性)给出它的基础部分。
    要注意的是,上面给出的信息片是由 TExtensionControlBlock 纪录自动传递的。
因此您不需要调用GetServerVariable。不过,如果您确有需要,特别是您要从 ReadCl
ient 中获得信息和需要知道要读入多少信息时,您可以调用它。
     在很多时候,您不需要调用 ReadClient 。但是,您浏览器发出的信息量大于48K
B的时候,您需要调用 ReadClient 来获取其余的信息。

--
         ------没有比人更高的山,
                   没有比脚更长的路。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: bbs.ndc.neu.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:211.936毫秒